Let a binding declare where its chosen value can be read in full
A skill on a node is only an id, and the editor grew a rule of its own to show the instructions behind it: it looked for the literal retriever name "Skills" and built the endpoint by hand. That is a special case inside machinery that is otherwise entirely schema-driven, and the next binding that wanted the same view would have needed another one. @FieldRetriever now takes a definitionUrl, emitted as x-retriever-definition-url, and the editor offers the view wherever it is declared. The MCP server binding deliberately declares none: its definitions endpoint answers with a JSON schema rather than readable text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c044d78a5a
commit
52b02ce349
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <url>/<value>}.
|
||||
*
|
||||
* <p>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 "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue