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..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. @@ -265,26 +309,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/AvailableNodesGenerator.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/AvailableNodesGenerator.java index f46456968b..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 @@ -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; @@ -73,9 +73,10 @@ 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.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 +127,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 +325,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 +418,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 (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); } else if (kind == FunctionData.Kind.FUNCTION) { @@ -460,6 +464,16 @@ private Optional getCategory(Symbol symbol, Predicate con } } + private Optional getAgent(Symbol symbol) { + return getCategory(symbol, classSymbol -> { + try { + return isAgentClass(classSymbol); + } 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/AgentCallBuilder.java b/flow-model-generator/modules/flow-model-generator-core/src/main/java/io/ballerina/flowmodelgenerator/core/model/node/AgentCallBuilder.java index 95546fe0c0..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 @@ -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; @@ -133,11 +187,32 @@ 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); } } } + 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 +222,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 new file mode 100644 index 0000000000..8302223536 --- /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,257 @@ +/* + * 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.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; +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.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +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"; + private Set cachedVisibleSymbolNames; + + // Agent Call Properties + 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"; + + // 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; + } + + @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) { + 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; + } + 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())) { + 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); + } + } + } + + 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; + } + Map props = formBuilder.build(); + Property variableProp = props.get(Property.VARIABLE_KEY); + if (variableProp == null) { + return; + } + String uniqueVarName = NameUtil.generateVariableName(STRING, getVisibleSymbolNames(context)); + 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; + return generateAgentCallSource(sourceBuilder, agentRunNode); + } + + 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 run node"); + } + + if (FlowNodeUtil.hasCheckKeyFlagSet(agentRunNode)) { + sourceBuilder.token().keyword(SyntaxKind.CHECK_KEYWORD); + } + + Set excludeKeys = getExcludeKeys(agentRunNode); + return 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(); + } + + 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-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) 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 377011f07c..4bc7ada807 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) { 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 ce88b7ef75..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": { 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..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": { 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..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": { 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 73798d8f55..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": { @@ -452,7 +456,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": [ 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 } 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 } 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": { 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": { 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<>(); 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}" } ] } 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..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 @@ -29,9 +29,16 @@ 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 LOAD_CONVERSATION_THREADS = "loadConversationThreads"; + public static final String TEST_ANNOTATION = "@test:"; public static final String CONFIG_GROUPS = "groups"; public static final String CONFIG_ENABLED = "enabled"; @@ -46,7 +53,9 @@ 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_ISOLATED = "isolated"; public static final String KEYWORD_FUNCTION = "function"; public static final String KEYWORD_RETURNS = "returns"; public static final String KEYWORD_DO = "do"; @@ -58,6 +67,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..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 @@ -19,13 +19,26 @@ package io.ballerina.testmanagerservice.extension; 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.ExpressionNode; +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.StatementNode; import io.ballerina.projects.Document; 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.model.TestFunction; import io.ballerina.testmanagerservice.extension.request.AddTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.GetTestFunctionRequest; import io.ballerina.testmanagerservice.extension.request.TestsDiscoveryRequest; @@ -37,6 +50,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 +63,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. @@ -137,7 +153,7 @@ public CompletableFuture getTestFunction(GetTestFunctio return CompletableFuture.supplyAsync(() -> { try { Path filePath = Path.of(request.filePath()); - Project project = this.workspaceManager.loadProject(filePath); + this.workspaceManager.loadProject(filePath); Optional document = this.workspaceManager.document(filePath); Optional semanticModel = this.workspaceManager.semanticModel(filePath); if (document.isEmpty() || semanticModel.isEmpty()) { @@ -151,7 +167,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); @@ -172,17 +189,66 @@ 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; + + 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 evalSetFile = getEvalSetFile(request.function()); + if (evalSetFile == null || evalSetFile.isEmpty()) { + evalSetFile = "session.json"; // Default fallback + } + String dataProviderFunction = Utils.getEvalSetDataProviderFunctionTemplate( + dataProviderFunctionName, + evalSetFile + ); + edits.add(new TextEdit(Utils.toRange(lineRange.endLine()), dataProviderFunction)); + + // Add ai:ConversationThread 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 +256,53 @@ public CompletableFuture addTestFunction(AddTestFunctionRe }); } + private String getDataProviderMode(TestFunction function) { + return Utils.getConfigFieldValue(function, "dataProviderMode"); + } + + private String getEvalSetFile(TestFunction function) { + return Utils.getConfigFieldValue(function, "evalSetFile"); + } + + private void addAiConversationThreadParameter(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(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. * @@ -218,6 +331,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())) { @@ -225,13 +340,180 @@ 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, 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, 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()); + 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) { + return Utils.getAnnotationsRange(annotations); + } + + /** + * 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(TestFunction function) { + return Utils.getConfigFieldValue(function, "dataProvider"); + } + + /** + * 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) { + return Utils.findFunctionByName(modulePartNode, dataProviderName); + } + + /** + * Find the location of the evalSetFile path in the data provider function. + * + * @param dataProviderFunc the data provider function + * @return the line range of the file path string, or empty if not found + */ + private Optional findEvalSetFilePathLocation(FunctionDefinitionNode dataProviderFunc) { + 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) { + return Utils.findLoadConversationThreadsArgumentInStatements(statements) + .map(ExpressionNode::lineRange); + } + + /** + * 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(ExpressionNode expression) { + return Utils.findLoadConversationThreadsArgument(expression) + .map(ExpressionNode::lineRange); + } + + /** + * 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) { + 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 668734c393..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 @@ -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")) @@ -83,8 +95,8 @@ public static TestFunction getTestFunctionModel(FunctionDefinitionNode functionD // annotations functionDefinitionNode.metadata().ifPresent(metadata -> { List annotations = new ArrayList<>(); - for (AnnotationNode annotationNode: metadata.annotations()) { - annotations.add(getAnnotationModel(annotationNode, semanticModel)); + for (AnnotationNode annotationNode : metadata.annotations()) { + 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,19 +117,21 @@ 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(); - for (MappingFieldNode field: fields) { + String dataProviderName = null; + for (MappingFieldNode field : fields) { if (field instanceof SpecificFieldNode specificFieldNode) { String fieldName = specificFieldNode.fieldName().toSourceCode().trim(); Optional expressionNode = specificFieldNode.valueExpr(); @@ -136,19 +151,182 @@ 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(); + dataProviderName = value; + builder.dataProvider(value); + } + } + case "dataProviderMode" -> { + if (expressionNode.isPresent()) { + String value = expressionNode.get().toSourceCode().trim(); + 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) { + 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 -> { + } } } } + + // 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) { + // Find the data provider function + Optional dataProviderFunc = + findFunctionByName(modulePartNode, dataProviderFunctionName); + + 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 ""; + } + + /** + * 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()) { + Optional result = findLoadConversationThreadsArgument(expr.get()); + if (result.isPresent()) { + return result; + } + } + } else if (statement instanceof ExpressionStatementNode exprStmt) { + 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 ""; + } + + /** + * 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 findLoadConversationThreadsArgument(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.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) { + 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 ""; + } + public static String getTestFunctionTemplate(TestFunction function) { StringBuilder builder = new StringBuilder(); @@ -164,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(); } @@ -214,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(); } @@ -263,13 +426,64 @@ 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() + .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)) { + 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 +510,131 @@ 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); + }); + } + + /** + * 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. + * + * @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, 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) + .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 ai:loadConversationThreads(\"" + evalSetFilePath + "\");") + .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..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 @@ -20,15 +20,25 @@ 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 evalSetFile; + 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 +55,48 @@ 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 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"); + } + + 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 +109,82 @@ 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); + + // Always add dataProvider (default to empty string) + if (dataProvider == null) { + dataProvider = value("Data Provider", "Data provider function", "", + "EXPRESSION", "dataProvider"); + } + properties.add(dataProvider); + + // 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); + + // 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); + + // 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); + + // Always add after (default to empty string) + if (after == null) { + after = value("After", "Function to run after this test", "", + "EXPRESSION", "after"); + } + properties.add(after); + + // Always add before (default to empty string) + if (before == null) { + before = value("Before", "Function to run before this test", "", + "EXPRESSION", "before"); + } + properties.add(before); + + // 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); + + // 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; String name = "Config"; - return new Annotation(metadata, null, org, module, name, List.of(groups, enabled)); + return new Annotation(metadata, null, org, module, name, properties); } } } 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..c090c16963 --- /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() 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/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/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..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 @@ -154,7 +154,20 @@ "character": 54 } }, - "newText": "(string a, string b = \"default\")" + "newText": "(string a, string b = \"default\") returns error?" + }, + { + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 29 + } + }, + "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 new file mode 100644 index 0000000000..a1ab1624a8 --- /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) returns error?" + }, + { + "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..328b4929bd --- /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": "() returns error?" + }, + { + "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!"); +}