From 35e49fe6cdeb3d6a67525119b3c36eb471ddce07 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 9 Feb 2026 11:12:06 +0530 Subject: [PATCH 01/26] Update test creation with additional parameters for `@test:Config` --- .../extension/Constants.java | 9 ++ .../extension/TestManagerService.java | 110 ++++++++++++- .../testmanagerservice/extension/Utils.java | 148 +++++++++++++++++- .../extension/model/Annotation.java | 82 +++++++++- 4 files changed, 340 insertions(+), 9 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java index ce6a7d39f7..224a907ec2 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java @@ -29,9 +29,15 @@ public class Constants { public static final String ORG_BALLERINA = "ballerina"; public static final String MODULE_TEST = "test"; + public static final String MODULE_AI = "ai"; public static final String IMPORT_TEST_STMT = "import ballerina/test;"; + public static final String IMPORT_AI_STMT = "import ballerina/ai;"; public static final String FILED_TEMPLATE = "%s: %s"; + public static final String DATA_PROVIDER_MODE_FUNCTION = "function"; + public static final String DATA_PROVIDER_MODE_EVALSET = "evalSet"; + public static final String DEFAULT_EVALSET_FUNCTION_NAME = "loadEvalsetData"; + public static final String TEST_ANNOTATION = "@test:"; public static final String CONFIG_GROUPS = "groups"; public static final String CONFIG_ENABLED = "enabled"; @@ -46,6 +52,7 @@ public class Constants { public static final String SPACE = " "; public static final String EQUAL = "="; public static final String COLON = ":"; + public static final String DOUBLE_QUOTE = "\""; public static final String KEYWORD_FUNCTION = "function"; public static final String KEYWORD_RETURNS = "returns"; @@ -58,6 +65,8 @@ public class Constants { public static final String ON_FAIL_ERROR_STMT = "on fail error err"; + public static final String AI_CONVERSATION_THREAD_TYPE = "ai:ConversationThread"; + private Constants() { } } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 7a92620b5d..d3b5b80fd9 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -19,6 +19,7 @@ package io.ballerina.testmanagerservice.extension; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.NonTerminalNode; @@ -26,6 +27,9 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; import io.ballerina.projects.Project; +import io.ballerina.testmanagerservice.extension.model.Annotation; +import io.ballerina.testmanagerservice.extension.model.FunctionParameter; +import io.ballerina.testmanagerservice.extension.model.Property; import io.ballerina.testmanagerservice.extension.request.AddTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.GetTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.TestsDiscoveryRequest; @@ -37,6 +41,7 @@ import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextRange; import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.common.utils.NameUtil; import org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService; import org.ballerinalang.langserver.commons.workspace.WorkspaceManager; import org.eclipse.lsp4j.TextEdit; @@ -49,7 +54,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; /** * Represents the extended language server service for the test manager service. @@ -172,17 +179,61 @@ public CompletableFuture addTestFunction(AddTestFunctionRe Path filePath = Path.of(request.filePath()); this.workspaceManager.loadProject(filePath); Optional document = this.workspaceManager.document(filePath); - if (document.isEmpty()) { + Optional semanticModel = this.workspaceManager.semanticModel(filePath); + if (document.isEmpty() || semanticModel.isEmpty()) { return new CommonSourceResponse(); } ModulePartNode modulePartNode = document.get().syntaxTree().rootNode(); LineRange lineRange = modulePartNode.lineRange(); List edits = new ArrayList<>(); + + // Check if test import is needed if (!Utils.isTestModuleImportExists(modulePartNode)) { edits.add(new TextEdit(Utils.toRange(lineRange.startLine()), Constants.IMPORT_TEST_STMT)); } + + // Check if dataProviderMode is evalSet + String dataProviderMode = getDataProviderMode(request.function()); + String dataProviderFunctionName = null; + + if (Constants.DATA_PROVIDER_MODE_EVALSET.equals(dataProviderMode)) { + // Add AI import if needed + if (!Utils.isAiModuleImportExists(modulePartNode)) { + edits.add(new TextEdit(Utils.toRange(lineRange.startLine()), Constants.IMPORT_AI_STMT)); + } + + // Generate unique function name for evalSet data provider + List visibleSymbols = semanticModel.get().visibleSymbols( + document.get(), + lineRange.endLine() + ); + Set visibleSymbolNames = visibleSymbols.stream() + .map(Symbol::getName) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + dataProviderFunctionName = NameUtil.getValidatedSymbolName( + visibleSymbolNames, + Constants.DEFAULT_EVALSET_FUNCTION_NAME + ); + + // Generate the evalSet data provider function + String dataProviderFunction = Utils.getEvalSetDataProviderFunctionTemplate( + dataProviderFunctionName + ); + edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), dataProviderFunction)); + + // Add ai:Trace parameter to the test function + addAiConversationThreadParameter(request.function()); + + // Update the dataProvider field with the generated function name + updateDataProviderField(request.function(), dataProviderFunctionName); + } + + // Generate the test function String function = Utils.getTestFunctionTemplate(request.function()); edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), function)); + return new CommonSourceResponse(Map.of(request.filePath(), edits)); } catch (Throwable e) { return new CommonSourceResponse(e); @@ -190,6 +241,63 @@ public CompletableFuture addTestFunction(AddTestFunctionRe }); } + private String getDataProviderMode(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + if (function.annotations() == null) { + return null; + } + for (Annotation annotation : function.annotations()) { + if ("Config".equals(annotation.name())) { + for (Property field : annotation.fields()) { + if ("dataProviderMode".equals(field.originalName())) { + return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; + } + } + } + } + return null; + } + + private void addAiConversationThreadParameter( + io.ballerina.testmanagerservice.extension.model.TestFunction function) { + if (function.parameters() == null) { + return; + } + FunctionParameter.FunctionParameterBuilder paramBuilder = new FunctionParameter.FunctionParameterBuilder(); + paramBuilder.type(Constants.AI_CONVERSATION_THREAD_TYPE); + paramBuilder.variable("thread"); + function.parameters().add(paramBuilder.build()); + } + + private void updateDataProviderField(io.ballerina.testmanagerservice.extension.model.TestFunction function, + String functionName) { + if (function.annotations() == null) { + return; + } + for (Annotation annotation : function.annotations()) { + if ("Config".equals(annotation.name())) { + for (int i = 0; i < annotation.fields().size(); i++) { + Property field = annotation.fields().get(i); + if ("dataProvider".equals(field.originalName())) { + // Update the dataProvider value with the generated function name + Property.PropertyBuilder builder = new Property.PropertyBuilder(); + builder.metadata(field.metadata()); + builder.codedata(field.codedata()); + builder.valueType(field.valueType()); + builder.valueTypeConstraint(field.valueTypeConstraint()); + builder.value(functionName); + builder.originalName(field.originalName()); + builder.placeholder(field.placeholder()); + builder.optional(field.optional()); + builder.editable(field.editable()); + builder.advanced(field.advanced()); + annotation.fields().set(i, builder.build()); + break; + } + } + } + } + } + /** * Update the test function model for the given test function. * diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 668734c393..632d4613b0 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -83,7 +83,7 @@ public static TestFunction getTestFunctionModel(FunctionDefinitionNode functionD // annotations functionDefinitionNode.metadata().ifPresent(metadata -> { List annotations = new ArrayList<>(); - for (AnnotationNode annotationNode: metadata.annotations()) { + for (AnnotationNode annotationNode : metadata.annotations()) { annotations.add(getAnnotationModel(annotationNode, semanticModel)); } functionBuilder.annotations(annotations); @@ -116,7 +116,7 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode return builder.build(); } SeparatedNodeList fields = mappingConstructor.fields(); - for (MappingFieldNode field: fields) { + for (MappingFieldNode field : fields) { if (field instanceof SpecificFieldNode specificFieldNode) { String fieldName = specificFieldNode.fieldName().toSourceCode().trim(); Optional expressionNode = specificFieldNode.valueExpr(); @@ -136,13 +136,60 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode if (expressionNode.isPresent() && expressionNode.get() instanceof ListConstructorExpressionNode expr) { List groupList = new ArrayList<>(); - for (Node groupExpr: expr.expressions()) { + for (Node groupExpr : expr.expressions()) { groupList.add(groupExpr.toSourceCode().trim()); } builder.groups(groupList); } } - default -> { } + case "dataProvider" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.dataProvider(value); + } + } + case "dataProviderMode" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.dataProviderMode(value); + } + } + case "dependsOn" -> { + if (expressionNode.isPresent() && + expressionNode.get() instanceof ListConstructorExpressionNode expr) { + List functionList = new ArrayList<>(); + for (Node functionExpr : expr.expressions()) { + functionList.add(functionExpr.toSourceCode().trim()); + } + builder.dependsOn(functionList); + } + } + case "after" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.after(value); + } + } + case "before" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.before(value); + } + } + case "runs" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.runs(value); + } + } + case "minPassRate" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.minPassRate(value); + } + } + default -> { + } } } } @@ -263,13 +310,56 @@ public static String buildTestConfigAnnotation(Annotation annotation) { case "groups" -> { if (value instanceof List valueList && !valueList.isEmpty() && valueList.getFirst() instanceof String) { - List groupList = valueList.stream().map(Object::toString).toList(); + List groupList = valueList.stream() + .map(group -> { + String groupStr = group.toString(); + if (!groupStr.startsWith(Constants.DOUBLE_QUOTE)) { + return Constants.DOUBLE_QUOTE + groupStr + Constants.DOUBLE_QUOTE; + } + return groupStr; + }) + .toList(); String groupStr = Constants.OPEN_BRACKET + String.join(Constants.COMMA + Constants.SPACE, groupList) + Constants.CLOSE_BRACKET; fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, groupStr)); } } - default -> { } + case "dataProvider" -> { + if (value instanceof String valueStr && !valueStr.isEmpty()) { + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, valueStr)); + } + } + case "dependsOn" -> { + if (value instanceof List valueList && !valueList.isEmpty() + && valueList.getFirst() instanceof String) { + List functionList = valueList.stream().map(Object::toString).toList(); + String functionStr = Constants.OPEN_BRACKET + String.join(Constants.COMMA + Constants.SPACE, + functionList) + Constants.CLOSE_BRACKET; + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, functionStr)); + } + } + case "after" -> { + if (value instanceof String valueStr && !valueStr.isEmpty()) { + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, valueStr)); + } + } + case "before" -> { + if (value instanceof String valueStr && !valueStr.isEmpty()) { + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, valueStr)); + } + } + case "runs" -> { + if (value instanceof String valueStr && !valueStr.isEmpty() && !valueStr.equals("1")) { + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, valueStr)); + } + } + case "minPassRate" -> { + if (value instanceof String valueStr && !valueStr.isEmpty() && !valueStr.equals("1")) { + fieldStrings.add(Constants.FILED_TEMPLATE.formatted(fieldName, valueStr)); + } + } + default -> { + } } } if (fieldStrings.isEmpty()) { @@ -296,6 +386,52 @@ public static boolean isTestModuleImportExists(ModulePartNode node) { }); } + /** + * Check whether the ai import exists in the module. + * + * @param node module part node + * @return true if the import exists, false otherwise + */ + public static boolean isAiModuleImportExists(ModulePartNode node) { + return node.imports().stream().anyMatch(importDeclarationNode -> { + String moduleName = importDeclarationNode.moduleName().stream() + .map(IdentifierToken::text) + .collect(Collectors.joining(".")); + return importDeclarationNode.orgName().isPresent() && + Constants.ORG_BALLERINA.equals(importDeclarationNode.orgName().get().orgName().text()) && + Constants.MODULE_AI.equals(moduleName); + }); + } + + /** + * Generate an evalSet data provider function template. + * + * @param functionName the name of the data provider function + * @return the generated function template + */ + public static String getEvalSetDataProviderFunctionTemplate(String functionName) { + StringBuilder builder = new StringBuilder(); + builder.append(Constants.LINE_SEPARATOR) + .append(Constants.LINE_SEPARATOR) + .append(Constants.KEYWORD_FUNCTION) + .append(Constants.SPACE) + .append(functionName) + .append(Constants.OPEN_PARAM) + .append(Constants.CLOSED_PARAM) + .append(Constants.SPACE) + .append(Constants.KEYWORD_RETURNS) + .append(Constants.SPACE) + .append("map<[" + Constants.AI_CONVERSATION_THREAD_TYPE + "]>|error") + .append(Constants.SPACE) + .append(Constants.OPEN_CURLY_BRACE) + .append(Constants.LINE_SEPARATOR) + .append(Constants.TAB_SEPARATOR) + .append("return {};") + .append(Constants.LINE_SEPARATOR) + .append(Constants.CLOSE_CURLY_BRACE); + return builder.toString(); + } + /** * Convert the syntax-node line range into a lsp4j range. * diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java index fc24c9b105..6be3dd0e69 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java @@ -20,15 +20,24 @@ import io.ballerina.testmanagerservice.extension.Constants; +import java.util.ArrayList; import java.util.List; public record Annotation(Metadata metadata, Codedata codedata, String org, String module, String name, List fields) { public static class ConfigAnnotationBuilder { + private Metadata metadata; private Property groups; private Property enabled; + private Property dataProvider; + private Property dataProviderMode; + private Property dependsOn; + private Property after; + private Property before; + private Property runs; + private Property minPassRate; public void metadata(Metadata metadata) { this.metadata = metadata; @@ -45,8 +54,43 @@ public void enabled(boolean enabled) { "FLAG", "enabled"); } + public void dataProvider(String functionName) { + dataProvider = value("Data Provider", "Data provider function", functionName, + "EXPRESSION", "dataProvider"); + } + + public void dataProviderMode(String mode) { + dataProviderMode = value("Data Provider Mode", "Mode of data provider (function or evalSet)", mode, + "EXPRESSION", "dataProviderMode"); + } + + public void dependsOn(List functionList) { + dependsOn = value("Depends On", "Functions this test depends on", functionList, + "EXPRESSION_SET", "dependsOn"); + } + + public void after(String functionName) { + after = value("After", "Function to run after this test", functionName, + "EXPRESSION", "after"); + } + + public void before(String functionName) { + before = value("Before", "Function to run before this test", functionName, + "EXPRESSION", "before"); + } + + public void runs(String runs) { + this.runs = value("Runs", "Number of times to execute this test", runs, + "EXPRESSION", "runs"); + } + + public void minPassRate(String minPassRate) { + this.minPassRate = value("Minimum Pass Rate (%)", "Minimum percentage of runs that must pass (0-100)", + minPassRate, "SLIDER", "minPassRate"); + } + private static Property value(String label, String description, Object value, String valueType, - String originalName) { + String originalName) { Property.PropertyBuilder builder = new Property.PropertyBuilder(); builder.metadata(new Metadata(label, description)); builder.valueType(valueType); @@ -59,18 +103,52 @@ private static Property value(String label, String description, Object value, St } public Annotation build() { + List properties = new ArrayList<>(); + if (groups == null) { groups = value("Groups", "Groups to run", List.of(), "EXPRESSION_SET", "groups"); } + properties.add(groups); + if (enabled == null) { enabled = value("Enabled", "Enable/Disable the test", true, "FLAG", "enabled"); } + properties.add(enabled); + + if (dataProvider != null) { + properties.add(dataProvider); + } + + if (dataProviderMode != null) { + properties.add(dataProviderMode); + } + + if (dependsOn != null) { + properties.add(dependsOn); + } + + if (after != null) { + properties.add(after); + } + + if (before != null) { + properties.add(before); + } + + if (runs != null) { + properties.add(runs); + } + + if (minPassRate != null) { + properties.add(minPassRate); + } + String org = Constants.ORG_BALLERINA; String module = Constants.MODULE_TEST; String name = "Config"; - return new Annotation(metadata, null, org, module, name, List.of(groups, enabled)); + return new Annotation(metadata, null, org, module, name, properties); } } } From ab063f99860672d1eb278ea596d0f0a44d14a45c Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Wed, 11 Feb 2026 11:00:10 +0530 Subject: [PATCH 02/26] Update eval creation with eval file path for data provider --- .../extension/Constants.java | 1 + .../extension/TestManagerService.java | 23 ++++++++++++++++++- .../testmanagerservice/extension/Utils.java | 15 +++++++++--- .../extension/model/Annotation.java | 10 ++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java index 224a907ec2..ff202d4bb5 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java @@ -54,6 +54,7 @@ public class Constants { public static final String COLON = ":"; public static final String DOUBLE_QUOTE = "\""; + public static final String KEYWORD_ISOLATED = "isolated"; public static final String KEYWORD_FUNCTION = "function"; public static final String KEYWORD_RETURNS = "returns"; public static final String KEYWORD_DO = "do"; diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index d3b5b80fd9..6af8f935a3 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -218,8 +218,13 @@ public CompletableFuture addTestFunction(AddTestFunctionRe ); // Generate the evalSet data provider function + String evalSetFile = getEvalSetFile(request.function()); + if (evalSetFile == null || evalSetFile.isEmpty()) { + evalSetFile = "session.json"; // Default fallback + } String dataProviderFunction = Utils.getEvalSetDataProviderFunctionTemplate( - dataProviderFunctionName + dataProviderFunctionName, + evalSetFile ); edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), dataProviderFunction)); @@ -257,6 +262,22 @@ private String getDataProviderMode(io.ballerina.testmanagerservice.extension.mod return null; } + private String getEvalSetFile(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + if (function.annotations() == null) { + return null; + } + for (Annotation annotation : function.annotations()) { + if ("Config".equals(annotation.name())) { + for (Property field : annotation.fields()) { + if ("evalSetFile".equals(field.originalName())) { + return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; + } + } + } + } + return null; + } + private void addAiConversationThreadParameter( io.ballerina.testmanagerservice.extension.model.TestFunction function) { if (function.parameters() == null) { diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 632d4613b0..1100ad43c4 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -154,6 +154,12 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode builder.dataProviderMode(value); } } + case "evalSetFile" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + builder.evalSetFile(value); + } + } case "dependsOn" -> { if (expressionNode.isPresent() && expressionNode.get() instanceof ListConstructorExpressionNode expr) { @@ -406,13 +412,16 @@ public static boolean isAiModuleImportExists(ModulePartNode node) { /** * Generate an evalSet data provider function template. * - * @param functionName the name of the data provider function + * @param functionName the name of the data provider function + * @param evalSetFilePath the path to the evalSet file * @return the generated function template */ - public static String getEvalSetDataProviderFunctionTemplate(String functionName) { + public static String getEvalSetDataProviderFunctionTemplate(String functionName, String evalSetFilePath) { StringBuilder builder = new StringBuilder(); builder.append(Constants.LINE_SEPARATOR) .append(Constants.LINE_SEPARATOR) + .append(Constants.KEYWORD_ISOLATED) + .append(Constants.SPACE) .append(Constants.KEYWORD_FUNCTION) .append(Constants.SPACE) .append(functionName) @@ -426,7 +435,7 @@ public static String getEvalSetDataProviderFunctionTemplate(String functionName) .append(Constants.OPEN_CURLY_BRACE) .append(Constants.LINE_SEPARATOR) .append(Constants.TAB_SEPARATOR) - .append("return {};") + .append("return ai:loadConversationThreads(\"" + evalSetFilePath + "\");") .append(Constants.LINE_SEPARATOR) .append(Constants.CLOSE_CURLY_BRACE); return builder.toString(); diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java index 6be3dd0e69..f732ae239f 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java @@ -33,6 +33,7 @@ public static class ConfigAnnotationBuilder { private Property enabled; private Property dataProvider; private Property dataProviderMode; + private Property evalSetFile; private Property dependsOn; private Property after; private Property before; @@ -64,6 +65,11 @@ public void dataProviderMode(String mode) { "EXPRESSION", "dataProviderMode"); } + public void evalSetFile(String filePath) { + evalSetFile = value("EvalSet File", "Path to the evalSet data file", filePath, + "EXPRESSION", "evalSetFile"); + } + public void dependsOn(List functionList) { dependsOn = value("Depends On", "Functions this test depends on", functionList, "EXPRESSION_SET", "dependsOn"); @@ -125,6 +131,10 @@ public Annotation build() { properties.add(dataProviderMode); } + if (evalSetFile != null) { + properties.add(evalSetFile); + } + if (dependsOn != null) { properties.add(dependsOn); } From 97016317a7a79cd893bc001cc95c4c4c21d0ebdb Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Wed, 11 Feb 2026 18:28:20 +0530 Subject: [PATCH 03/26] Add support for creating reusable agents --- .../core/AvailableNodesGenerator.java | 33 ++- .../core/model/NodeBuilder.java | 2 + .../core/model/NodeKind.java | 2 + .../core/model/node/AgentRunBuilder.java | 195 ++++++++++++++++++ .../extension/FlowModelGeneratorService.java | 7 + 5 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java index f46456968b..f80270fb8f 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java @@ -40,7 +40,7 @@ import io.ballerina.flowmodelgenerator.core.model.Metadata; import io.ballerina.flowmodelgenerator.core.model.NodeBuilder; import io.ballerina.flowmodelgenerator.core.model.NodeKind; -import io.ballerina.flowmodelgenerator.core.model.node.AgentBuilder; +import io.ballerina.flowmodelgenerator.core.model.node.AgentRunBuilder; import io.ballerina.flowmodelgenerator.core.model.node.ChunkerBuilder; import io.ballerina.flowmodelgenerator.core.model.node.DataLoaderBuilder; import io.ballerina.flowmodelgenerator.core.model.node.EmbeddingProviderBuilder; @@ -74,8 +74,8 @@ import static io.ballerina.modelgenerator.commons.CommonUtils.PERSIST_MODEL_FILE; import static io.ballerina.modelgenerator.commons.CommonUtils.getPersistModelFilePath; import static io.ballerina.modelgenerator.commons.CommonUtils.isAiEmbeddingProvider; -import static io.ballerina.modelgenerator.commons.CommonUtils.isAiModelProvider; import static io.ballerina.modelgenerator.commons.CommonUtils.isAiKnowledgeBase; +import static io.ballerina.modelgenerator.commons.CommonUtils.isAiModelProvider; import static io.ballerina.modelgenerator.commons.CommonUtils.isPersistClient; /** @@ -126,6 +126,10 @@ public JsonArray getAvailableNodes(LinePosition position) { return getAvailableNodes(true, position); } + public JsonArray getAvailableAgents(LinePosition position) { + return this.getAvailableItemsByCategory(position, Category.Name.AGENT, this::getAgent); + } + public JsonArray getAvailableModelProviders(LinePosition position) { return this.getAvailableItemsByCategory(position, Category.Name.MODEL_PROVIDER, this::getModelProvider); } @@ -320,16 +324,13 @@ private List getAiNodes(boolean disableBallerinaAiNodes) { .items(List.of(knowledgeBase, dataLoaders, recursiveDocumentChunker, chunkers, augmentUserQuery, vectorStore, embeddingProvider)).build(); - AvailableNode agentCall = new AvailableNode( - new Metadata.Builder<>(null).label(AgentBuilder.LABEL) - .description(AgentBuilder.DESCRIPTION).build(), - new Codedata.Builder<>(null).node(NodeKind.AGENT_CALL). - org(disableBallerinaAiNodes ? BALLERINAX : BALLERINA).module(Ai.AI_PACKAGE) - .packageName(Ai.AI_PACKAGE).symbol(Ai.AGENT_RUN_METHOD_NAME) - .object(Ai.AGENT_TYPE_NAME).build(), true); + AvailableNode agent = new AvailableNode( + new Metadata.Builder<>(null).label(AgentRunBuilder.LABEL) + .description(AgentRunBuilder.DESCRIPTION).build(), + new Codedata.Builder<>(null).node(NodeKind.AGENTS).build(), true); Category agentCategory = new Category.Builder(null).name(Category.Name.AGENT) - .items(List.of(agentCall)).build(); + .items(List.of(agent)).build(); return List.of(directLlmCategory, ragCategory, agentCategory); } @@ -416,6 +417,8 @@ private Optional getCategory(Symbol symbol, Predicate con FunctionData.Kind kind = methodFunction.kind(); if (kind == FunctionData.Kind.REMOTE) { nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.REMOTE_ACTION_CALL); + } else if (className.equals("Agent") && label.equals("run")) { + nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.AGENT_RUN); } else if (kind == FunctionData.Kind.FUNCTION && isAiKnowledgeBase(classSymbol)) { nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.KNOWLEDGE_BASE_CALL); } else if (kind == FunctionData.Kind.FUNCTION) { @@ -460,6 +463,16 @@ private Optional getCategory(Symbol symbol, Predicate con } } + private Optional getAgent(Symbol symbol) { + return getCategory(symbol, classSymbol -> { + try { + return classSymbol.getName().orElse("").equals(Ai.AGENT_TYPE_NAME); + } catch (Exception e) { + return false; + } + }); + } + private Optional getModelProvider(Symbol symbol) { return getCategory(symbol, classSymbol -> classSymbol.qualifiers().contains(Qualifier.CLIENT) && isAiModelProvider(classSymbol) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeBuilder.java index ba4e56e1b3..c247c37725 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeBuilder.java @@ -25,6 +25,7 @@ import io.ballerina.flowmodelgenerator.core.DiagnosticHandler; import io.ballerina.flowmodelgenerator.core.model.node.AgentBuilder; import io.ballerina.flowmodelgenerator.core.model.node.AgentCallBuilder; +import io.ballerina.flowmodelgenerator.core.model.node.AgentRunBuilder; import io.ballerina.flowmodelgenerator.core.model.node.AssignBuilder; import io.ballerina.flowmodelgenerator.core.model.node.AutomationBuilder; import io.ballerina.flowmodelgenerator.core.model.node.BinaryBuilder; @@ -161,6 +162,7 @@ public abstract class NodeBuilder implements DiagnosticHandler.DiagnosticCapable put(NodeKind.WAIT, WaitBuilder::new); put(NodeKind.AGENT, AgentBuilder::new); put(NodeKind.AGENT_CALL, AgentCallBuilder::new); + put(NodeKind.AGENT_RUN, AgentRunBuilder::new); put(NodeKind.CLASS_INIT, ClassInitBuilder::new); put(NodeKind.MEMORY, MemoryBuilder::new); put(NodeKind.MEMORY_STORE, MemoryStoreBuilder::new); diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeKind.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeKind.java index c0bf0222e1..81f54b1829 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeKind.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/NodeKind.java @@ -80,7 +80,9 @@ public enum NodeKind { AGENT, AGENT_CALL, + AGENT_RUN, CLASS_INIT, + AGENTS, MODEL_PROVIDER, MODEL_PROVIDERS, diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java new file mode 100644 index 0000000000..64aaf3cda7 --- /dev/null +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.flowmodelgenerator.core.model.node; + +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.flowmodelgenerator.core.AiUtils; +import io.ballerina.flowmodelgenerator.core.model.FlowNode; +import io.ballerina.flowmodelgenerator.core.model.NodeKind; +import io.ballerina.flowmodelgenerator.core.model.Property; +import io.ballerina.flowmodelgenerator.core.model.SourceBuilder; +import io.ballerina.flowmodelgenerator.core.utils.FlowNodeUtil; +import io.ballerina.modelgenerator.commons.FunctionData; +import io.ballerina.modelgenerator.commons.ParameterData; +import org.ballerinalang.langserver.common.utils.NameUtil; +import org.eclipse.lsp4j.TextEdit; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Represents a function call node. + * + * @since 1.5.1 + */ +public class AgentRunBuilder extends CallBuilder { + + private static final String BALLERINA = "ballerina"; + + // Agent Call Properties + public static final String QUERY = "query"; + public static final String SESSION_ID = "sessionId"; + public static final String CONTEXT = "context"; + + public static final String LABEL = "Agent"; + public static final String DESCRIPTION = "Executes the agent for a given user query."; + static final Set AGENT_CALL_PARAMS_TO_SHOW = Set.of(QUERY, SESSION_ID, CONTEXT); + + @Override + protected NodeKind getFunctionNodeKind() { + return NodeKind.AGENT_RUN; + } + + @Override + protected FunctionData.Kind getFunctionResultKind() { + return FunctionData.Kind.FUNCTION; + } + + @Override + public void setConcreteConstData() { + codedata().node(NodeKind.AGENT_RUN); + metadata().description(DESCRIPTION); + } + + @Override + public void setConcreteTemplateData(TemplateContext context) { + super.setConcreteTemplateData(context); + // TODO: This is a temporary solution until we have a proper plan for handling all generic types. + makeInferredTypePropertyOptional(); + overrideVariableName(context); + } + + private void makeInferredTypePropertyOptional() { + if (formBuilder == null) { + return; + } + Map props = formBuilder.build(); + for (Map.Entry entry : props.entrySet()) { + Property prop = entry.getValue(); + if (prop.codedata() != null && + ParameterData.Kind.PARAM_FOR_TYPE_INFER.name().equals(prop.codedata().kind())) { + props.put(entry.getKey(), AiUtils.copyAsOptionalAdvanced(prop)); + } + } + } + + private void overrideVariableName(TemplateContext context) { + if (formBuilder == null) { + return; + } + Map props = formBuilder.build(); + Property variableProp = props.get(Property.VARIABLE_KEY); + if (variableProp == null) { + return; + } + String uniqueVarName = NameUtil.generateVariableName("string", context.getAllVisibleSymbolNames()); + props.put(Property.VARIABLE_KEY, AiUtils.createUpdatedProperty(variableProp, uniqueVarName)); + } + + private void newVariableWithInferredTypeAndDefault(SourceBuilder sourceBuilder) { + FlowNode flowNode = sourceBuilder.flowNode; + Optional optionalType = sourceBuilder.getProperty(Property.TYPE_KEY); + Optional variable = sourceBuilder.getProperty(Property.VARIABLE_KEY); + + if (optionalType.isEmpty() || variable.isEmpty()) { + return; + } + + Property type = optionalType.get(); + String typeName = type.value().toString(); + + if (flowNode.codedata().inferredReturnType() != null) { + Optional inferredParam = flowNode.properties().values().stream() + .filter(property -> property.codedata() != null && property.codedata().kind() != null && + property.codedata().kind().equals(ParameterData.Kind.PARAM_FOR_TYPE_INFER.name())) + .findFirst(); + if (inferredParam.isPresent()) { + String returnType = flowNode.codedata().inferredReturnType(); + Object inferredValue = inferredParam.get().value(); + // Default to "string" when the inferred type value is null or empty + String inferredType = (inferredValue != null && !inferredValue.toString().isEmpty()) + ? inferredValue.toString() + : "string"; + String inferredTypeDef = inferredParam.get() + .codedata().originalName(); + typeName = returnType.replace(inferredTypeDef, inferredType); + } + } + + sourceBuilder.token().expressionWithType(typeName, variable.get()).keyword(SyntaxKind.EQUAL_TOKEN); + } + + @Override + public Map> toSource(SourceBuilder sourceBuilder) { + // Use custom variable declaration with inferred type handling and default to "string" + newVariableWithInferredTypeAndDefault(sourceBuilder); + + FlowNode agentRunNode = sourceBuilder.flowNode; + Map> allTextEdits = new HashMap<>(); + + generateAgentCallSource(sourceBuilder, agentRunNode, allTextEdits); + + return allTextEdits; + } + + private void generateAgentCallSource(SourceBuilder sourceBuilder, FlowNode agentRunNode, + Map> allTextEdits) { + Optional connection = agentRunNode.getProperty(Property.CONNECTION_KEY); + if (connection.isEmpty()) { + throw new IllegalStateException("Agent variable must be defined for an agent call node"); + } + + if (FlowNodeUtil.hasCheckKeyFlagSet(agentRunNode)) { + sourceBuilder.token().keyword(SyntaxKind.CHECK_KEYWORD); + } + + Set excludeKeys = getExcludeKeys(agentRunNode); + Map> callTextEdits = sourceBuilder.token() + .name(connection.get().toSourceCode()) + .keyword(BALLERINA.equals(agentRunNode.codedata().org()) ? + SyntaxKind.DOT_TOKEN : SyntaxKind.RIGHT_ARROW_TOKEN) + .name(agentRunNode.metadata().label()) + .stepOut() + .functionParameters(agentRunNode, excludeKeys) + .textEdit() + .build(); + + callTextEdits.forEach((path, textEdits) -> + allTextEdits.merge(path, textEdits, (existing, incoming) -> { + List merged = new ArrayList<>(existing); + merged.addAll(incoming); + return merged; + })); + } + + private Set getExcludeKeys(FlowNode agentRunNode) { + return agentRunNode.properties() != null + ? agentRunNode.properties().keySet().stream() + .filter(key -> !AGENT_CALL_PARAMS_TO_SHOW.contains(key)) + .collect(Collectors.toSet()) + : new HashSet<>(); + } +} diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/FlowModelGeneratorService.java b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/FlowModelGeneratorService.java index 9c8ab241eb..3f2c844a38 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/FlowModelGeneratorService.java +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/main/java/io/ballerina/flowmodelgenerator/extension/FlowModelGeneratorService.java @@ -348,6 +348,13 @@ public CompletableFuture getAvailableVectorKnow generator -> generator.getAvailableVectorKnowledgeBases(request.position())); } + @JsonRequest + public CompletableFuture getAvailableAgents( + FlowModelAvailableNodesRequest request) { + return handleAvailableNodesRequest(request, + generator -> generator.getAvailableAgents(request.position())); + } + @JsonRequest public CompletableFuture getAvailableModelProviders( FlowModelAvailableNodesRequest request) { From d021f1058b925f3906c4a872d177aaa96a64b337 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 04:49:41 +0530 Subject: [PATCH 04/26] Update getTestFunction flow to retrieve test:Config parameters properly --- .../extension/TestManagerService.java | 3 +- .../testmanagerservice/extension/Utils.java | 115 +++++++++++++++++- .../extension/model/Annotation.java | 58 ++++++--- 3 files changed, 154 insertions(+), 22 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 6af8f935a3..2042348bad 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -158,7 +158,8 @@ public CompletableFuture getTestFunction(GetTestFunctio .findFirst(); return matchingFunc.map(functionDefinitionNode -> GetTestFunctionResponse.from( - Utils.getTestFunctionModel(functionDefinitionNode, semanticModel.get()))) + Utils.getTestFunctionModel(functionDefinitionNode, semanticModel.get(), + modulePartNode))) .orElseGet(GetTestFunctionResponse::get); } catch (Throwable e) { return GetTestFunctionResponse.from(e); diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 1100ad43c4..0ab62a51f0 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -21,7 +21,14 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.AnnotationSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.CheckExpressionNode; +import io.ballerina.compiler.syntax.tree.ExpressionFunctionBodyNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.ExpressionStatementNode; +import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; +import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; +import io.ballerina.compiler.syntax.tree.FunctionBodyNode; +import io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; @@ -29,8 +36,12 @@ import io.ballerina.compiler.syntax.tree.MappingFieldNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.PositionalArgumentNode; +import io.ballerina.compiler.syntax.tree.ReturnStatementNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.compiler.syntax.tree.StatementNode; import io.ballerina.testmanagerservice.extension.model.Annotation; import io.ballerina.testmanagerservice.extension.model.Codedata; import io.ballerina.testmanagerservice.extension.model.FunctionParameter; @@ -71,7 +82,8 @@ public static String getExprUri(String sourcePath) { } public static TestFunction getTestFunctionModel(FunctionDefinitionNode functionDefinitionNode, - SemanticModel semanticModel) { + SemanticModel semanticModel, + ModulePartNode modulePartNode) { TestFunction.FunctionBuilder functionBuilder = new TestFunction.FunctionBuilder(); functionBuilder.metadata(new Metadata("Test Function", "Test Function")) @@ -84,7 +96,7 @@ public static TestFunction getTestFunctionModel(FunctionDefinitionNode functionD functionDefinitionNode.metadata().ifPresent(metadata -> { List annotations = new ArrayList<>(); for (AnnotationNode annotationNode : metadata.annotations()) { - annotations.add(getAnnotationModel(annotationNode, semanticModel)); + annotations.add(getAnnotationModel(annotationNode, semanticModel, modulePartNode)); } functionBuilder.annotations(annotations); @@ -95,7 +107,8 @@ public static TestFunction getTestFunctionModel(FunctionDefinitionNode functionD return functionBuilder.build(); } - public static Annotation getAnnotationModel(AnnotationNode annotationNode, SemanticModel semanticModel) { + public static Annotation getAnnotationModel(AnnotationNode annotationNode, SemanticModel semanticModel, + ModulePartNode modulePartNode) { AnnotationSymbol annotationSymbol = (AnnotationSymbol) semanticModel.symbol(annotationNode).get(); String annotName = annotationSymbol.getName().orElse(""); if (annotName.isEmpty()) { @@ -104,18 +117,20 @@ public static Annotation getAnnotationModel(AnnotationNode annotationNode, Seman Optional annotValue = annotationNode.annotValue(); MappingConstructorExpressionNode mappingConstructor = annotValue.orElse(null); if (annotName.equals("Config")) { - return buildConfigAnnotation(mappingConstructor); + return buildConfigAnnotation(mappingConstructor, modulePartNode); } return null; } - private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode mappingConstructor) { + private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode mappingConstructor, + ModulePartNode modulePartNode) { Annotation.ConfigAnnotationBuilder builder = new Annotation.ConfigAnnotationBuilder(); builder.metadata(new Metadata("Config", "Test Function Configurations")); if (mappingConstructor == null) { return builder.build(); } SeparatedNodeList fields = mappingConstructor.fields(); + String dataProviderName = null; for (MappingFieldNode field : fields) { if (field instanceof SpecificFieldNode specificFieldNode) { String fieldName = specificFieldNode.fieldName().toSourceCode().trim(); @@ -145,6 +160,7 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode case "dataProvider" -> { if (expressionNode.isPresent()) { String value = expressionNode.get().toSourceCode().trim(); + dataProviderName = value; builder.dataProvider(value); } } @@ -199,9 +215,98 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode } } } + + // Extract evalSetFile from data provider function if present + if (dataProviderName != null) { + String evalSetPath = extractEvalSetFileFromDataProvider(modulePartNode, dataProviderName); + if (!evalSetPath.isEmpty()) { + builder.evalSetFile(evalSetPath); + } + } + return builder.build(); } + private static String extractEvalSetFileFromDataProvider(ModulePartNode modulePartNode, + String dataProviderFunctionName) { + if (dataProviderFunctionName == null || dataProviderFunctionName.isEmpty()) { + return ""; + } + + // Remove quotes if present in function name + final String functionName = dataProviderFunctionName.replaceAll("\"", ""); + + // Find the data provider function + Optional dataProviderFunc = modulePartNode.members().stream() + .filter(mem -> mem instanceof FunctionDefinitionNode) + .map(mem -> (FunctionDefinitionNode) mem) + .filter(mem -> mem.functionName().text().trim().equals(functionName)) + .findFirst(); + + if (dataProviderFunc.isEmpty()) { + return ""; + } + + // Extract the ai:loadConversationThreads() call from function body + FunctionBodyNode functionBody = dataProviderFunc.get().functionBody(); + if (functionBody instanceof FunctionBodyBlockNode blockBody) { + // Handle block-based function body + return extractFromStatements(blockBody.statements()); + } else if (functionBody instanceof ExpressionFunctionBodyNode exprBody) { + // Handle expression-based function body (return expr;) + return extractFromExpression(exprBody.expression()); + } + + return ""; + } + + private static String extractFromStatements(NodeList statements) { + for (StatementNode statement : statements) { + if (statement instanceof ReturnStatementNode returnStmt) { + Optional expr = returnStmt.expression(); + if (expr.isPresent()) { + String result = extractFromExpression(expr.get()); + if (!result.isEmpty()) { + return result; + } + } + } else if (statement instanceof ExpressionStatementNode exprStmt) { + String result = extractFromExpression(exprStmt.expression()); + if (!result.isEmpty()) { + return result; + } + } + } + return ""; + } + + private static String extractFromExpression(ExpressionNode expression) { + // Handle check expression: check ai:loadConversationThreads(...) + if (expression instanceof CheckExpressionNode checkExpr) { + return extractFromExpression(checkExpr.expression()); + } + + // Handle function call: ai:loadConversationThreads("path") + if (expression instanceof FunctionCallExpressionNode funcCall) { + String functionName = funcCall.functionName().toSourceCode().trim(); + + // Check if it's the ai:loadConversationThreads function + if (functionName.contains("loadConversationThreads")) { + // Extract first argument (the file path) + for (FunctionArgumentNode arg : funcCall.arguments()) { + if (arg instanceof PositionalArgumentNode positionalArg) { + ExpressionNode argExpr = positionalArg.expression(); + String argValue = argExpr.toSourceCode().trim(); + // Remove surrounding quotes + return argValue.replaceAll("^\"|\"$", ""); + } + } + } + } + + return ""; + } + public static String getTestFunctionTemplate(TestFunction function) { StringBuilder builder = new StringBuilder(); diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java index f732ae239f..bb0f32106a 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java @@ -123,37 +123,63 @@ public Annotation build() { } properties.add(enabled); - if (dataProvider != null) { - properties.add(dataProvider); + // Always add dataProvider (default to empty string) + if (dataProvider == null) { + dataProvider = value("Data Provider", "Data provider function", "", + "EXPRESSION", "dataProvider"); } + properties.add(dataProvider); - if (dataProviderMode != null) { - properties.add(dataProviderMode); + // Always add dataProviderMode (default to "function") + if (dataProviderMode == null) { + dataProviderMode = value("Data Provider Mode", + "Mode of data provider (function or evalSet)", "function", + "EXPRESSION", "dataProviderMode"); } + properties.add(dataProviderMode); - if (evalSetFile != null) { - properties.add(evalSetFile); + // Always add evalSetFile (default to empty string) + if (evalSetFile == null) { + evalSetFile = value("EvalSet File", "Path to the evalSet data file", "", + "EXPRESSION", "evalSetFile"); } + properties.add(evalSetFile); - if (dependsOn != null) { - properties.add(dependsOn); + // Always add dependsOn (default to empty list) + if (dependsOn == null) { + dependsOn = value("Depends On", "Functions this test depends on", List.of(), + "EXPRESSION_SET", "dependsOn"); } + properties.add(dependsOn); - if (after != null) { - properties.add(after); + // Always add after (default to empty string) + if (after == null) { + after = value("After", "Function to run after this test", "", + "EXPRESSION", "after"); } + properties.add(after); - if (before != null) { - properties.add(before); + // Always add before (default to empty string) + if (before == null) { + before = value("Before", "Function to run before this test", "", + "EXPRESSION", "before"); } + properties.add(before); - if (runs != null) { - properties.add(runs); + // Always add runs (default to "1") + if (runs == null) { + runs = value("Runs", "Number of times to execute this test", "1", + "EXPRESSION", "runs"); } + properties.add(runs); - if (minPassRate != null) { - properties.add(minPassRate); + // Always add minPassRate (default to "1") + if (minPassRate == null) { + minPassRate = value("Minimum Pass Rate (%)", + "Minimum percentage of runs that must pass (0-100)", + "1", "SLIDER", "minPassRate"); } + properties.add(minPassRate); String org = Constants.ORG_BALLERINA; String module = Constants.MODULE_TEST; From 9b2251f0aa571ddae26c9c21e62ab975bdbd2f59 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 11:43:58 +0530 Subject: [PATCH 05/26] Add support for updating config fields in tests --- .../extension/TestManagerService.java | 249 ++++++++++++++++++ .../extension/model/Annotation.java | 2 +- .../config/get_test_function1.json | 96 +++++++ .../config/project_with_test_config1.json | 13 + 4 files changed, 359 insertions(+), 1 deletion(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 2042348bad..203f775265 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -20,8 +20,13 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ExpressionFunctionBodyNode; +import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NonTerminalNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; @@ -37,6 +42,7 @@ import io.ballerina.testmanagerservice.extension.response.CommonSourceResponse; import io.ballerina.testmanagerservice.extension.response.GetTestFunctionResponse; import io.ballerina.testmanagerservice.extension.response.TestsDiscoveryResponse; +import io.ballerina.tools.text.LinePosition; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextRange; @@ -348,6 +354,8 @@ public CompletableFuture updateTestFunction(UpdateTestFunc } List edits = new ArrayList<>(); + + // Update function name if changed String functionName = functionDefinitionNode.functionName().text().trim(); LineRange nameRange = functionDefinitionNode.functionName().lineRange(); if (!functionName.equals(request.function().functionName().value())) { @@ -355,13 +363,254 @@ public CompletableFuture updateTestFunction(UpdateTestFunc request.function().functionName().value().toString())); } + // Update function signature LineRange signatureRange = functionDefinitionNode.functionSignature().lineRange(); String functionSignature = Utils.buildFunctionSignature(request.function()); edits.add(new TextEdit(Utils.toRange(signatureRange), functionSignature)); + + // Update annotations if present + if (functionDefinitionNode.metadata().isPresent() && + request.function().annotations() != null && + !request.function().annotations().isEmpty()) { + updateAnnotations(functionDefinitionNode, request.function(), edits); + } + + // Update evalSetFile in data provider if present + updateEvalSetFile(textDocument, modulePartNode, request.function(), edits); + return new CommonSourceResponse(Map.of(request.filePath(), edits)); } catch (Throwable e) { return new CommonSourceResponse(e); } }); } + + /** + * Update the annotations of the function. + * + * @param functionDefinitionNode the function definition node + * @param function the test function model + * @param edits list of text edits to add to + */ + private void updateAnnotations(FunctionDefinitionNode functionDefinitionNode, + io.ballerina.testmanagerservice.extension.model.TestFunction function, + List edits) { + if (functionDefinitionNode.metadata().isEmpty()) { + return; + } + + MetadataNode metadataNode = functionDefinitionNode.metadata().get(); + NodeList annotations = metadataNode.annotations(); + + if (annotations.isEmpty()) { + return; + } + + // Get the range of all annotations + LineRange annotationsRange = getAnnotationsRange(annotations); + if (annotationsRange == null) { + return; + } + + // Build new annotation string + String newAnnotations = Utils.buildAnnotation(function.annotations()); + if (!newAnnotations.isEmpty()) { + edits.add(new TextEdit(Utils.toRange(annotationsRange), newAnnotations)); + } + } + + /** + * Update the evalSetFile in the data provider function. + * + * @param textDocument the text document + * @param modulePartNode the module part node + * @param function the test function model + * @param edits list of text edits to add to + */ + private void updateEvalSetFile(TextDocument textDocument, ModulePartNode modulePartNode, + io.ballerina.testmanagerservice.extension.model.TestFunction function, + List edits) { + String newEvalSetFile = getEvalSetFile(function); + String dataProviderName = getDataProviderName(function); + + if (newEvalSetFile == null || newEvalSetFile.isEmpty() || + dataProviderName == null || dataProviderName.isEmpty()) { + return; + } + + // Find the data provider function + Optional dataProviderFunc = findDataProviderFunction(modulePartNode, dataProviderName); + if (dataProviderFunc.isEmpty()) { + return; + } + + // Find the evalSetFile path location in the data provider function + Optional filePathRange = findEvalSetFilePathLocation(dataProviderFunc.get(), textDocument); + if (filePathRange.isEmpty()) { + return; + } + + // Extract current file path from the source + String currentFilePath = extractCurrentFilePath(textDocument, filePathRange.get()); + + // If changed, add edit to update it + if (!currentFilePath.equals(newEvalSetFile)) { + edits.add(new TextEdit(Utils.toRange(filePathRange.get()), "\"" + newEvalSetFile + "\"")); + } + } + + /** + * Get the line range covering all annotations. + * + * @param annotations the list of annotation nodes + * @return the line range covering all annotations, or null if empty + */ + private LineRange getAnnotationsRange(NodeList annotations) { + if (annotations.isEmpty()) { + return null; + } + + LinePosition startPos = annotations.get(0).lineRange().startLine(); + LinePosition endPos = annotations.get(annotations.size() - 1).lineRange().endLine(); + + return LineRange.from(null, startPos, endPos); + } + + /** + * Extract the dataProvider field value from annotations. + * + * @param function the test function model + * @return the data provider name, or null if not found + */ + private String getDataProviderName(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + if (function.annotations() == null) { + return null; + } + for (Annotation annotation : function.annotations()) { + if ("Config".equals(annotation.name())) { + for (Property field : annotation.fields()) { + if ("dataProvider".equals(field.originalName())) { + return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; + } + } + } + } + return null; + } + + /** + * Find the data provider function by name in the module. + * + * @param modulePartNode the module part node + * @param dataProviderName the data provider function name + * @return the function definition node, or empty if not found + */ + private Optional findDataProviderFunction(ModulePartNode modulePartNode, + String dataProviderName) { + if (dataProviderName == null || dataProviderName.isEmpty()) { + return Optional.empty(); + } + + // Remove quotes if present in function name + final String functionName = dataProviderName.replaceAll("\"", ""); + + return modulePartNode.members().stream() + .filter(mem -> mem instanceof FunctionDefinitionNode) + .map(mem -> (FunctionDefinitionNode) mem) + .filter(mem -> mem.functionName().text().trim().equals(functionName)) + .findFirst(); + } + + /** + * Find the location of the evalSetFile path in the data provider function. + * + * @param dataProviderFunc the data provider function + * @param textDocument the text document + * @return the line range of the file path string, or empty if not found + */ + private Optional findEvalSetFilePathLocation(FunctionDefinitionNode dataProviderFunc, + TextDocument textDocument) { + io.ballerina.compiler.syntax.tree.FunctionBodyNode functionBody = dataProviderFunc.functionBody(); + + if (functionBody instanceof FunctionBodyBlockNode blockBody) { + return findFilePathInStatements(blockBody.statements()); + } else if (functionBody instanceof ExpressionFunctionBodyNode exprBody) { + return findFilePathInExpression(exprBody.expression()); + } + + return Optional.empty(); + } + + /** + * Find the file path location in statements. + * + * @param statements the list of statements + * @return the line range of the file path string, or empty if not found + */ + private Optional findFilePathInStatements( + NodeList statements) { + for (io.ballerina.compiler.syntax.tree.StatementNode statement : statements) { + if (statement instanceof io.ballerina.compiler.syntax.tree.ReturnStatementNode returnStmt) { + Optional expr = returnStmt.expression(); + if (expr.isPresent()) { + Optional result = findFilePathInExpression(expr.get()); + if (result.isPresent()) { + return result; + } + } + } else if (statement instanceof io.ballerina.compiler.syntax.tree.ExpressionStatementNode exprStmt) { + Optional result = findFilePathInExpression(exprStmt.expression()); + if (result.isPresent()) { + return result; + } + } + } + return Optional.empty(); + } + + /** + * Find the file path location in an expression. + * + * @param expression the expression node + * @return the line range of the file path string, or empty if not found + */ + private Optional findFilePathInExpression( + io.ballerina.compiler.syntax.tree.ExpressionNode expression) { + // Handle check expression: check ai:loadConversationThreads(...) + if (expression instanceof io.ballerina.compiler.syntax.tree.CheckExpressionNode checkExpr) { + return findFilePathInExpression(checkExpr.expression()); + } + + // Handle function call: ai:loadConversationThreads("path") + if (expression instanceof io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode funcCall) { + String functionName = funcCall.functionName().toSourceCode().trim(); + + // Check if it's the ai:loadConversationThreads function + if (functionName.contains("loadConversationThreads")) { + // Extract first argument (the file path) + for (io.ballerina.compiler.syntax.tree.FunctionArgumentNode arg : funcCall.arguments()) { + if (arg instanceof io.ballerina.compiler.syntax.tree.PositionalArgumentNode positionalArg) { + io.ballerina.compiler.syntax.tree.ExpressionNode argExpr = positionalArg.expression(); + return Optional.of(argExpr.lineRange()); + } + } + } + } + + return Optional.empty(); + } + + /** + * Extract the current file path from the source at the given range. + * + * @param textDocument the text document + * @param range the line range of the file path + * @return the file path without quotes + */ + private String extractCurrentFilePath(TextDocument textDocument, LineRange range) { + int start = textDocument.textPositionFrom(range.startLine()); + int end = textDocument.textPositionFrom(range.endLine()); + String text = textDocument.toString().substring(start, end); + return text.replaceAll("^\"|\"$", "").trim(); + } } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java index bb0f32106a..1d8f350171 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java @@ -66,7 +66,7 @@ public void dataProviderMode(String mode) { } public void evalSetFile(String filePath) { - evalSetFile = value("EvalSet File", "Path to the evalSet data file", filePath, + evalSetFile = value("Evalset File", "Path to the evalSet data file", filePath, "EXPRESSION", "evalSetFile"); } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/get_test_function/config/get_test_function1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/get_test_function/config/get_test_function1.json index 72eb0101ab..c3510bae4b 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/get_test_function/config/get_test_function1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/get_test_function/config/get_test_function1.json @@ -124,6 +124,102 @@ "optional": true, "editable": true, "advanced": false + }, + { + "metadata": { + "label": "Data Provider", + "description": "Data provider function" + }, + "valueType": "EXPRESSION", + "originalName": "dataProvider", + "value": "", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Data Provider Mode", + "description": "Mode of data provider (function or evalSet)" + }, + "valueType": "EXPRESSION", + "originalName": "dataProviderMode", + "value": "function", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "EvalSet File", + "description": "Path to the evalSet data file" + }, + "valueType": "EXPRESSION", + "originalName": "evalSetFile", + "value": "", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Depends On", + "description": "Functions this test depends on" + }, + "valueType": "EXPRESSION_SET", + "originalName": "dependsOn", + "value": [], + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "After", + "description": "Function to run after this test" + }, + "valueType": "EXPRESSION", + "originalName": "after", + "value": "", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Before", + "description": "Function to run before this test" + }, + "valueType": "EXPRESSION", + "originalName": "before", + "value": "", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Runs", + "description": "Number of times to execute this test" + }, + "valueType": "EXPRESSION", + "originalName": "runs", + "value": "1", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Minimum Pass Rate (%)", + "description": "Minimum percentage of runs that must pass (0-100)" + }, + "valueType": "SLIDER", + "originalName": "minPassRate", + "value": "1", + "optional": true, + "editable": true, + "advanced": false } ] } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json index 4689549714..414b1787a8 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json @@ -130,6 +130,19 @@ }, "output": { "sample1/tests/test1.bal": [ + { + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 29 + } + }, + "newText": "@test:Config{\ngroups: [\"g1\"]\n}" + }, { "range": { "start": { From e531e4d2107967374f67ad28066c7af318612345 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 11:54:59 +0530 Subject: [PATCH 06/26] Move chat agent back to module level --- .../builder/service/AiChatServiceBuilder.java | 60 ++----------------- 1 file changed, 5 insertions(+), 55 deletions(-) diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AiChatServiceBuilder.java b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AiChatServiceBuilder.java index 93c1aac2bb..19963aefe3 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AiChatServiceBuilder.java +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/main/java/io/ballerina/servicemodelgenerator/extension/builder/service/AiChatServiceBuilder.java @@ -19,7 +19,6 @@ package io.ballerina.servicemodelgenerator.extension.builder.service; import io.ballerina.compiler.syntax.tree.ModulePartNode; -import io.ballerina.projects.SemanticVersion; import io.ballerina.servicemodelgenerator.extension.model.Service; import io.ballerina.servicemodelgenerator.extension.model.Value; import io.ballerina.servicemodelgenerator.extension.model.context.AddModelContext; @@ -27,7 +26,6 @@ import io.ballerina.servicemodelgenerator.extension.util.ListenerUtil; import io.ballerina.servicemodelgenerator.extension.util.Utils; import org.eclipse.lsp4j.TextEdit; -import org.wso2.ballerinalang.util.RepoUtils; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -55,35 +53,15 @@ public final class AiChatServiceBuilder extends AbstractServiceBuilder { private static final String AGENT_NAME_PROPERTY = "agentName"; private static final String AGENT = "Agent"; - private static final String MODEL = "Model"; private static final String DEFAULT_AGENT_NAME = "chat"; - private static final String MIN_BALLERINA_VERSION = "2201.13.0"; - private static String getServiceFields(String agentVarName) { - return " private final ai:Agent " + agentVarName + ";"; - } - - private static String getServiceInitFunction(String agentVarName, String modelVarName) { - return String.format( - " function init() returns error? { %s" + - " self.%s = check new (%s" + - " systemPrompt = {role: string ``, instructions: string ``}, model = %s" + - ", tools = []%s" + - " );%s" + - " }", - NEW_LINE, agentVarName, NEW_LINE, modelVarName, NEW_LINE, NEW_LINE - ); - } - - private String buildAgentMethodCall(String agentVarName, String orgName, boolean useSelfReference) { - String prefix = useSelfReference ? "self." : ""; + private String buildAgentMethodCall(String agentVarName, String orgName) { String operator = BALLERINA.equals(orgName) ? "." : "->"; - return String.format("%s%s%srun(request.message, request.sessionId)", prefix, agentVarName, operator); + return String.format("%s%srun(request.message, request.sessionId)", agentVarName, operator); } private String getAgentChatFunction(String agentVarName, String orgName) { - boolean useSelfReference = isBallerinaVersionAtLeast(MIN_BALLERINA_VERSION); - String methodCall = buildAgentMethodCall(agentVarName, orgName, useSelfReference); + String methodCall = buildAgentMethodCall(agentVarName, orgName); return String.format( " resource function post chat(@http:Payload ai:ChatReqMessage request) " + @@ -115,7 +93,6 @@ public Map> addModel(AddModelContext context) throws Exce String agentName = getAgentNameFromService(service); String agentVarName = agentName + AGENT; - String modelVarName = agentName + MODEL; addDefaultListenerEdit(context, edits); @@ -124,7 +101,7 @@ public Map> addModel(AddModelContext context) throws Exce StringBuilder serviceBuilder = new StringBuilder(NEW_LINE); buildServiceNodeStr(service, serviceBuilder); - buildServiceNodeBody(getServiceMembers(agentVarName, modelVarName, service.getOrgName()), serviceBuilder); + buildServiceNodeBody(getServiceMembers(agentVarName, service.getOrgName()), serviceBuilder); ModulePartNode rootNode = context.document().syntaxTree().rootNode(); edits.add(new TextEdit(Utils.toRange(rootNode.lineRange().endLine()), serviceBuilder.toString())); @@ -171,39 +148,12 @@ private void addDefaultListenerEdit(AddModelContext context, List edit } } - private List getServiceMembers(String agentVarName, String modelVarName, String orgName) { + private List getServiceMembers(String agentVarName, String orgName) { List members = new ArrayList<>(); - - // Check Ballerina version - only include fields and init function for 2201.13.0+ - if (isBallerinaVersionAtLeast(MIN_BALLERINA_VERSION)) { - members.add(getServiceFields(agentVarName)); - members.add(getServiceInitFunction(agentVarName, modelVarName)); - } - members.add(getAgentChatFunction(agentVarName, orgName)); return members; } - private boolean isBallerinaVersionAtLeast(String minVersion) { - try { - String ballerinaVersion = RepoUtils.getBallerinaPackVersion(); - if (ballerinaVersion == null || ballerinaVersion.isEmpty()) { - return false; - } - String[] versionParts = ballerinaVersion.split("-"); - if (versionParts.length == 0 || versionParts[0].isEmpty()) { - return false; - } - String coreVersion = versionParts[0]; - SemanticVersion current = SemanticVersion.from(coreVersion); - SemanticVersion minimum = SemanticVersion.from(minVersion); - return current.greaterThanOrEqualTo(minimum); - } catch (RuntimeException e) { - // If version check fails, default to generating agent at module level - return false; - } - } - private void addRequiredImports(Service service, ModulePartNode rootNode, List edits) { Set importStmts = new LinkedHashSet<>(); From 290214bc5b54dba5e088cbce6135a0922fb50ec9 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 13:57:30 +0530 Subject: [PATCH 07/26] Update tests --- .../core/model/node/AgentRunBuilder.java | 2 +- .../test/resources/available_nodes/config/caller.json | 9 ++------- .../resources/available_nodes/config/connector1.json | 9 ++------- .../resources/available_nodes/config/connector2.json | 9 ++------- .../resources/available_nodes/config/connector3.json | 9 ++------- .../resources/available_nodes/config/force_assign.json | 9 ++------- .../test/resources/available_nodes/config/foreach1.json | 9 ++------- .../test/resources/available_nodes/config/function1.json | 9 ++------- .../test/resources/available_nodes/config/function2.json | 9 ++------- .../test/resources/available_nodes/config/function3.json | 9 ++------- .../available_nodes/config/inside_nested_foreach.json | 9 ++------- .../src/test/resources/available_nodes/config/lock1.json | 9 ++------- .../test/resources/available_nodes/config/match1.json | 9 ++------- .../test/resources/available_nodes/config/match2.json | 9 ++------- .../test/resources/available_nodes/config/method1.json | 9 ++------- .../available_nodes/config/module_with_existing_ai.json | 9 ++------- .../available_nodes/config/on_fail_clause1.json | 9 ++------- .../resources/available_nodes/config/transaction1.json | 9 ++------- .../resources/available_nodes/config/transaction2.json | 9 ++------- .../test/resources/available_nodes/config/while1.json | 9 ++------- 20 files changed, 39 insertions(+), 134 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java index 64aaf3cda7..e9e0ce76f2 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java @@ -55,7 +55,7 @@ public class AgentRunBuilder extends CallBuilder { public static final String CONTEXT = "context"; public static final String LABEL = "Agent"; - public static final String DESCRIPTION = "Executes the agent for a given user query."; + public static final String DESCRIPTION = "Create or reuse an Agent."; static final Set AGENT_CALL_PARAMS_TO_SHOW = Set.of(QUERY, SESSION_ID, CONTEXT); @Override diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/caller.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/caller.json index c1f5d1f930..2c66f351d6 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/caller.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/caller.json @@ -268,15 +268,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector1.json index 9c9a6e4e08..267d0ecd9c 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector1.json @@ -515,15 +515,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector2.json index cfe838b06a..341c6e9bb5 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector2.json @@ -4633,15 +4633,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector3.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector3.json index d1c9b29378..711865ef5b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector3.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/connector3.json @@ -230,15 +230,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/force_assign.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/force_assign.json index 82ea6999f4..1287cd4154 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/force_assign.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/force_assign.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/foreach1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/foreach1.json index 54846f044d..b2aa7c20e8 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/foreach1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/foreach1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function1.json index 82ea6999f4..1287cd4154 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function2.json index 292143e005..dc5f43dcb5 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function2.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function3.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function3.json index fe01c53ead..2d29a5257c 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function3.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/function3.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/inside_nested_foreach.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/inside_nested_foreach.json index b2880a15eb..80d368c2af 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/inside_nested_foreach.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/inside_nested_foreach.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/lock1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/lock1.json index 275bfc29a4..cc3b13a477 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/lock1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/lock1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match1.json index 36854735ef..3d46bc247e 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match2.json index 71504ed0c7..c9f2a67e28 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/match2.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/method1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/method1.json index 03f7243aba..49f8dbf08c 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/method1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/method1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/module_with_existing_ai.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/module_with_existing_ai.json index c00ff6a1d6..17f6eef1ed 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/module_with_existing_ai.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/module_with_existing_ai.json @@ -149,15 +149,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/on_fail_clause1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/on_fail_clause1.json index ad386ff604..1693614d63 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/on_fail_clause1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/on_fail_clause1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction1.json index 80c0b8165b..43bf038db8 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction2.json index ad386ff604..1693614d63 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/transaction2.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/while1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/while1.json index bbb5e9bbcc..675d06416b 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/while1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/available_nodes/config/while1.json @@ -146,15 +146,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerina", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } From b634fca0d310bf1d37ff52aa4c6ae9915c7a4093 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 14:42:59 +0530 Subject: [PATCH 08/26] Update tests --- .../agents_manager/config/available_agents.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/available_agents.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/available_agents.json index cbbc669115..156af80bcc 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/available_agents.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/available_agents.json @@ -935,15 +935,10 @@ { "metadata": { "label": "Agent", - "description": "Create new agent" + "description": "Create or reuse an Agent." }, "codedata": { - "node": "AGENT_CALL", - "org": "ballerinax", - "module": "ai", - "packageName": "ai", - "object": "Agent", - "symbol": "run" + "node": "AGENTS" }, "enabled": true } From 883a0aa9d54fafd9f30169255523827c4dcb66b2 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 15:08:44 +0530 Subject: [PATCH 09/26] Add check for empty string when adding groups in tests annotation --- .../io/ballerina/testmanagerservice/extension/Utils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 0ab62a51f0..7b92a6d6b1 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -422,6 +422,14 @@ public static String buildTestConfigAnnotation(Annotation annotation) { if (value instanceof List valueList && !valueList.isEmpty() && valueList.getFirst() instanceof String) { List groupList = valueList.stream() + .filter(group -> { + String groupStr = group.toString().trim(); + if (groupStr.isEmpty() || groupStr.equals("\"\"")) { + return false; + } + String unquoted = groupStr.replaceAll("^\"|\"$", ""); + return !unquoted.isEmpty(); + }) .map(group -> { String groupStr = group.toString(); if (!groupStr.startsWith(Constants.DOUBLE_QUOTE)) { From a56e63d4a88997358949fc90c2f084fe67f7b03c Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 12 Feb 2026 16:29:22 +0530 Subject: [PATCH 10/26] Update tests for chat service --- .../add_service/config/add_ai_service_ballerina.json | 10 +++------- .../add_service/config/add_ai_service_ballerinax.json | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerina.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerina.json index 7ac95768d3..cf8bc5d28b 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerina.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerina.json @@ -13,6 +13,7 @@ "icon": "http://localhost:8080/icons/ai.png", "properties": { "listener": { + "imports": {}, "metadata": { "label": "Listeners", "description": "The Listeners to be bound with the service" @@ -21,8 +22,6 @@ "type": "LISTENER" }, "placeholder": "", - "valueType": "SINGLE_SELECT", - "valueTypeConstraint": "ai:Listener", "value": "aiListener", "values": [ "aiListener" @@ -30,13 +29,13 @@ "items": [ "aiListener" ], - "imports": {}, "enabled": true, "editable": true, "optional": false, "advanced": false }, "agentName": { + "imports": {}, "metadata": { "label": "Agent Name", "description": "The name of the agent variable" @@ -45,10 +44,7 @@ "type": "PROPERTY" }, "placeholder": "", - "valueType": "IDENTIFIER", - "valueTypeConstraint": "string", "value": "support", - "imports": {}, "enabled": true, "editable": true, "optional": false, @@ -83,7 +79,7 @@ "character": 0 } }, - "newText": "\nservice on aiListener {\n private final ai:Agent supportAgent;\n\n function init() returns error? { \n self.supportAgent = check new (\n systemPrompt = {role: string ``, instructions: string ``}, model = supportModel, tools = []\n );\n }\n\n resource function post chat(@http:Payload ai:ChatReqMessage request) returns ai:ChatRespMessage|error {\n string stringResult = check self.supportAgent.run(request.message, request.sessionId);\n return {message: stringResult};\n }\n}" + "newText": "\nservice on aiListener {\n resource function post chat(@http:Payload ai:ChatReqMessage request) returns ai:ChatRespMessage|error {\n string stringResult = check supportAgent.run(request.message, request.sessionId);\n return {message: stringResult};\n }\n}" } ] } diff --git a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerinax.json b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerinax.json index 4490dd56eb..8bc0c0874a 100644 --- a/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerinax.json +++ b/service-model-generator/modules/service-model-generator-ls-extension/src/test/resources/add_service/config/add_ai_service_ballerinax.json @@ -13,6 +13,7 @@ "icon": "http://localhost:8080/icons/ai.png", "properties": { "listener": { + "imports": {}, "metadata": { "label": "Listeners", "description": "The Listeners to be bound with the service" @@ -21,8 +22,6 @@ "type": "LISTENER" }, "placeholder": "", - "valueType": "SINGLE_SELECT", - "valueTypeConstraint": "ai:Listener", "value": "aiListener", "values": [ "aiListener" @@ -30,13 +29,13 @@ "items": [ "aiListener" ], - "imports": {}, "enabled": true, "editable": true, "optional": false, "advanced": false }, "agentName": { + "imports": {}, "metadata": { "label": "Agent Name", "description": "The name of the agent variable" @@ -45,10 +44,7 @@ "type": "PROPERTY" }, "placeholder": "", - "valueType": "IDENTIFIER", - "valueTypeConstraint": "string", "value": "", - "imports": {}, "enabled": true, "editable": true, "optional": false, @@ -83,7 +79,7 @@ "character": 0 } }, - "newText": "\nservice on aiListener {\n private final ai:Agent chatAgent;\n\n function init() returns error? { \n self.chatAgent = check new (\n systemPrompt = {role: string ``, instructions: string ``}, model = chatModel, tools = []\n );\n }\n\n resource function post chat(@http:Payload ai:ChatReqMessage request) returns ai:ChatRespMessage|error {\n string stringResult = check self.chatAgent->run(request.message, request.sessionId);\n return {message: stringResult};\n }\n}" + "newText": "\nservice on aiListener {\n resource function post chat(@http:Payload ai:ChatReqMessage request) returns ai:ChatRespMessage|error {\n string stringResult = check chatAgent->run(request.message, request.sessionId);\n return {message: stringResult};\n }\n}" } ] } From ef283c8c70233d100e11c67975c460ce7e3864f1 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 04:42:21 +0530 Subject: [PATCH 11/26] Add tests for adding and updating tests with min pass rate and evalset files --- .../add_test_with_minpassrate_config1.json | 114 ++++++++++++ .../source/sample_minpassrate/Ballerina.toml | 6 + .../source/sample_minpassrate/main.bal | 3 + .../source/sample_minpassrate/tests/test1.bal | 2 + .../config/update_evalset_file_config1.json | 172 ++++++++++++++++++ .../config/update_minpassrate_config1.json | 127 +++++++++++++ .../sample_update_evalset/Ballerina.toml | 6 + .../source/sample_update_evalset/main.bal | 3 + .../sample_update_evalset/tests/test1.bal | 11 ++ .../sample_update_minpassrate/Ballerina.toml | 6 + .../source/sample_update_minpassrate/main.bal | 3 + .../sample_update_minpassrate/tests/test1.bal | 6 + 12 files changed, 459 insertions(+) create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/Ballerina.toml create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/main.bal create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/tests/test1.bal create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/Ballerina.toml create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/main.bal create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/tests/test1.bal create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/Ballerina.toml create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/main.bal create mode 100644 test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/tests/test1.bal diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json new file mode 100644 index 0000000000..40def159ea --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json @@ -0,0 +1,114 @@ +{ + "filePath": "sample_minpassrate/tests/test1.bal", + "description": "Test to add test function with minPassRate configuration", + "function": { + "metadata": { + "label": "Test Function", + "description": "Test Function" + }, + "codedata": { + "lineRange": { + "fileName": "tests/test1.bal", + "startLine": { + "line": 3, + "offset": 0 + }, + "endLine": { + "line": 6, + "offset": 1 + } + } + }, + "functionName": { + "metadata": { + "label": "Test Function", + "description": "Test function" + }, + "valueType": "IDENTIFIER", + "value": "testWithMinPassRate", + "optional": false, + "editable": true, + "advanced": false + }, + "returnType": { + "metadata": { + "label": "Return Type", + "description": "Return type of the function" + }, + "valueType": "TYPE", + "optional": true, + "editable": true, + "advanced": true + }, + "parameters": [], + "annotations": [ + { + "metadata": { + "label": "Config", + "description": "Test Function Configurations" + }, + "org": "ballerina", + "module": "test", + "name": "Config", + "fields": [ + { + "metadata": { + "label": "Groups", + "description": "Groups to run" + }, + "valueType": "EXPRESSION_SET", + "originalName": "groups", + "value": [ + "\"stability\"" + ], + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Runs", + "description": "Number of times to execute this test" + }, + "valueType": "EXPRESSION", + "originalName": "runs", + "value": "10", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Minimum Pass Rate (%)", + "description": "Minimum percentage of runs that must pass (0-100)" + }, + "valueType": "SLIDER", + "originalName": "minPassRate", + "value": "80", + "optional": true, + "editable": true, + "advanced": false + } + ] + } + ], + "editable": true + }, + "output": { + "sample_minpassrate/tests/test1.bal": [ + { + "range": { + "start": { + "line": 1, + "character": 22 + }, + "end": { + "line": 1, + "character": 22 + } + }, + "newText": "@test:Config{\ngroups: [\"stability\"],\nruns: 10,\nminPassRate: 80\n}\nfunction testWithMinPassRate() {\n\tdo {\n\t} on fail error err {\n\t}\n}" + } + ] + } +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/Ballerina.toml b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/Ballerina.toml new file mode 100644 index 0000000000..0e8975938a --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/Ballerina.toml @@ -0,0 +1,6 @@ +[package] +org = "ballerina" +name = "test_manager_service" +version = "0.1.0" + +bi = true diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/main.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/main.bal new file mode 100644 index 0000000000..b684ce66a3 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/main.bal @@ -0,0 +1,3 @@ +public function main() { + +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/tests/test1.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/tests/test1.bal new file mode 100644 index 0000000000..8142655b70 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/source/sample_minpassrate/tests/test1.bal @@ -0,0 +1,2 @@ +import ballerina/io; +import ballerina/test; diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json new file mode 100644 index 0000000000..93cb98f3ad --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json @@ -0,0 +1,172 @@ +{ + "filePath": "sample_update_evalset/tests/test1.bal", + "description": "Test to update evalSetFile in an existing test function with evalSet mode", + "function": { + "metadata": { + "label": "Test Function", + "description": "Test Function" + }, + "codedata": { + "lineRange": { + "fileName": "tests/test1.bal", + "startLine": { + "line": 3, + "offset": 0 + }, + "endLine": { + "line": 5, + "offset": 1 + } + } + }, + "functionName": { + "metadata": { + "label": "Test Function", + "description": "Test function" + }, + "valueType": "IDENTIFIER", + "value": "testEvalSetFunction", + "optional": false, + "editable": true, + "advanced": false + }, + "returnType": { + "metadata": { + "label": "Return Type", + "description": "Return type of the function" + }, + "valueType": "TYPE", + "optional": true, + "editable": true, + "advanced": true + }, + "parameters": [ + { + "type": { + "valueType": "TYPE", + "value": "ai:Trace", + "optional": false, + "editable": true, + "advanced": false + }, + "variable": { + "valueType": "IDENTIFIER", + "value": "thread", + "optional": false, + "editable": true, + "advanced": false + }, + "optional": false, + "editable": true, + "advanced": false + } + ], + "annotations": [ + { + "metadata": { + "label": "Config", + "description": "Test Function Configurations" + }, + "org": "ballerina", + "module": "test", + "name": "Config", + "fields": [ + { + "metadata": { + "label": "Groups", + "description": "Groups to run" + }, + "valueType": "EXPRESSION_SET", + "originalName": "groups", + "value": [ + "\"evalset\"" + ], + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Data Provider", + "description": "Data provider function" + }, + "valueType": "EXPRESSION", + "originalName": "dataProvider", + "value": "loadEvalSetData", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Data Provider Mode", + "description": "Mode of data provider (function or evalSet)" + }, + "valueType": "EXPRESSION", + "originalName": "dataProviderMode", + "value": "evalSet", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "EvalSet File", + "description": "Path to the evalSet data file" + }, + "valueType": "EXPRESSION", + "originalName": "evalSetFile", + "value": "resources/updated_sessions.json", + "optional": true, + "editable": true, + "advanced": false + } + ] + } + ], + "editable": true + }, + "output": { + "sample_update_evalset/tests/test1.bal": [ + { + "range": { + "start": { + "line": 4, + "character": 28 + }, + "end": { + "line": 4, + "character": 45 + } + }, + "newText": "(ai:Trace thread)" + }, + { + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 65 + } + }, + "newText": "@test:Config{\ngroups: [\"evalset\"],\ndataProvider: loadEvalSetData\n}" + }, + { + "range": { + "start": { + "line": 9, + "character": 44 + }, + "end": { + "line": 9, + "character": 73 + } + }, + "newText": "\"resources/updated_sessions.json\"" + } + ] + } +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json new file mode 100644 index 0000000000..b0cb6edf88 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json @@ -0,0 +1,127 @@ +{ + "filePath": "sample_update_minpassrate/tests/test1.bal", + "description": "Test to update minPassRate in an existing test function", + "function": { + "metadata": { + "label": "Test Function", + "description": "Test Function" + }, + "codedata": { + "lineRange": { + "fileName": "tests/test1.bal", + "startLine": { + "line": 2, + "offset": 0 + }, + "endLine": { + "line": 4, + "offset": 1 + } + } + }, + "functionName": { + "metadata": { + "label": "Test Function", + "description": "Test function" + }, + "valueType": "IDENTIFIER", + "value": "testWithMinPassRate", + "optional": false, + "editable": true, + "advanced": false + }, + "returnType": { + "metadata": { + "label": "Return Type", + "description": "Return type of the function" + }, + "valueType": "TYPE", + "optional": true, + "editable": true, + "advanced": true + }, + "parameters": [], + "annotations": [ + { + "metadata": { + "label": "Config", + "description": "Test Function Configurations" + }, + "org": "ballerina", + "module": "test", + "name": "Config", + "fields": [ + { + "metadata": { + "label": "Groups", + "description": "Groups to run" + }, + "valueType": "EXPRESSION_SET", + "originalName": "groups", + "value": [ + "\"stability\"" + ], + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Runs", + "description": "Number of times to execute this test" + }, + "valueType": "EXPRESSION", + "originalName": "runs", + "value": "20", + "optional": true, + "editable": true, + "advanced": false + }, + { + "metadata": { + "label": "Minimum Pass Rate (%)", + "description": "Minimum percentage of runs that must pass (0-100)" + }, + "valueType": "SLIDER", + "originalName": "minPassRate", + "value": "90", + "optional": true, + "editable": true, + "advanced": false + } + ] + } + ], + "editable": true + }, + "output": { + "sample_update_minpassrate/tests/test1.bal": [ + { + "range": { + "start": { + "line": 3, + "character": 28 + }, + "end": { + "line": 3, + "character": 30 + } + }, + "newText": "()" + }, + { + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 63 + } + }, + "newText": "@test:Config{\ngroups: [\"stability\"],\nruns: 20,\nminPassRate: 90\n}" + } + ] + } +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/Ballerina.toml b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/Ballerina.toml new file mode 100644 index 0000000000..0e8975938a --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/Ballerina.toml @@ -0,0 +1,6 @@ +[package] +org = "ballerina" +name = "test_manager_service" +version = "0.1.0" + +bi = true diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/main.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/main.bal new file mode 100644 index 0000000000..b684ce66a3 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/main.bal @@ -0,0 +1,3 @@ +public function main() { + +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/tests/test1.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/tests/test1.bal new file mode 100644 index 0000000000..2fa5f36577 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_evalset/tests/test1.bal @@ -0,0 +1,11 @@ +import ballerina/test; +import ballerina/ai; + +@test:Config {groups: ["evalset"], dataProvider: loadEvalSetData} +function testEvalSetFunction(ai:Trace thread) { + test:assertTrue(true, msg = "Failed!"); +} + +function loadEvalSetData() returns ai:Trace[]|error { + return check ai:loadConversationThreads("resources/old_sessions.json"); +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/Ballerina.toml b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/Ballerina.toml new file mode 100644 index 0000000000..0e8975938a --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/Ballerina.toml @@ -0,0 +1,6 @@ +[package] +org = "ballerina" +name = "test_manager_service" +version = "0.1.0" + +bi = true diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/main.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/main.bal new file mode 100644 index 0000000000..b684ce66a3 --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/main.bal @@ -0,0 +1,3 @@ +public function main() { + +} diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/tests/test1.bal b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/tests/test1.bal new file mode 100644 index 0000000000..bd215ab01d --- /dev/null +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/source/sample_update_minpassrate/tests/test1.bal @@ -0,0 +1,6 @@ +import ballerina/test; + +@test:Config {groups: ["stability"], runs: 10, minPassRate: 70} +function testWithMinPassRate() { + test:assertTrue(true, msg = "Failed!"); +} From d08fc9aeb52608ede96e082a84aa5182a34ba184 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 05:14:58 +0530 Subject: [PATCH 12/26] Fix coderabbit suggestions --- .../core/AvailableNodesGenerator.java | 5 +- .../extension/Constants.java | 1 + .../extension/TestManagerService.java | 89 ++++++++----------- .../testmanagerservice/extension/Utils.java | 28 ++++-- .../extension/model/Annotation.java | 2 +- .../config/update_evalset_file_config1.json | 4 +- 6 files changed, 63 insertions(+), 66 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java index f80270fb8f..6a9f334a67 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java @@ -73,6 +73,7 @@ import static io.ballerina.modelgenerator.commons.CommonUtils.PERSIST; import static io.ballerina.modelgenerator.commons.CommonUtils.PERSIST_MODEL_FILE; import static io.ballerina.modelgenerator.commons.CommonUtils.getPersistModelFilePath; +import static io.ballerina.modelgenerator.commons.CommonUtils.isAgentClass; import static io.ballerina.modelgenerator.commons.CommonUtils.isAiEmbeddingProvider; import static io.ballerina.modelgenerator.commons.CommonUtils.isAiKnowledgeBase; import static io.ballerina.modelgenerator.commons.CommonUtils.isAiModelProvider; @@ -417,7 +418,7 @@ private Optional getCategory(Symbol symbol, Predicate con FunctionData.Kind kind = methodFunction.kind(); if (kind == FunctionData.Kind.REMOTE) { nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.REMOTE_ACTION_CALL); - } else if (className.equals("Agent") && label.equals("run")) { + } else if (isAgentClass(classSymbol) && label.equals(Ai.AGENT_RUN_METHOD_NAME)) { nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.AGENT_RUN); } else if (kind == FunctionData.Kind.FUNCTION && isAiKnowledgeBase(classSymbol)) { nodeBuilder = NodeBuilder.getNodeFromKind(NodeKind.KNOWLEDGE_BASE_CALL); @@ -466,7 +467,7 @@ private Optional getCategory(Symbol symbol, Predicate con private Optional getAgent(Symbol symbol) { return getCategory(symbol, classSymbol -> { try { - return classSymbol.getName().orElse("").equals(Ai.AGENT_TYPE_NAME); + return isAgentClass(classSymbol); } catch (Exception e) { return false; } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java index ff202d4bb5..e4c1c27c81 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Constants.java @@ -37,6 +37,7 @@ public class Constants { public static final String DATA_PROVIDER_MODE_FUNCTION = "function"; public static final String DATA_PROVIDER_MODE_EVALSET = "evalSet"; public static final String DEFAULT_EVALSET_FUNCTION_NAME = "loadEvalsetData"; + public static final String LOAD_CONVERSATION_THREADS = "loadConversationThreads"; public static final String TEST_ANNOTATION = "@test:"; public static final String CONFIG_GROUPS = "groups"; diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 203f775265..0620c839a5 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -22,12 +22,17 @@ import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.ExpressionFunctionBodyNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.ExpressionStatementNode; import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; +import io.ballerina.compiler.syntax.tree.FunctionBodyNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NonTerminalNode; +import io.ballerina.compiler.syntax.tree.ReturnStatementNode; +import io.ballerina.compiler.syntax.tree.StatementNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; @@ -35,6 +40,7 @@ import io.ballerina.testmanagerservice.extension.model.Annotation; import io.ballerina.testmanagerservice.extension.model.FunctionParameter; import io.ballerina.testmanagerservice.extension.model.Property; +import io.ballerina.testmanagerservice.extension.model.TestFunction; import io.ballerina.testmanagerservice.extension.request.AddTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.GetTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.TestsDiscoveryRequest; @@ -235,7 +241,7 @@ public CompletableFuture addTestFunction(AddTestFunctionRe ); edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), dataProviderFunction)); - // Add ai:Trace parameter to the test function + // Add ai:ConversationThread parameter to the test function addAiConversationThreadParameter(request.function()); // Update the dataProvider field with the generated function name @@ -253,7 +259,7 @@ public CompletableFuture addTestFunction(AddTestFunctionRe }); } - private String getDataProviderMode(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + private String getDataProviderMode(TestFunction function) { if (function.annotations() == null) { return null; } @@ -269,7 +275,7 @@ private String getDataProviderMode(io.ballerina.testmanagerservice.extension.mod return null; } - private String getEvalSetFile(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + private String getEvalSetFile(TestFunction function) { if (function.annotations() == null) { return null; } @@ -285,8 +291,7 @@ private String getEvalSetFile(io.ballerina.testmanagerservice.extension.model.Te return null; } - private void addAiConversationThreadParameter( - io.ballerina.testmanagerservice.extension.model.TestFunction function) { + private void addAiConversationThreadParameter(TestFunction function) { if (function.parameters() == null) { return; } @@ -296,8 +301,7 @@ private void addAiConversationThreadParameter( function.parameters().add(paramBuilder.build()); } - private void updateDataProviderField(io.ballerina.testmanagerservice.extension.model.TestFunction function, - String functionName) { + private void updateDataProviderField(TestFunction function, String functionName) { if (function.annotations() == null) { return; } @@ -370,8 +374,8 @@ public CompletableFuture updateTestFunction(UpdateTestFunc // Update annotations if present if (functionDefinitionNode.metadata().isPresent() && - request.function().annotations() != null && - !request.function().annotations().isEmpty()) { + request.function().annotations() != null && + !request.function().annotations().isEmpty()) { updateAnnotations(functionDefinitionNode, request.function(), edits); } @@ -392,9 +396,8 @@ public CompletableFuture updateTestFunction(UpdateTestFunc * @param function the test function model * @param edits list of text edits to add to */ - private void updateAnnotations(FunctionDefinitionNode functionDefinitionNode, - io.ballerina.testmanagerservice.extension.model.TestFunction function, - List edits) { + private void updateAnnotations(FunctionDefinitionNode functionDefinitionNode, TestFunction function, + List edits) { if (functionDefinitionNode.metadata().isEmpty()) { return; } @@ -422,19 +425,18 @@ private void updateAnnotations(FunctionDefinitionNode functionDefinitionNode, /** * Update the evalSetFile in the data provider function. * - * @param textDocument the text document - * @param modulePartNode the module part node - * @param function the test function model - * @param edits list of text edits to add to + * @param textDocument the text document + * @param modulePartNode the module part node + * @param function the test function model + * @param edits list of text edits to add to */ - private void updateEvalSetFile(TextDocument textDocument, ModulePartNode modulePartNode, - io.ballerina.testmanagerservice.extension.model.TestFunction function, - List edits) { + private void updateEvalSetFile(TextDocument textDocument, ModulePartNode modulePartNode, TestFunction function, + List edits) { String newEvalSetFile = getEvalSetFile(function); String dataProviderName = getDataProviderName(function); if (newEvalSetFile == null || newEvalSetFile.isEmpty() || - dataProviderName == null || dataProviderName.isEmpty()) { + dataProviderName == null || dataProviderName.isEmpty()) { return; } @@ -482,7 +484,7 @@ private LineRange getAnnotationsRange(NodeList annotations) { * @param function the test function model * @return the data provider name, or null if not found */ - private String getDataProviderName(io.ballerina.testmanagerservice.extension.model.TestFunction function) { + private String getDataProviderName(TestFunction function) { if (function.annotations() == null) { return null; } @@ -501,12 +503,12 @@ private String getDataProviderName(io.ballerina.testmanagerservice.extension.mod /** * Find the data provider function by name in the module. * - * @param modulePartNode the module part node - * @param dataProviderName the data provider function name + * @param modulePartNode the module part node + * @param dataProviderName the data provider function name * @return the function definition node, or empty if not found */ private Optional findDataProviderFunction(ModulePartNode modulePartNode, - String dataProviderName) { + String dataProviderName) { if (dataProviderName == null || dataProviderName.isEmpty()) { return Optional.empty(); } @@ -530,7 +532,7 @@ private Optional findDataProviderFunction(ModulePartNode */ private Optional findEvalSetFilePathLocation(FunctionDefinitionNode dataProviderFunc, TextDocument textDocument) { - io.ballerina.compiler.syntax.tree.FunctionBodyNode functionBody = dataProviderFunc.functionBody(); + FunctionBodyNode functionBody = dataProviderFunc.functionBody(); if (functionBody instanceof FunctionBodyBlockNode blockBody) { return findFilePathInStatements(blockBody.statements()); @@ -548,17 +550,17 @@ private Optional findEvalSetFilePathLocation(FunctionDefinitionNode d * @return the line range of the file path string, or empty if not found */ private Optional findFilePathInStatements( - NodeList statements) { - for (io.ballerina.compiler.syntax.tree.StatementNode statement : statements) { - if (statement instanceof io.ballerina.compiler.syntax.tree.ReturnStatementNode returnStmt) { - Optional expr = returnStmt.expression(); + NodeList statements) { + for (StatementNode statement : statements) { + if (statement instanceof ReturnStatementNode returnStmt) { + Optional expr = returnStmt.expression(); if (expr.isPresent()) { Optional result = findFilePathInExpression(expr.get()); if (result.isPresent()) { return result; } } - } else if (statement instanceof io.ballerina.compiler.syntax.tree.ExpressionStatementNode exprStmt) { + } else if (statement instanceof ExpressionStatementNode exprStmt) { Optional result = findFilePathInExpression(exprStmt.expression()); if (result.isPresent()) { return result; @@ -574,30 +576,9 @@ private Optional findFilePathInStatements( * @param expression the expression node * @return the line range of the file path string, or empty if not found */ - private Optional findFilePathInExpression( - io.ballerina.compiler.syntax.tree.ExpressionNode expression) { - // Handle check expression: check ai:loadConversationThreads(...) - if (expression instanceof io.ballerina.compiler.syntax.tree.CheckExpressionNode checkExpr) { - return findFilePathInExpression(checkExpr.expression()); - } - - // Handle function call: ai:loadConversationThreads("path") - if (expression instanceof io.ballerina.compiler.syntax.tree.FunctionCallExpressionNode funcCall) { - String functionName = funcCall.functionName().toSourceCode().trim(); - - // Check if it's the ai:loadConversationThreads function - if (functionName.contains("loadConversationThreads")) { - // Extract first argument (the file path) - for (io.ballerina.compiler.syntax.tree.FunctionArgumentNode arg : funcCall.arguments()) { - if (arg instanceof io.ballerina.compiler.syntax.tree.PositionalArgumentNode positionalArg) { - io.ballerina.compiler.syntax.tree.ExpressionNode argExpr = positionalArg.expression(); - return Optional.of(argExpr.lineRange()); - } - } - } - } - - return Optional.empty(); + private Optional findFilePathInExpression(ExpressionNode expression) { + return Utils.findLoadConversationThreadsArgument(expression) + .map(ExpressionNode::lineRange); } /** diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 7b92a6d6b1..22c35b97f0 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -280,10 +280,16 @@ private static String extractFromStatements(NodeList statements) return ""; } - private static String extractFromExpression(ExpressionNode expression) { + /** + * Find the argument expression for the file path in ai:loadConversationThreads() call. + * + * @param expression the expression node to search + * @return the ExpressionNode of the file path argument, or empty if not found + */ + public static Optional findLoadConversationThreadsArgument(ExpressionNode expression) { // Handle check expression: check ai:loadConversationThreads(...) if (expression instanceof CheckExpressionNode checkExpr) { - return extractFromExpression(checkExpr.expression()); + return findLoadConversationThreadsArgument(checkExpr.expression()); } // Handle function call: ai:loadConversationThreads("path") @@ -291,19 +297,27 @@ private static String extractFromExpression(ExpressionNode expression) { String functionName = funcCall.functionName().toSourceCode().trim(); // Check if it's the ai:loadConversationThreads function - if (functionName.contains("loadConversationThreads")) { + if (functionName.equals(Constants.LOAD_CONVERSATION_THREADS) || + functionName.equals(Constants.MODULE_AI + Constants.COLON + Constants.LOAD_CONVERSATION_THREADS)) { // Extract first argument (the file path) for (FunctionArgumentNode arg : funcCall.arguments()) { if (arg instanceof PositionalArgumentNode positionalArg) { - ExpressionNode argExpr = positionalArg.expression(); - String argValue = argExpr.toSourceCode().trim(); - // Remove surrounding quotes - return argValue.replaceAll("^\"|\"$", ""); + return Optional.of(positionalArg.expression()); } } } } + return Optional.empty(); + } + + private static String extractFromExpression(ExpressionNode expression) { + Optional argExpr = findLoadConversationThreadsArgument(expression); + if (argExpr.isPresent()) { + String argValue = argExpr.get().toSourceCode().trim(); + // Remove surrounding quotes + return argValue.replaceAll("^\"|\"$", ""); + } return ""; } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java index 1d8f350171..2fd004aed9 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/model/Annotation.java @@ -140,7 +140,7 @@ public Annotation build() { // Always add evalSetFile (default to empty string) if (evalSetFile == null) { - evalSetFile = value("EvalSet File", "Path to the evalSet data file", "", + evalSetFile = value("Evalset File", "Path to the evalset data file", "", "EXPRESSION", "evalSetFile"); } properties.add(evalSetFile); diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json index 93cb98f3ad..9f7b8cb9e5 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json @@ -111,8 +111,8 @@ }, { "metadata": { - "label": "EvalSet File", - "description": "Path to the evalSet data file" + "label": "Evalset File", + "description": "Path to the evalset data file" }, "valueType": "EXPRESSION", "originalName": "evalSetFile", From bc7cfaa74310f4c61e78319c80ed3e075bf0e487 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 05:26:06 +0530 Subject: [PATCH 13/26] Refactor code --- .../extension/TestManagerService.java | 91 ++------------ .../testmanagerservice/extension/Utils.java | 115 +++++++++++++++--- 2 files changed, 107 insertions(+), 99 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 0620c839a5..5858b26f6a 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -260,35 +260,11 @@ public CompletableFuture addTestFunction(AddTestFunctionRe } private String getDataProviderMode(TestFunction function) { - if (function.annotations() == null) { - return null; - } - for (Annotation annotation : function.annotations()) { - if ("Config".equals(annotation.name())) { - for (Property field : annotation.fields()) { - if ("dataProviderMode".equals(field.originalName())) { - return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; - } - } - } - } - return null; + return Utils.getConfigFieldValue(function, "dataProviderMode"); } private String getEvalSetFile(TestFunction function) { - if (function.annotations() == null) { - return null; - } - for (Annotation annotation : function.annotations()) { - if ("Config".equals(annotation.name())) { - for (Property field : annotation.fields()) { - if ("evalSetFile".equals(field.originalName())) { - return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; - } - } - } - } - return null; + return Utils.getConfigFieldValue(function, "evalSetFile"); } private void addAiConversationThreadParameter(TestFunction function) { @@ -468,14 +444,7 @@ private void updateEvalSetFile(TextDocument textDocument, ModulePartNode moduleP * @return the line range covering all annotations, or null if empty */ private LineRange getAnnotationsRange(NodeList annotations) { - if (annotations.isEmpty()) { - return null; - } - - LinePosition startPos = annotations.get(0).lineRange().startLine(); - LinePosition endPos = annotations.get(annotations.size() - 1).lineRange().endLine(); - - return LineRange.from(null, startPos, endPos); + return Utils.getAnnotationsRange(annotations); } /** @@ -485,19 +454,7 @@ private LineRange getAnnotationsRange(NodeList annotations) { * @return the data provider name, or null if not found */ private String getDataProviderName(TestFunction function) { - if (function.annotations() == null) { - return null; - } - for (Annotation annotation : function.annotations()) { - if ("Config".equals(annotation.name())) { - for (Property field : annotation.fields()) { - if ("dataProvider".equals(field.originalName())) { - return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; - } - } - } - } - return null; + return Utils.getConfigFieldValue(function, "dataProvider"); } /** @@ -509,18 +466,7 @@ private String getDataProviderName(TestFunction function) { */ private Optional findDataProviderFunction(ModulePartNode modulePartNode, String dataProviderName) { - if (dataProviderName == null || dataProviderName.isEmpty()) { - return Optional.empty(); - } - - // Remove quotes if present in function name - final String functionName = dataProviderName.replaceAll("\"", ""); - - return modulePartNode.members().stream() - .filter(mem -> mem instanceof FunctionDefinitionNode) - .map(mem -> (FunctionDefinitionNode) mem) - .filter(mem -> mem.functionName().text().trim().equals(functionName)) - .findFirst(); + return Utils.findFunctionByName(modulePartNode, dataProviderName); } /** @@ -549,25 +495,9 @@ private Optional findEvalSetFilePathLocation(FunctionDefinitionNode d * @param statements the list of statements * @return the line range of the file path string, or empty if not found */ - private Optional findFilePathInStatements( - NodeList statements) { - for (StatementNode statement : statements) { - if (statement instanceof ReturnStatementNode returnStmt) { - Optional expr = returnStmt.expression(); - if (expr.isPresent()) { - Optional result = findFilePathInExpression(expr.get()); - if (result.isPresent()) { - return result; - } - } - } else if (statement instanceof ExpressionStatementNode exprStmt) { - Optional result = findFilePathInExpression(exprStmt.expression()); - if (result.isPresent()) { - return result; - } - } - } - return Optional.empty(); + private Optional findFilePathInStatements(NodeList statements) { + return Utils.findLoadConversationThreadsArgumentInStatements(statements) + .map(ExpressionNode::lineRange); } /** @@ -589,9 +519,6 @@ private Optional findFilePathInExpression(ExpressionNode expression) * @return the file path without quotes */ private String extractCurrentFilePath(TextDocument textDocument, LineRange range) { - int start = textDocument.textPositionFrom(range.startLine()); - int end = textDocument.textPositionFrom(range.endLine()); - String text = textDocument.toString().substring(start, end); - return text.replaceAll("^\"|\"$", "").trim(); + return Utils.extractTextFromRange(textDocument, range); } } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index 22c35b97f0..e793e97b77 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -229,19 +229,8 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode private static String extractEvalSetFileFromDataProvider(ModulePartNode modulePartNode, String dataProviderFunctionName) { - if (dataProviderFunctionName == null || dataProviderFunctionName.isEmpty()) { - return ""; - } - - // Remove quotes if present in function name - final String functionName = dataProviderFunctionName.replaceAll("\"", ""); - // Find the data provider function - Optional dataProviderFunc = modulePartNode.members().stream() - .filter(mem -> mem instanceof FunctionDefinitionNode) - .map(mem -> (FunctionDefinitionNode) mem) - .filter(mem -> mem.functionName().text().trim().equals(functionName)) - .findFirst(); + Optional dataProviderFunc = findFunctionByName(modulePartNode, dataProviderFunctionName); if (dataProviderFunc.isEmpty()) { return ""; @@ -260,23 +249,39 @@ private static String extractEvalSetFileFromDataProvider(ModulePartNode modulePa return ""; } - private static String extractFromStatements(NodeList statements) { + /** + * Find the loadConversationThreads argument in statements. + * + * @param statements the list of statements + * @return the ExpressionNode of the file path argument, or empty if not found + */ + public static Optional findLoadConversationThreadsArgumentInStatements( + NodeList statements) { for (StatementNode statement : statements) { if (statement instanceof ReturnStatementNode returnStmt) { Optional expr = returnStmt.expression(); if (expr.isPresent()) { - String result = extractFromExpression(expr.get()); - if (!result.isEmpty()) { + Optional result = findLoadConversationThreadsArgument(expr.get()); + if (result.isPresent()) { return result; } } } else if (statement instanceof ExpressionStatementNode exprStmt) { - String result = extractFromExpression(exprStmt.expression()); - if (!result.isEmpty()) { + Optional result = findLoadConversationThreadsArgument(exprStmt.expression()); + if (result.isPresent()) { return result; } } } + return Optional.empty(); + } + + private static String extractFromStatements(NodeList statements) { + Optional argExpr = findLoadConversationThreadsArgumentInStatements(statements); + if (argExpr.isPresent()) { + String argValue = argExpr.get().toSourceCode().trim(); + return argValue.replaceAll("^\"|\"$", ""); + } return ""; } @@ -536,6 +541,82 @@ public static boolean isAiModuleImportExists(ModulePartNode node) { }); } + /** + * Get a field value from the Config annotation of a test function. + * + * @param function the test function + * @param fieldName the field name to extract + * @return the field value without quotes, or null if not found + */ + public static String getConfigFieldValue(TestFunction function, String fieldName) { + if (function.annotations() == null) { + return null; + } + for (Annotation annotation : function.annotations()) { + if ("Config".equals(annotation.name())) { + for (Property field : annotation.fields()) { + if (fieldName.equals(field.originalName())) { + return field.value() != null ? field.value().toString().replaceAll("\"", "") : null; + } + } + } + } + return null; + } + + /** + * Find a function by name in the module. + * + * @param modulePartNode the module part node + * @param functionName the function name (with or without quotes) + * @return the function definition node, or empty if not found + */ + public static Optional findFunctionByName(ModulePartNode modulePartNode, + String functionName) { + if (functionName == null || functionName.isEmpty()) { + return Optional.empty(); + } + // Remove quotes if present in function name + final String cleanName = functionName.replaceAll("\"", ""); + + return modulePartNode.members().stream() + .filter(mem -> mem instanceof FunctionDefinitionNode) + .map(mem -> (FunctionDefinitionNode) mem) + .filter(mem -> mem.functionName().text().trim().equals(cleanName)) + .findFirst(); + } + + /** + * Get the line range covering all annotations. + * + * @param annotations the list of annotation nodes + * @return the line range covering all annotations, or null if empty + */ + public static LineRange getAnnotationsRange(NodeList annotations) { + if (annotations.isEmpty()) { + return null; + } + + LinePosition startPos = annotations.get(0).lineRange().startLine(); + LinePosition endPos = annotations.get(annotations.size() - 1).lineRange().endLine(); + + return LineRange.from(null, startPos, endPos); + } + + /** + * Extract text from a document at the given line range. + * + * @param textDocument the text document + * @param range the line range + * @return the extracted text with quotes removed and trimmed + */ + public static String extractTextFromRange(io.ballerina.tools.text.TextDocument textDocument, LineRange range) { + int start = textDocument.textPositionFrom(range.startLine()); + int end = textDocument.textPositionFrom(range.endLine()); + String text = textDocument.toString().substring(start, end); + return text.replaceAll("^\"|\"$", "").trim(); + } + /** * Generate an evalSet data provider function template. * From d17c8afceea1ffc0e2e0b44d79b79174c7ce6c33 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 08:19:28 +0530 Subject: [PATCH 14/26] Remove unused imports --- .../extension/TestManagerService.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 5858b26f6a..187647c584 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -23,7 +23,6 @@ import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.ExpressionFunctionBodyNode; import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.ExpressionStatementNode; import io.ballerina.compiler.syntax.tree.FunctionBodyBlockNode; import io.ballerina.compiler.syntax.tree.FunctionBodyNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; @@ -31,7 +30,6 @@ import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NonTerminalNode; -import io.ballerina.compiler.syntax.tree.ReturnStatementNode; import io.ballerina.compiler.syntax.tree.StatementNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; @@ -48,7 +46,6 @@ import io.ballerina.testmanagerservice.extension.response.CommonSourceResponse; import io.ballerina.testmanagerservice.extension.response.GetTestFunctionResponse; import io.ballerina.testmanagerservice.extension.response.TestsDiscoveryResponse; -import io.ballerina.tools.text.LinePosition; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextRange; @@ -156,7 +153,6 @@ public CompletableFuture getTestFunction(GetTestFunctio return CompletableFuture.supplyAsync(() -> { try { Path filePath = Path.of(request.filePath()); - Project project = this.workspaceManager.loadProject(filePath); Optional document = this.workspaceManager.document(filePath); Optional semanticModel = this.workspaceManager.semanticModel(filePath); if (document.isEmpty() || semanticModel.isEmpty()) { @@ -207,7 +203,7 @@ public CompletableFuture addTestFunction(AddTestFunctionRe // Check if dataProviderMode is evalSet String dataProviderMode = getDataProviderMode(request.function()); - String dataProviderFunctionName = null; + String dataProviderFunctionName; if (Constants.DATA_PROVIDER_MODE_EVALSET.equals(dataProviderMode)) { // Add AI import if needed @@ -423,7 +419,7 @@ private void updateEvalSetFile(TextDocument textDocument, ModulePartNode moduleP } // Find the evalSetFile path location in the data provider function - Optional filePathRange = findEvalSetFilePathLocation(dataProviderFunc.get(), textDocument); + Optional filePathRange = findEvalSetFilePathLocation(dataProviderFunc.get()); if (filePathRange.isEmpty()) { return; } @@ -473,11 +469,9 @@ private Optional findDataProviderFunction(ModulePartNode * Find the location of the evalSetFile path in the data provider function. * * @param dataProviderFunc the data provider function - * @param textDocument the text document * @return the line range of the file path string, or empty if not found */ - private Optional findEvalSetFilePathLocation(FunctionDefinitionNode dataProviderFunc, - TextDocument textDocument) { + private Optional findEvalSetFilePathLocation(FunctionDefinitionNode dataProviderFunc) { FunctionBodyNode functionBody = dataProviderFunc.functionBody(); if (functionBody instanceof FunctionBodyBlockNode blockBody) { From 562e57ce58a46afabf5936221abe0da043fc7b5d Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 09:32:41 +0530 Subject: [PATCH 15/26] Fix checkstyle errors --- .../io/ballerina/testmanagerservice/extension/Utils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index e793e97b77..e06c8a33f5 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -230,7 +230,8 @@ private static Annotation buildConfigAnnotation(MappingConstructorExpressionNode private static String extractEvalSetFileFromDataProvider(ModulePartNode modulePartNode, String dataProviderFunctionName) { // Find the data provider function - Optional dataProviderFunc = findFunctionByName(modulePartNode, dataProviderFunctionName); + Optional dataProviderFunc = + findFunctionByName(modulePartNode, dataProviderFunctionName); if (dataProviderFunc.isEmpty()) { return ""; @@ -572,7 +573,7 @@ public static String getConfigFieldValue(TestFunction function, String fieldName * @return the function definition node, or empty if not found */ public static Optional findFunctionByName(ModulePartNode modulePartNode, - String functionName) { + String functionName) { if (functionName == null || functionName.isEmpty()) { return Optional.empty(); } From a6e01c1a8ddd00671d4a3e62e70674082bdeab42 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 10:25:40 +0530 Subject: [PATCH 16/26] Fix failing test --- .../testmanagerservice/extension/TestManagerService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java index 187647c584..a252b8d510 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/TestManagerService.java @@ -153,6 +153,7 @@ public CompletableFuture getTestFunction(GetTestFunctio return CompletableFuture.supplyAsync(() -> { try { Path filePath = Path.of(request.filePath()); + this.workspaceManager.loadProject(filePath); Optional document = this.workspaceManager.document(filePath); Optional semanticModel = this.workspaceManager.semanticModel(filePath); if (document.isEmpty() || semanticModel.isEmpty()) { From a9e5ca97ed5dfd51865af700698fa41fba2235ce Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 13 Feb 2026 12:49:58 +0530 Subject: [PATCH 17/26] Fix PR comments suggestions --- .../core/model/node/AgentRunBuilder.java | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java index e9e0ce76f2..beae67f673 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java @@ -31,8 +31,6 @@ import org.eclipse.lsp4j.TextEdit; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -50,13 +48,14 @@ public class AgentRunBuilder extends CallBuilder { private static final String BALLERINA = "ballerina"; // Agent Call Properties - public static final String QUERY = "query"; - public static final String SESSION_ID = "sessionId"; - public static final String CONTEXT = "context"; + private static final String QUERY = "query"; + private static final String SESSION_ID = "sessionId"; + private static final String CONTEXT = "context"; public static final String LABEL = "Agent"; public static final String DESCRIPTION = "Create or reuse an Agent."; static final Set AGENT_CALL_PARAMS_TO_SHOW = Set.of(QUERY, SESSION_ID, CONTEXT); + private static final String STRING = "string"; @Override protected NodeKind getFunctionNodeKind() { @@ -105,7 +104,7 @@ private void overrideVariableName(TemplateContext context) { if (variableProp == null) { return; } - String uniqueVarName = NameUtil.generateVariableName("string", context.getAllVisibleSymbolNames()); + String uniqueVarName = NameUtil.generateVariableName(STRING, context.getAllVisibleSymbolNames()); props.put(Property.VARIABLE_KEY, AiUtils.createUpdatedProperty(variableProp, uniqueVarName)); } @@ -129,10 +128,10 @@ private void newVariableWithInferredTypeAndDefault(SourceBuilder sourceBuilder) if (inferredParam.isPresent()) { String returnType = flowNode.codedata().inferredReturnType(); Object inferredValue = inferredParam.get().value(); - // Default to "string" when the inferred type value is null or empty + // Default to STRING when the inferred type value is null or empty String inferredType = (inferredValue != null && !inferredValue.toString().isEmpty()) ? inferredValue.toString() - : "string"; + : STRING; String inferredTypeDef = inferredParam.get() .codedata().originalName(); typeName = returnType.replace(inferredTypeDef, inferredType); @@ -144,22 +143,16 @@ private void newVariableWithInferredTypeAndDefault(SourceBuilder sourceBuilder) @Override public Map> toSource(SourceBuilder sourceBuilder) { - // Use custom variable declaration with inferred type handling and default to "string" + // Use custom variable declaration with inferred type handling and default to STRING newVariableWithInferredTypeAndDefault(sourceBuilder); - FlowNode agentRunNode = sourceBuilder.flowNode; - Map> allTextEdits = new HashMap<>(); - - generateAgentCallSource(sourceBuilder, agentRunNode, allTextEdits); - - return allTextEdits; + return generateAgentCallSource(sourceBuilder, agentRunNode); } - private void generateAgentCallSource(SourceBuilder sourceBuilder, FlowNode agentRunNode, - Map> allTextEdits) { + private Map> generateAgentCallSource(SourceBuilder sourceBuilder, FlowNode agentRunNode) { Optional connection = agentRunNode.getProperty(Property.CONNECTION_KEY); if (connection.isEmpty()) { - throw new IllegalStateException("Agent variable must be defined for an agent call node"); + throw new IllegalStateException("Agent variable must be defined for an agent run node"); } if (FlowNodeUtil.hasCheckKeyFlagSet(agentRunNode)) { @@ -167,7 +160,7 @@ private void generateAgentCallSource(SourceBuilder sourceBuilder, FlowNode agent } Set excludeKeys = getExcludeKeys(agentRunNode); - Map> callTextEdits = sourceBuilder.token() + return sourceBuilder.token() .name(connection.get().toSourceCode()) .keyword(BALLERINA.equals(agentRunNode.codedata().org()) ? SyntaxKind.DOT_TOKEN : SyntaxKind.RIGHT_ARROW_TOKEN) @@ -176,13 +169,6 @@ private void generateAgentCallSource(SourceBuilder sourceBuilder, FlowNode agent .functionParameters(agentRunNode, excludeKeys) .textEdit() .build(); - - callTextEdits.forEach((path, textEdits) -> - allTextEdits.merge(path, textEdits, (existing, incoming) -> { - List merged = new ArrayList<>(existing); - merged.addAll(incoming); - return merged; - })); } private Set getExcludeKeys(FlowNode agentRunNode) { From 93fa9ac9f3186b4aad4d7f44f4a5949da672b8c4 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Sun, 15 Feb 2026 17:36:19 +0530 Subject: [PATCH 18/26] Update agent tool search to return input parameters --- .../core/search/AgentToolSearchCommand.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/search/AgentToolSearchCommand.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/search/AgentToolSearchCommand.java index 3febf8fb0c..b4d414f50d 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/search/AgentToolSearchCommand.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/search/AgentToolSearchCommand.java @@ -141,7 +141,7 @@ private List buildAgentToolNodes() { // Skip if function is within current position (editing context) Optional location = symbol.getLocation(); - if (location.isPresent()) { + if (position != null && location.isPresent()) { LineRange fnLineRange = location.get().lineRange(); if (fnLineRange.fileName().equals(position.fileName()) && PositionUtil.isWithinLineRange(fnLineRange, position)) { @@ -157,6 +157,26 @@ private List buildAgentToolNodes() { } boolean isIsolatedFunction = functionSymbol.qualifiers().contains(Qualifier.ISOLATED); + + // Extract input parameters + List> inputParameters = new ArrayList<>(); + FunctionTypeSymbol functionTypeSymbol = functionSymbol.typeDescriptor(); + Optional> optParams = functionTypeSymbol.params(); + if (optParams.isPresent()) { + for (ParameterSymbol parameterSymbol : optParams.get()) { + String paramName = parameterSymbol.getName().orElse(""); + String paramType = parameterSymbol.typeDescriptor().signature(); + inputParameters.add(Map.of("name", paramName, "type", paramType)); + } + } + + // Extract output type + String outputType = ""; + Optional optReturnTypeSymbol = functionTypeSymbol.returnTypeDescriptor(); + if (optReturnTypeSymbol.isPresent()) { + outputType = optReturnTypeSymbol.get().signature(); + } + Metadata metadata = new Metadata.Builder<>(null) .label(symbol.getName().get()) .description(functionSymbol.documentation() @@ -164,6 +184,8 @@ private List buildAgentToolNodes() { .orElse("Agent tool function")) .addData("isAgentTool", true) .addData("isIsolatedFunction", isIsolatedFunction) + .addData("inputParameters", inputParameters) + .addData("outputType", outputType) .build(); Codedata.Builder codedataBuilder = new Codedata.Builder<>(null) From 23b0086fd293e7f122a5be8c25dd9aea511df945 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Sun, 15 Feb 2026 23:19:12 +0530 Subject: [PATCH 19/26] Add caching for agent call builder --- .../flowmodelgenerator/core/AiUtils.java | 39 +++++----- .../core/model/node/AgentCallBuilder.java | 74 +++++++++++++++++- .../core/model/node/AgentRunBuilder.java | 75 ++++++++++++++++++- 3 files changed, 164 insertions(+), 24 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java index 887e72700a..fd88702a45 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java @@ -265,26 +265,25 @@ public static void addPropertyFromTemplate(NodeBuilder nodeBuilder, String key, Object valueToUse = customValue != null ? customValue : property.value(); boolean hidden = isHidden || property.hidden(); - nodeBuilder.properties().custom() - .metadata() - .label(property.metadata().label()) - .description(property.metadata().description()) - .stepOut() - .types(property.types()) - .placeholder(property.placeholder()) - .value(valueToUse) - .defaultValue(property.defaultValue()) - .imports(property.imports() != null ? property.imports().toString() : null) - .optional(property.optional()) - .editable(property.editable()) - .advanced(property.advanced()) - .hidden(hidden) - .modified(property.modified()) - .codedata() - .kind(property.codedata() != null ? property.codedata().kind() : "") - .stepOut() - .stepOut() - .addProperty(key); + Property copied = new Property( + property.metadata(), + property.types(), + valueToUse, + property.oldValue(), + property.placeholder(), + property.optional(), + property.editable(), + property.advanced(), + hidden, + property.modified(), + property.diagnostics(), + property.codedata(), + property.advancedValue(), + property.imports(), + property.defaultValue(), + property.comment() + ); + nodeBuilder.properties().build().put(key, copied); } /** diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java index 95546fe0c0..2c165214e3 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java @@ -29,6 +29,7 @@ import io.ballerina.flowmodelgenerator.core.Constants; import io.ballerina.flowmodelgenerator.core.model.Codedata; import io.ballerina.flowmodelgenerator.core.model.FlowNode; +import io.ballerina.flowmodelgenerator.core.model.Metadata; import io.ballerina.flowmodelgenerator.core.model.NodeBuilder; import io.ballerina.flowmodelgenerator.core.model.NodeKind; import io.ballerina.flowmodelgenerator.core.model.Property; @@ -66,6 +67,7 @@ public class AgentCallBuilder extends CallBuilder { private static final String BALLERINA = "ballerina"; + private Set cachedVisibleSymbolNames; // Agent Properties public static final String AGENT = "AGENT"; @@ -98,6 +100,9 @@ public class AgentCallBuilder extends CallBuilder { // Cache for agent templates to avoid expensive repeated creation private static final Map agentTemplateCache = new ConcurrentHashMap<>(); + // Cache for agent call function templates to avoid repeated FunctionDataBuilder.build() calls + private static final Map agentCallFnCache = new ConcurrentHashMap<>(); + @Override protected NodeKind getFunctionNodeKind() { return NodeKind.AGENT_CALL; @@ -118,12 +123,61 @@ public void setConcreteConstData() { public void setConcreteTemplateData(TemplateContext context) { setAgentProperties(this, context, null); setAdditionalAgentProperties(this, null); - super.setConcreteTemplateData(context); + + FlowNode callTemplate = getOrCreateCallFunctionTemplate(context); + restoreFromTemplate(callTemplate); + + Codedata contextCd = context.codedata(); + codedata().lineRange(contextCd.lineRange()).sourceCode(contextCd.sourceCode()); + // TODO: This is a temporary solution until we have a proper plan for handling all generic types. makeInferredTypePropertyOptional(); overrideVariableName(context); } + private FlowNode getOrCreateCallFunctionTemplate(TemplateContext context) { + Codedata cd = context.codedata(); + String cacheKey = String.format("%s|%s|%s|%s|%s", + cd.org(), cd.packageName(), cd.version(), cd.symbol(), cd.object()); + return agentCallFnCache.computeIfAbsent(cacheKey, k -> { + AgentCallBuilder temp = new AgentCallBuilder(); + temp.defaultModuleName(moduleInfo); + temp.callSuperSetConcreteTemplateData(context); + return temp.build(); + }); + } + + void callSuperSetConcreteTemplateData(TemplateContext context) { + super.setConcreteTemplateData(context); + } + + private void restoreFromTemplate(FlowNode template) { + Metadata md = template.metadata(); + if (md != null) { + metadata().label(md.label()).description(md.description()); + if (md.icon() != null) { + metadata().icon(md.icon()); + } + } + + Codedata cd = template.codedata(); + if (cd != null) { + codedata().node(cd.node()).org(cd.org()).module(cd.module()) + .packageName(cd.packageName()).object(cd.object()) + .version(cd.version()).symbol(cd.symbol()) + .inferredReturnType(cd.inferredReturnType()); + } + + if (template.properties() != null) { + Map currentProps = properties().build(); + template.properties().forEach(currentProps::put); + } + + if (template.flags() != 0) { + flag(template.flags()); + } + } + private void makeInferredTypePropertyOptional() { if (formBuilder == null) { return; @@ -138,6 +192,22 @@ private void makeInferredTypePropertyOptional() { } } + private Set getVisibleSymbolNames(TemplateContext context) { + if (cachedVisibleSymbolNames == null) { + cachedVisibleSymbolNames = context.getAllVisibleSymbolNames(); + } + return cachedVisibleSymbolNames; + } + + @Override + protected void setReturnTypeProperties(FunctionData functionData, TemplateContext context, + String label, String doc, boolean hidden) { + properties() + .type(functionData.returnType(), false, functionData.importStatements(), hidden, + Property.RESULT_TYPE_LABEL) + .data(functionData.returnType(), getVisibleSymbolNames(context), label, doc); + } + private void overrideVariableName(TemplateContext context) { if (formBuilder == null) { return; @@ -147,7 +217,7 @@ private void overrideVariableName(TemplateContext context) { if (variableProp == null) { return; } - String uniqueVarName = NameUtil.generateVariableName("string", context.getAllVisibleSymbolNames()); + String uniqueVarName = NameUtil.generateVariableName("string", getVisibleSymbolNames(context)); props.put(Property.VARIABLE_KEY, AiUtils.createUpdatedProperty(variableProp, uniqueVarName)); } diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java index beae67f673..49112c4305 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java @@ -20,7 +20,9 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.flowmodelgenerator.core.AiUtils; +import io.ballerina.flowmodelgenerator.core.model.Codedata; import io.ballerina.flowmodelgenerator.core.model.FlowNode; +import io.ballerina.flowmodelgenerator.core.model.Metadata; import io.ballerina.flowmodelgenerator.core.model.NodeKind; import io.ballerina.flowmodelgenerator.core.model.Property; import io.ballerina.flowmodelgenerator.core.model.SourceBuilder; @@ -36,6 +38,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** @@ -46,6 +49,7 @@ public class AgentRunBuilder extends CallBuilder { private static final String BALLERINA = "ballerina"; + private Set cachedVisibleSymbolNames; // Agent Call Properties private static final String QUERY = "query"; @@ -57,6 +61,9 @@ public class AgentRunBuilder extends CallBuilder { static final Set AGENT_CALL_PARAMS_TO_SHOW = Set.of(QUERY, SESSION_ID, CONTEXT); private static final String STRING = "string"; + // Cache for agent run function templates to avoid repeated FunctionDataBuilder.build() calls + private static final Map agentRunFnCache = new ConcurrentHashMap<>(); + @Override protected NodeKind getFunctionNodeKind() { return NodeKind.AGENT_RUN; @@ -75,12 +82,60 @@ public void setConcreteConstData() { @Override public void setConcreteTemplateData(TemplateContext context) { - super.setConcreteTemplateData(context); + FlowNode callTemplate = getOrCreateCallFunctionTemplate(context); + restoreFromTemplate(callTemplate); + + Codedata contextCd = context.codedata(); + codedata().lineRange(contextCd.lineRange()).sourceCode(contextCd.sourceCode()); + // TODO: This is a temporary solution until we have a proper plan for handling all generic types. makeInferredTypePropertyOptional(); overrideVariableName(context); } + private FlowNode getOrCreateCallFunctionTemplate(TemplateContext context) { + Codedata cd = context.codedata(); + String cacheKey = String.format("%s|%s|%s|%s|%s", + cd.org(), cd.packageName(), cd.version(), cd.symbol(), cd.object()); + return agentRunFnCache.computeIfAbsent(cacheKey, k -> { + AgentRunBuilder temp = new AgentRunBuilder(); + temp.defaultModuleName(moduleInfo); + temp.callSuperSetConcreteTemplateData(context); + return temp.build(); + }); + } + + void callSuperSetConcreteTemplateData(TemplateContext context) { + super.setConcreteTemplateData(context); + } + + private void restoreFromTemplate(FlowNode template) { + Metadata md = template.metadata(); + if (md != null) { + metadata().label(md.label()).description(md.description()); + if (md.icon() != null) { + metadata().icon(md.icon()); + } + } + + Codedata cd = template.codedata(); + if (cd != null) { + codedata().node(cd.node()).org(cd.org()).module(cd.module()) + .packageName(cd.packageName()).object(cd.object()) + .version(cd.version()).symbol(cd.symbol()) + .inferredReturnType(cd.inferredReturnType()); + } + + if (template.properties() != null) { + Map currentProps = properties().build(); + template.properties().forEach(currentProps::put); + } + + if (template.flags() != 0) { + flag(template.flags()); + } + } + private void makeInferredTypePropertyOptional() { if (formBuilder == null) { return; @@ -95,6 +150,22 @@ private void makeInferredTypePropertyOptional() { } } + private Set getVisibleSymbolNames(TemplateContext context) { + if (cachedVisibleSymbolNames == null) { + cachedVisibleSymbolNames = context.getAllVisibleSymbolNames(); + } + return cachedVisibleSymbolNames; + } + + @Override + protected void setReturnTypeProperties(FunctionData functionData, TemplateContext context, + String label, String doc, boolean hidden) { + properties() + .type(functionData.returnType(), false, functionData.importStatements(), hidden, + Property.RESULT_TYPE_LABEL) + .data(functionData.returnType(), getVisibleSymbolNames(context), label, doc); + } + private void overrideVariableName(TemplateContext context) { if (formBuilder == null) { return; @@ -104,7 +175,7 @@ private void overrideVariableName(TemplateContext context) { if (variableProp == null) { return; } - String uniqueVarName = NameUtil.generateVariableName(STRING, context.getAllVisibleSymbolNames()); + String uniqueVarName = NameUtil.generateVariableName(STRING, getVisibleSymbolNames(context)); props.put(Property.VARIABLE_KEY, AiUtils.createUpdatedProperty(variableProp, uniqueVarName)); } From c20ed20313101c06bafc21a40a8cfc1699e7499a Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Sun, 15 Feb 2026 23:27:11 +0530 Subject: [PATCH 20/26] Update failing tests --- .../search/config/agent_tools/calc_sum.json | 13 +++++++- .../search/config/agent_tools/default.json | 31 +++++++++++++++++-- .../config/agent_tools/partial_match.json | 9 +++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/calc_sum.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/calc_sum.json index 97945af9e8..166897e4e2 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/calc_sum.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/calc_sum.json @@ -33,7 +33,18 @@ "description": "Agent tool function", "data": { "isAgentTool": true, - "isIsolatedFunction": true + "isIsolatedFunction": true, + "inputParameters": [ + { + "name": "a", + "type": "int" + }, + { + "name": "b", + "type": "int" + } + ], + "outputType": "int" } }, "codedata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/default.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/default.json index 5f2851a402..add5535715 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/default.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/default.json @@ -31,7 +31,18 @@ "description": "Agent tool function", "data": { "isAgentTool": true, - "isIsolatedFunction": true + "isIsolatedFunction": true, + "inputParameters": [ + { + "name": "a", + "type": "int" + }, + { + "name": "b", + "type": "int" + } + ], + "outputType": "int" } }, "codedata": { @@ -49,7 +60,14 @@ "description": "Agent tool function", "data": { "isAgentTool": true, - "isIsolatedFunction": true + "isIsolatedFunction": true, + "inputParameters": [ + { + "name": "location", + "type": "string" + } + ], + "outputType": "json" } }, "codedata": { @@ -67,7 +85,14 @@ "description": "Agent tool function", "data": { "isAgentTool": true, - "isIsolatedFunction": true + "isIsolatedFunction": true, + "inputParameters": [ + { + "name": "data", + "type": "json" + } + ], + "outputType": "json" } }, "codedata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/partial_match.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/partial_match.json index 3cec11d884..27deee17ee 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/partial_match.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/search/config/agent_tools/partial_match.json @@ -33,7 +33,14 @@ "description": "Agent tool function", "data": { "isAgentTool": true, - "isIsolatedFunction": true + "isIsolatedFunction": true, + "inputParameters": [ + { + "name": "data", + "type": "json" + } + ], + "outputType": "json" } }, "codedata": { From 067738bb35f92b21df065ae7890e19ff1bb8a2c8 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 13:47:35 +0530 Subject: [PATCH 21/26] Update type descriptor field name in AGENT_CALL node --- .../flowmodelgenerator/core/AiUtils.java | 44 +++++++++++++++++++ .../core/model/node/AgentCallBuilder.java | 7 ++- .../core/model/node/AgentRunBuilder.java | 7 ++- .../config/agent_call_flow_node_7.json | 2 +- .../config/agent_call_flow_node_8.json | 2 +- .../config/agent_call_flow_node_9.json | 2 +- .../config/agent_call_template_hide_td.json | 2 +- .../config/agent_source_hide_td.json | 2 +- 8 files changed, 61 insertions(+), 7 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java index fd88702a45..7635a9c481 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AiUtils.java @@ -242,6 +242,50 @@ public static Property copyAsOptionalAdvanced(Property original) { ); } + /** + * Creates a copy of a property with updated metadata label while preserving all other fields. + * + * @param original the property to copy from + * @param newLabel the new label to set in metadata + * @return the new property with updated metadata label + */ + public static Property createPropertyWithUpdatedLabel(Property original, String newLabel) { + if (original == null) { + throw new IllegalArgumentException("Original property cannot be null"); + } + if (original.metadata() == null) { + throw new IllegalArgumentException("Original property metadata cannot be null"); + } + + Metadata updatedMetadata = new Metadata( + newLabel, + original.metadata().description(), + original.metadata().keywords(), + original.metadata().icon(), + original.metadata().functionKind(), + original.metadata().data() + ); + + return new Property( + updatedMetadata, + original.types(), + original.value(), + original.oldValue(), + original.placeholder(), + original.optional(), + original.editable(), + original.advanced(), + original.hidden(), + original.modified(), + original.diagnostics(), + original.codedata(), + original.advancedValue(), + original.imports(), + original.defaultValue(), + original.comment() + ); + } + /** * Adds a property to a NodeBuilder by copying all attributes from an existing property with an optional custom * value. diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java index 2c165214e3..d6b2d425c9 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java @@ -187,7 +187,12 @@ private void makeInferredTypePropertyOptional() { Property prop = entry.getValue(); if (prop.codedata() != null && ParameterData.Kind.PARAM_FOR_TYPE_INFER.name().equals(prop.codedata().kind())) { - props.put(entry.getKey(), AiUtils.copyAsOptionalAdvanced(prop)); + Property updatedProp = AiUtils.copyAsOptionalAdvanced(prop); + // Update the label to "Type Descriptor" for better clarity + if ("td".equals(entry.getKey()) && updatedProp.metadata() != null) { + updatedProp = AiUtils.createPropertyWithUpdatedLabel(updatedProp, "Type Descriptor"); + } + props.put(entry.getKey(), updatedProp); } } } diff --git a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java index 49112c4305..8302223536 100644 --- a/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java +++ b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentRunBuilder.java @@ -145,7 +145,12 @@ private void makeInferredTypePropertyOptional() { Property prop = entry.getValue(); if (prop.codedata() != null && ParameterData.Kind.PARAM_FOR_TYPE_INFER.name().equals(prop.codedata().kind())) { - props.put(entry.getKey(), AiUtils.copyAsOptionalAdvanced(prop)); + Property updatedProp = AiUtils.copyAsOptionalAdvanced(prop); + // Update the label to "Type Descriptor" for better clarity + if ("td".equals(entry.getKey()) && updatedProp.metadata() != null) { + updatedProp = AiUtils.createPropertyWithUpdatedLabel(updatedProp, "Type Descriptor"); + } + props.put(entry.getKey(), updatedProp); } } } diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json index ce88b7ef75..7b07578c08 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json @@ -639,7 +639,7 @@ }, "td": { "metadata": { - "label": "Td", + "label": "Type Descriptor", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json index 211212df28..cffe2921ee 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json @@ -540,7 +540,7 @@ }, "td": { "metadata": { - "label": "Td", + "label": "Type Descriptor", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json index 823f16ff00..d4bb5342a0 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json @@ -542,7 +542,7 @@ }, "td": { "metadata": { - "label": "Td", + "label": "Type Descriptor", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json index 73798d8f55..de17925065 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json @@ -452,7 +452,7 @@ }, "td": { "metadata": { - "label": "Td", + "label": "Type Descriptor", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_source_hide_td.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_source_hide_td.json index 994e6ef87b..40596f34fe 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_source_hide_td.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_source_hide_td.json @@ -455,7 +455,7 @@ }, "td": { "metadata": { - "label": "Td", + "label": "Type Descriptor", "description": "Type descriptor specifying the expected return type format" }, "types": [ From 9cd89fd0117089d5aafbdd173933e21e0d07e371 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 14:26:30 +0530 Subject: [PATCH 22/26] Fix failing tests --- .../config/agent_call_flow_node_1.json | 26 +++++++++-------- .../config/agent_call_flow_node_2.json | 26 +++++++++-------- .../config/agent_call_flow_node_3.json | 26 +++++++++-------- .../config/agent_call_flow_node_4.json | 26 +++++++++-------- .../config/agent_call_flow_node_7.json | 28 +++++++++++-------- .../config/agent_call_flow_node_8.json | 28 +++++++++++-------- .../config/agent_call_flow_node_9.json | 28 +++++++++++-------- 7 files changed, 108 insertions(+), 80 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_1.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_1.json index c43c49f51d..86507a8745 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_1.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_1.json @@ -139,7 +139,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -162,7 +163,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -185,7 +187,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -222,7 +225,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -249,7 +253,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -276,7 +281,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -298,7 +304,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -383,10 +390,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_2.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_2.json index be6a5be2a0..ab10fe5d80 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_2.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_2.json @@ -140,7 +140,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -163,7 +164,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -186,7 +188,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -223,7 +226,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -250,7 +254,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -277,7 +282,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -299,7 +305,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -384,10 +391,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_3.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_3.json index a9734319ee..a313adb23d 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_3.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_3.json @@ -139,7 +139,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -162,7 +163,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -185,7 +187,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -222,7 +225,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -249,7 +253,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -276,7 +281,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -298,7 +304,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -383,10 +390,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_4.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_4.json index 56005ebd65..6dcfb1d9de 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_4.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_4.json @@ -140,7 +140,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -163,7 +164,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -186,7 +188,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -223,7 +226,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -250,7 +254,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -277,7 +282,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -300,7 +306,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -385,10 +392,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json index 7b07578c08..27ac1f2d70 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_7.json @@ -238,7 +238,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -261,7 +262,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -283,7 +285,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -310,7 +313,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "\"INFER_TOOL_COUNT\"" }, @@ -337,7 +341,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -359,7 +364,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -395,7 +401,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "toolLoadingStrategy" }, "defaultValue": "\"LLM_FILTER\"" }, @@ -480,10 +487,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { @@ -639,7 +643,7 @@ }, "td": { "metadata": { - "label": "Type Descriptor", + "label": "Td", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json index cffe2921ee..e4db0138fa 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_8.json @@ -140,7 +140,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -162,7 +163,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -184,7 +186,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -211,7 +214,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "\"INFER_TOOL_COUNT\"" }, @@ -238,7 +242,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -260,7 +265,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -296,7 +302,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "toolLoadingStrategy" }, "defaultValue": "\"LLM_FILTER\"" }, @@ -381,10 +388,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { @@ -540,7 +544,7 @@ }, "td": { "metadata": { - "label": "Type Descriptor", + "label": "Td", "description": "Type descriptor specifying the expected return type format" }, "types": [ diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json index d4bb5342a0..099728927a 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_flow_node_9.json @@ -141,7 +141,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -164,7 +165,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -186,7 +188,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -213,7 +216,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "\"INFER_TOOL_COUNT\"" }, @@ -240,7 +244,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -262,7 +267,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -298,7 +304,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "toolLoadingStrategy" }, "defaultValue": "\"LLM_FILTER\"" }, @@ -383,10 +390,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { @@ -542,7 +546,7 @@ }, "td": { "metadata": { - "label": "Type Descriptor", + "label": "Td", "description": "Type descriptor specifying the expected return type format" }, "types": [ From f7d9f642bded274398e6d14eea11bfa374b7da7a Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 15:44:12 +0530 Subject: [PATCH 23/26] Fix failing tests --- .../config/agent_call_template.json | 26 +++++++++++-------- .../config/agent_call_template_ballerina.json | 23 +++++++++------- .../config/agent_call_template_hide_td.json | 26 +++++++++++-------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template.json index e69f8b3fb3..96b0783768 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template.json @@ -66,7 +66,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -88,7 +89,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -110,7 +112,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -146,7 +149,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -173,7 +177,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -200,7 +205,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -222,7 +228,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -294,10 +301,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_ballerina.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_ballerina.json index 74b6fb1776..52b5f7d596 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_ballerina.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_ballerina.json @@ -66,7 +66,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -88,7 +89,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -110,7 +112,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -137,7 +140,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "\"INFER_TOOL_COUNT\"" }, @@ -164,7 +168,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -186,7 +191,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -258,10 +264,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json index de17925065..2018fc6636 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/agents_manager/config/agent_call_template_hide_td.json @@ -66,7 +66,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -88,7 +89,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -110,7 +112,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -137,7 +140,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "\"INFER_TOOL_COUNT\"" }, @@ -164,7 +168,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -186,7 +191,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -222,7 +228,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "toolLoadingStrategy" }, "defaultValue": "\"LLM_FILTER\"" }, @@ -294,10 +301,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { From 56bb38c521bb216a824df1146dcd04c30eea1e01 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 16:45:52 +0530 Subject: [PATCH 24/26] Fix failing tests --- ...agent_with_backticks_in_system_prompt.json | 26 +++++++++++-------- .../diagram_generator/config/chat_agent.json | 26 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/agent_with_backticks_in_system_prompt.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/agent_with_backticks_in_system_prompt.json index b943d898fd..747a9abba8 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/agent_with_backticks_in_system_prompt.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/agent_with_backticks_in_system_prompt.json @@ -155,7 +155,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -178,7 +179,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -201,7 +203,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -237,7 +240,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -264,7 +268,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -291,7 +296,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -313,7 +319,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -398,10 +405,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { diff --git a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/chat_agent.json b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/chat_agent.json index e0b90715e1..7b930695f6 100644 --- a/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/chat_agent.json +++ b/flow-model-generator/modules/flow-model-generator-ls-extension/src/test/resources/diagram_generator/config/chat_agent.json @@ -155,7 +155,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "systemPrompt" }, "defaultValue": "{role: \"\", instructions: \"\"}" }, @@ -178,7 +179,8 @@ "advanced": false, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "model" }, "defaultValue": "object {}" }, @@ -201,7 +203,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "tools" }, "defaultValue": "[]" }, @@ -237,7 +240,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "agentType" }, "defaultValue": "\"FUNCTION_CALL_AGENT\"" }, @@ -264,7 +268,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "maxIter" }, "defaultValue": "0" }, @@ -291,7 +296,8 @@ "advanced": true, "hidden": false, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "verbose" }, "defaultValue": "false" }, @@ -313,7 +319,8 @@ "advanced": true, "hidden": true, "codedata": { - "kind": "INCLUDED_FIELD" + "kind": "INCLUDED_FIELD", + "originalName": "memory" }, "defaultValue": "()" }, @@ -398,10 +405,7 @@ "optional": false, "editable": true, "advanced": true, - "hidden": true, - "codedata": { - "kind": "" - } + "hidden": true }, "role": { "metadata": { From 8db28273947f8a67ad495688f0cf8ee542c5c841 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 17:06:25 +0530 Subject: [PATCH 25/26] Update test function return type to error? --- .../testmanagerservice/extension/Utils.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java index e06c8a33f5..c3f8b1e907 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/main/java/io/ballerina/testmanagerservice/extension/Utils.java @@ -342,21 +342,6 @@ public static String getTestFunctionTemplate(TestFunction function) { builder.append(Constants.SPACE) .append(Constants.OPEN_CURLY_BRACE) .append(Constants.LINE_SEPARATOR) - .append(Constants.TAB_SEPARATOR) - .append(Constants.KEYWORD_DO) - .append(Constants.SPACE) - .append(Constants.OPEN_CURLY_BRACE) - .append(Constants.LINE_SEPARATOR) - .append(Constants.TAB_SEPARATOR) - .append(Constants.CLOSE_CURLY_BRACE) - .append(Constants.SPACE) - .append(Constants.ON_FAIL_ERROR_STMT) - .append(Constants.SPACE) - .append(Constants.OPEN_CURLY_BRACE) - .append(Constants.LINE_SEPARATOR) - .append(Constants.TAB_SEPARATOR) - .append(Constants.CLOSE_CURLY_BRACE) - .append(Constants.LINE_SEPARATOR) .append(Constants.CLOSE_CURLY_BRACE); return builder.toString(); } @@ -392,7 +377,7 @@ public static String buildFunctionParams(List parameters) { public static String buildReturnType(Property returnType) { if (returnType == null || returnType.value() == null || returnType.value().toString().trim().isEmpty()) { - return ""; + return Constants.SPACE + Constants.KEYWORD_RETURNS + Constants.SPACE + "error?"; } return Constants.SPACE + Constants.KEYWORD_RETURNS + Constants.SPACE + returnType.value().toString().trim(); } From e969364868e61be469af265fb0458a33ae6ed8a3 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 16 Feb 2026 20:04:40 +0530 Subject: [PATCH 26/26] Update failing tests --- .../add_test_with_minpassrate_config1.json | 2 +- .../config/project_with_test_config1.json | 2 +- .../config/project_with_test_config1.json | 26 +++++++++---------- .../config/update_evalset_file_config1.json | 2 +- .../config/update_minpassrate_config1.json | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json index 40def159ea..c090c16963 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/add_test_with_minpassrate_config1.json @@ -107,7 +107,7 @@ "character": 22 } }, - "newText": "@test:Config{\ngroups: [\"stability\"],\nruns: 10,\nminPassRate: 80\n}\nfunction testWithMinPassRate() {\n\tdo {\n\t} on fail error err {\n\t}\n}" + "newText": "@test:Config{\ngroups: [\"stability\"],\nruns: 10,\nminPassRate: 80\n}\nfunction testWithMinPassRate() returns error? {\n}" } ] } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/project_with_test_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/project_with_test_config1.json index 34e5c6c5c3..598b7c85bc 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/project_with_test_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/add_test_functions/config/project_with_test_config1.json @@ -141,7 +141,7 @@ "character": 1 } }, - "newText": "@test:Config{\ngroups: [\"g1\"]\n}\nfunction testFunction1(string a, string b = \"default\") {\n\tdo {\n\t} on fail error err {\n\t}\n}" + "newText": "@test:Config{\ngroups: [\"g1\"]\n}\nfunction testFunction1(string a, string b = \"default\") returns error? {\n}" } ] } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json index 414b1787a8..17b3a9f6ef 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/project_with_test_config1.json @@ -133,41 +133,41 @@ { "range": { "start": { - "line": 3, - "character": 0 + "line": 4, + "character": 9 }, "end": { - "line": 3, - "character": 29 + "line": 4, + "character": 22 } }, - "newText": "@test:Config{\ngroups: [\"g1\"]\n}" + "newText": "myNewTestFunction" }, { "range": { "start": { "line": 4, - "character": 9 + "character": 22 }, "end": { "line": 4, - "character": 22 + "character": 54 } }, - "newText": "myNewTestFunction" + "newText": "(string a, string b = \"default\") returns error?" }, { "range": { "start": { - "line": 4, - "character": 22 + "line": 3, + "character": 0 }, "end": { - "line": 4, - "character": 54 + "line": 3, + "character": 29 } }, - "newText": "(string a, string b = \"default\")" + "newText": "@test:Config{\ngroups: [\"g1\"]\n}" } ] } diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json index 9f7b8cb9e5..a1ab1624a8 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_evalset_file_config1.json @@ -139,7 +139,7 @@ "character": 45 } }, - "newText": "(ai:Trace thread)" + "newText": "(ai:Trace thread) returns error?" }, { "range": { diff --git a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json index b0cb6edf88..328b4929bd 100644 --- a/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json +++ b/test-manager-service/modules/test-manager-service-ls-extension/src/test/resources/update_test_function/config/update_minpassrate_config1.json @@ -107,7 +107,7 @@ "character": 30 } }, - "newText": "()" + "newText": "() returns error?" }, { "range": {