diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index e003732b9f..0e516695ca 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -560,14 +560,14 @@ importers: ../../workspaces/ballerina/ballerina-extension: dependencies: '@ai-sdk/amazon-bedrock': - specifier: 4.0.83 - version: 4.0.83(zod@4.1.8) + specifier: 4.0.67 + version: 4.0.67(zod@4.1.8) '@ai-sdk/anthropic': - specifier: 3.0.64 - version: 3.0.64(zod@4.1.8) + specifier: 3.0.48 + version: 3.0.48(zod@4.1.8) '@ai-sdk/google-vertex': - specifier: 4.0.94 - version: 4.0.94(zod@4.1.8) + specifier: 4.0.66 + version: 4.0.66(zod@4.1.8) '@iarna/toml': specifier: 2.2.5 version: 2.2.5 @@ -688,9 +688,6 @@ importers: xstate: specifier: 4.38.3 version: 4.38.3 - yaml: - specifier: 2.8.3 - version: 2.8.3 zod: specifier: 4.1.8 version: 4.1.8 @@ -4157,6 +4154,9 @@ importers: axios: specifier: 1.15.0 version: 1.15.0 + canonicalize: + specifier: 2.1.0 + version: 2.1.0 copyfiles: specifier: 2.4.1 version: 2.4.1 @@ -4175,6 +4175,9 @@ importers: fs-extra: specifier: 11.3.0 version: 11.3.0 + glob: + specifier: 11.1.0 + version: 11.1.0 handlebars: specifier: 4.7.9 version: 4.7.9 @@ -4305,9 +4308,6 @@ importers: eslint: specifier: ^9.27.0 version: 9.39.4(jiti@2.6.1) - glob: - specifier: 11.1.0 - version: 11.1.0 mocha: specifier: 11.4.0 version: 11.4.0 @@ -4968,8 +4968,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/amazon-bedrock@4.0.83': - resolution: {integrity: sha512-DoRpvIWGU/r83UeJAM9L93Lca8Kf/yP5fIhfEOltMPGP/PXrGe0BZaz0maLSRn8djJ6+HzWIsgu5ZI6bZqXEXg==} + '@ai-sdk/amazon-bedrock@4.0.67': + resolution: {integrity: sha512-Bb5HkT5h2mIsQTGzlcGNEDu+19d5pA8FPNiqlbXxVmYFKbLjZu/4nncY0GXVnXUglX9GdheMIofanYlL9O3KIg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -4986,14 +4986,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/anthropic@3.0.63': - resolution: {integrity: sha512-SiLosFr0FfKfrNpAAj8mD/i3S5YBB/z5orb1DH3pN1yATuBNjjPMLnRE4P3Dn7Y5cQsro0uzw5g5117hkShWoQ==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/anthropic@3.0.64': - resolution: {integrity: sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g==} + '@ai-sdk/anthropic@3.0.48': + resolution: {integrity: sha512-QSvz0XumBFaxulalUB+3D2/tLvfsYjIcG3HlqOxmmXIzATIBLInTVEw0Sy3yXJjqhNQZedBokVrN53Bh/V+eWQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -5014,14 +5008,14 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google-vertex@4.0.94': - resolution: {integrity: sha512-jaPG5kLnFBLm0IPNH0yQNJseE6cyZmpDYqumQ3jQmrmQ8530QVwjZ+Z88L1h2pNYqEeFug4h5F+IVuXddXFgyw==} + '@ai-sdk/google-vertex@4.0.66': + resolution: {integrity: sha512-euSOJSgOwemtnQ/EIM2LgKPPWUqOkZwYTejsle79K/kOWvDzu376IbvaLaegLQ+uymxx2pskhmSrMvAD1uAQoA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@3.0.53': - resolution: {integrity: sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw==} + '@ai-sdk/google@3.0.33': + resolution: {integrity: sha512-ElHkhMGMJ1MY5AlwLljWWE1jj+Bs3cMyq0KbeWUu2H89OsMAORiE4cB3xhfLlSIEnVmVKx/YHjoW3bN+DFI24A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -5038,12 +5032,6 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.21': - resolution: {integrity: sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@3.0.1': resolution: {integrity: sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg==} engines: {node: '>=18'} @@ -13371,6 +13359,10 @@ packages: caniuse-lite@1.0.30001788: resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + canonicalize@2.1.0: + resolution: {integrity: sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==} + hasBin: true + canvas@3.2.3: resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} engines: {node: ^18.12.0 || >= 20.9.0} @@ -25190,11 +25182,11 @@ snapshots: aws4fetch: 1.0.20 zod: 4.1.11 - '@ai-sdk/amazon-bedrock@4.0.83(zod@4.1.8)': + '@ai-sdk/amazon-bedrock@4.0.67(zod@4.1.8)': dependencies: - '@ai-sdk/anthropic': 3.0.64(zod@4.1.8) + '@ai-sdk/anthropic': 3.0.48(zod@4.1.8) '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.21(zod@4.1.8) + '@ai-sdk/provider-utils': 4.0.15(zod@4.1.8) '@smithy/eventstream-codec': 4.2.13 '@smithy/util-utf8': 4.2.2 aws4fetch: 1.0.20 @@ -25212,16 +25204,10 @@ snapshots: '@ai-sdk/provider-utils': 4.0.15(zod@4.1.11) zod: 4.1.11 - '@ai-sdk/anthropic@3.0.63(zod@4.1.8)': + '@ai-sdk/anthropic@3.0.48(zod@4.1.8)': dependencies: '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.21(zod@4.1.8) - zod: 4.1.8 - - '@ai-sdk/anthropic@3.0.64(zod@4.1.8)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.21(zod@4.1.8) + '@ai-sdk/provider-utils': 4.0.15(zod@4.1.8) zod: 4.1.8 '@ai-sdk/devtools@0.0.6': @@ -25244,21 +25230,21 @@ snapshots: '@vercel/oidc': 3.1.0 zod: 4.1.8 - '@ai-sdk/google-vertex@4.0.94(zod@4.1.8)': + '@ai-sdk/google-vertex@4.0.66(zod@4.1.8)': dependencies: - '@ai-sdk/anthropic': 3.0.63(zod@4.1.8) - '@ai-sdk/google': 3.0.53(zod@4.1.8) + '@ai-sdk/anthropic': 3.0.48(zod@4.1.8) + '@ai-sdk/google': 3.0.33(zod@4.1.8) '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.21(zod@4.1.8) + '@ai-sdk/provider-utils': 4.0.15(zod@4.1.8) google-auth-library: 10.6.2 zod: 4.1.8 transitivePeerDependencies: - supports-color - '@ai-sdk/google@3.0.53(zod@4.1.8)': + '@ai-sdk/google@3.0.33(zod@4.1.8)': dependencies: '@ai-sdk/provider': 3.0.8 - '@ai-sdk/provider-utils': 4.0.21(zod@4.1.8) + '@ai-sdk/provider-utils': 4.0.15(zod@4.1.8) zod: 4.1.8 '@ai-sdk/provider-utils@4.0.15(zod@4.1.11)': @@ -25282,13 +25268,6 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.1.11 - '@ai-sdk/provider-utils@4.0.21(zod@4.1.8)': - dependencies: - '@ai-sdk/provider': 3.0.8 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.1.8 - '@ai-sdk/provider@3.0.1': dependencies: json-schema: 0.4.0 @@ -41382,6 +41361,8 @@ snapshots: caniuse-lite@1.0.30001788: {} + canonicalize@2.1.0: {} + canvas@3.2.3: dependencies: node-addon-api: 7.1.1 diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/ballerina.ts b/workspaces/ballerina/ballerina-core/src/interfaces/ballerina.ts index 2f15649ae6..fca680b76d 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/ballerina.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/ballerina.ts @@ -121,7 +121,6 @@ export interface TypeField { selected?: boolean; originalTypeName?: string; resolvedUnionType?: TypeField | TypeField[]; - elements?: TypeField[]; } export interface BallerinaConnectorInfo extends BallerinaConnector { diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts index 49827b8bdf..23683bfe49 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts @@ -361,12 +361,6 @@ export enum FUNCTION_TYPE { ALL = "all", } -export enum VISIBILITY { - PUBLIC = "public", - PRIVATE = "private", - MODULE = "module", -} - /** * Represents the directory structure of artifacts in a project. */ @@ -426,7 +420,7 @@ export interface ProjectStructureArtifactResponse { position?: NodePosition; resources?: ProjectStructureArtifactResponse[]; isNew?: boolean; - visibility?: VISIBILITY; + isPublic?: boolean; } export interface UpdatedArtifactsResponse { @@ -559,7 +553,7 @@ export type NodeKind = | "MATCH" | "METHOD_CALL" | "MEMORY" - | "SHORT_TERM_MEMORY_STORE" + | "MEMORY_STORE" | "MODEL_PROVIDER" | "MODEL_PROVIDERS" | "VARIABLE" @@ -600,7 +594,6 @@ export type NodeKind = | "WORKFLOW" | "WORKFLOW_RUN" | "WORKER" - | "RECORD" | "VARIABLE"; export type OverviewFlow = { diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/extended-lang-client.ts b/workspaces/ballerina/ballerina-core/src/interfaces/extended-lang-client.ts index 60be19f747..a363d7a337 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/extended-lang-client.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/extended-lang-client.ts @@ -23,7 +23,7 @@ import { DocumentIdentifier, LinePosition, LineRange, NOT_SUPPORTED_TYPE, Positi import { BallerinaConnectorInfo, BallerinaExampleCategory, BallerinaModuleResponse, BallerinaModulesRequest, BallerinaTrigger, BallerinaTriggerInfo, BallerinaConnector, ExecutorPosition, ExpressionRange, JsonToRecordMapperDiagnostic, MainTriggerModifyRequest, NoteBookCellOutputValue, NotebookCellMetaInfo, OASpec, PackageSummary, PartialSTModification, ResolvedTypeForExpression, ResolvedTypeForSymbol, STModification, SequenceModel, SequenceModelDiagnostic, ServiceTriggerModifyRequest, SymbolDocumentation, XMLToRecordConverterDiagnostic, TypeField, ComponentInfo } from "./ballerina"; import { ModulePart, STNode } from "@wso2/syntax-tree"; import { CodeActionParams, DefinitionParams, DocumentSymbolParams, ExecuteCommandParams, InitializeParams, InitializeResult, LocationLink, RenameParams } from "vscode-languageserver-protocol"; -import { Category, Flow, FlowNode, CodeData, ConfigVariable, FunctionNode, Property, PropertyTypeMemberInfo, DIRECTORY_MAP, Imports, NodeKind, InputType, FormFieldInputType, ProjectStructureArtifactResponse, VISIBILITY } from "./bi"; +import { Category, Flow, FlowNode, CodeData, ConfigVariable, FunctionNode, Property, PropertyTypeMemberInfo, DIRECTORY_MAP, Imports, NodeKind, InputType, FormFieldInputType, ProjectStructureArtifactResponse } from "./bi"; import { ConnectorRequest, ConnectorResponse } from "../rpc-types/connector-wizard/interfaces"; import { SqFlow } from "../rpc-types/sequence-diagram/interfaces"; import { FieldType, FunctionModel, ListenerModel, ServiceClassModel, ServiceInitModel, ServiceModel } from "./service"; @@ -968,7 +968,7 @@ export type SearchKind = | "CHUNKER" | "AGENT" | "MEMORY" - | "SHORT_TERM_MEMORY_STORE" + | "MEMORY_STORE" | "AGENT_TOOL" | "CLASS_INIT" | "ALL"; @@ -1869,21 +1869,9 @@ export interface AIToolResponse { }; } -export interface CertConfig { - path?: string; - password?: string; -} - -export interface SecureSocketConfig { - cert?: CertConfig; - key?: CertConfig; - insecure?: boolean; -} - export interface McpToolsRequest { serviceUrl?: string; accessToken?: string; - secureSocket?: SecureSocketConfig; } export interface McpToolsResponse { @@ -2001,7 +1989,6 @@ export interface BaseArtifact { name: string; module?: string; scope: string; - visibility?: VISIBILITY; icon?: string; // Optional for those that have an icon children?: Record; // To allow nested structures accessor?: string; // Specific to Entry Points diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/index.ts index 7edd71fbc3..c8f7789aeb 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/index.ts @@ -29,7 +29,6 @@ export interface AIAgentAPI { getTool: (params: AIToolRequest) => Promise; getMcpTools: (params: McpToolsRequest) => Promise; genTool: (params: AIGentToolsRequest) => Promise; - fixMissingImports: () => Promise; getPackageVersion: (params: AIGetPackageVersionRequest) => Promise; configureDefaultModelProvider: () => Promise; createAIAgent: (params: AIAgentRequest) => Promise; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/rpc-type.ts index 5107d652df..4ccfe7ce83 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/rpc-type.ts @@ -32,7 +32,6 @@ export const getTool: RequestType = { method: `${ export const getMcpTools: RequestType = { method: `${_preFix}/getMcpTools` }; export const genTool: RequestType = { method: `${_preFix}/genTool` }; export const getPackageVersion: RequestType = { method: `${_preFix}/getPackageVersion` }; -export const fixMissingImports: RequestType = { method: `${_preFix}/fixMissingImports` }; export const configureDefaultModelProvider: NotificationType = { method: `${_preFix}/configureDefaultModelProvider` }; export const createAIAgent: RequestType = { method: `${_preFix}/createAIAgent` }; export const updateAIAgentTools: RequestType = { method: `${_preFix}/updateAIAgentTools` }; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts index 33b8055547..5c38c870eb 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts @@ -50,11 +50,7 @@ import { CompactConversationRequest, CompactConversationResponse, PromptEnhancementRequest, - PromptEnhancementResponse, - ClarifyAnswerRequest, - ClarifyCancelRequest, - RunningServiceInfo, - StopRunningServiceRequest, + PromptEnhancementResponse } from "./interfaces"; export interface AIPanelAPI { @@ -128,11 +124,4 @@ export interface AIPanelAPI { // ================================== enhancePrompt: (params: PromptEnhancementRequest) => Promise; promptForLogin: () => void; - submitClarifyAnswer: (params: ClarifyAnswerRequest) => Promise; - cancelClarify: (params: ClarifyCancelRequest) => Promise; - // ================================== - // Running Services (long-lived agent processes) - // ================================== - getRunningServices: () => Promise; - stopRunningService: (params: StopRunningServiceRequest) => Promise; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts index 1c413ac137..0c03b163ac 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts @@ -27,8 +27,8 @@ import { ComponentInfo, DataMapperMetadata, Diagnostics, DMModel, ImportStatemen // General Interfaces // ================================== export type AIPanelPrompt = - | { type: 'command-template'; command: Command; templateId: TemplateId; text?: string; params?: Record; metadata?: Record; hiddenContext?: string } - | { type: 'text'; text: string; planMode: boolean; codeContext?: CodeContext; autoSubmit?: boolean; hiddenContext?: string; suggestedCommandTemplates?: AIPanelPrompt[]; inputPlaceholder?:string; } + | { type: 'command-template'; command: Command; templateId: TemplateId; text?: string; params?: Record; metadata?: Record } + | { type: 'text'; text: string; planMode: boolean; codeContext?: CodeContext; autoSubmit?: boolean } | undefined; export interface AIMachineSnapshot { @@ -301,7 +301,6 @@ export type CodeContext = export interface GenerateAgentCodeRequest { usecase: string; - hiddenContext?: string; operationType?: OperationType; fileAttachmentContents: FileAttatchment[]; threadId?: string; //TODO: Make this required once we support threads in UI @@ -466,15 +465,6 @@ export interface WebToolApprovalRequest { requestId: string; } -export interface ClarifyAnswerRequest { - requestId: string; - answers: Array<{ question: string; answers: string[] }>; -} - -export interface ClarifyCancelRequest { - requestId: string; -} - export type ErrorCode = { code: number; message: string; @@ -523,28 +513,6 @@ export interface UsageResponse { export interface OpenFileDiffRequest { relativePath: string; } - -// ================================== -// Running Services (long-lived processes started by the AI agent) -// ================================== - -/** Serializable view of a running service tracked by the agent's RunningServicesManager. */ -export interface RunningServiceInfo { - /** Unique identifier returned by the runBallerinaPackage tool. */ - taskId: string; - /** Filesystem path of the package being run. */ - packagePath: string; - /** Epoch ms when the process started. */ - startedAt: number; - /** True once the process has exited (naturally or via stop). */ - exited: boolean; - /** Exit code of the process. 0 while still running. */ - exitCode: number; -} - -export interface StopRunningServiceRequest { - taskId: string; -} // ================================== // Compaction Related Interfaces // ================================== diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts index 14963a2e8e..234bd4d1e6 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts @@ -52,11 +52,7 @@ import { CompactConversationRequest, CompactConversationResponse, PromptEnhancementRequest, - PromptEnhancementResponse, - ClarifyAnswerRequest, - ClarifyCancelRequest, - RunningServiceInfo, - StopRunningServiceRequest, + PromptEnhancementResponse } from "./interfaces"; import { RequestType, NotificationType } from "vscode-messenger-common"; @@ -114,8 +110,3 @@ export const compactConversation: RequestType = { method: `${_preFix}/getShowContextUsage` }; export const enhancePrompt: RequestType = { method: `${_preFix}/enhancePrompt` }; export const promptForLogin: NotificationType = { method: `${_preFix}/promptForLogin` }; -export const submitClarifyAnswer: RequestType = { method: `${_preFix}/submitClarifyAnswer` }; -export const cancelClarify: RequestType = { method: `${_preFix}/cancelClarify` }; -export const getRunningServices: RequestType = { method: `${_preFix}/getRunningServices` }; -export const stopRunningService: RequestType = { method: `${_preFix}/stopRunningService` }; -export const runningServicesChanged: NotificationType = { method: `${_preFix}/runningServicesChanged` }; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/index.ts index 833eaac3b2..505ed1be11 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/index.ts @@ -115,7 +115,6 @@ import { BIAiSuggestionsRequest, BIAiSuggestionsResponse, AIChatRequest, - InlineAgentChatRequest, BreakpointRequest, CurrentBreakpointsResponse, FormDidOpenParams, @@ -131,10 +130,7 @@ import { DeleteProjectRequest, OpenReadmeRequest, ValidateProjectFormRequest, - ValidateProjectFormResponse, - SuggestedProjectDefaultsResponse, - UpdateProjectTitleRequest, - UpdatePackageTitleRequest + ValidateProjectFormResponse } from "./interfaces"; export interface BIDiagramAPI { @@ -155,7 +151,6 @@ export interface BIDiagramAPI { getAiSuggestions: (params: BIAiSuggestionsRequest) => Promise; createProject: (params: ProjectRequest) => void; validateProjectPath: (params: ValidateProjectFormRequest) => Promise; - getSuggestedProjectDefaults: (params: { isInProject: boolean }) => Promise; deleteProject: (params: DeleteProjectRequest) => void; addProjectToWorkspace: (params: AddProjectToWorkspaceRequest) => void; getWorkspaces: () => Promise; @@ -177,8 +172,6 @@ export interface BIDiagramAPI { deployProject: (params: DeploymentRequest) => Promise; deployWorkspace: (params: WorkspaceDeploymentRequest) => Promise; openAIChat: (params: AIChatRequest) => void; - startInlineAgentChat: (params: InlineAgentChatRequest) => void; - cleanupAgentChatServices: () => Promise; getSignatureHelp: (params: SignatureHelpRequest) => Promise; buildProject: (mode: BuildMode) => void; runProject: () => void; @@ -224,6 +217,4 @@ export interface BIDiagramAPI { getOpenApiGeneratedModules: (params: OpenAPIGeneratedModulesRequest) => Promise; deleteOpenApiGeneratedModules: (params: OpenAPIClientDeleteRequest) => Promise; OpenConfigTomlRequest: (params: OpenConfigTomlRequest) => Promise; - updateProjectTitle: (params: UpdateProjectTitleRequest) => Promise; - updatePackageTitle: (params: UpdatePackageTitleRequest) => Promise; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/interfaces.ts index 487b972852..60f9b14dd5 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/interfaces.ts @@ -17,7 +17,7 @@ */ import { LineRange } from "../../interfaces/common"; -import { DIRECTORY_MAP, Flow, FlowNode, OverviewFlow } from "../../interfaces/bi"; +import { DIRECTORY_MAP, Flow, OverviewFlow } from "../../interfaces/bi"; import { BallerinaProjectComponents } from "../../interfaces/extended-lang-client"; import { RemoteFunction, ServiceType } from "../../interfaces/ballerina"; import { ImportInfo } from "../ai-panel/interfaces"; @@ -30,10 +30,8 @@ export interface ProjectRequest { createAsWorkspace?: boolean; workspaceName?: string; orgName?: string; - orgHandle?: string; version?: string; isLibrary?: boolean; - projectHandle?: string; } export interface AddProjectToWorkspaceRequest { @@ -43,10 +41,8 @@ export interface AddProjectToWorkspaceRequest { convertToWorkspace?: boolean; workspaceName?: string; orgName?: string; - orgHandle?: string; version?: string; isLibrary?: boolean; - projectHandle?: string; } export interface WorkspacesResponse { @@ -169,12 +165,6 @@ export interface AIChatRequest { planMode: boolean; } -export interface InlineAgentChatRequest { - agentVarName: string; - filePath: string; - agentNode?: FlowNode; -} - export interface ImportStatements { filePath: string; statements: ImportInfo[]; @@ -236,13 +226,6 @@ export interface ValidateProjectFormRequest { createAsWorkspace?: boolean; } -export interface SuggestedProjectDefaultsResponse { - projectName: string; - projectHandle: string; - integrationName: string; - packageName: string; -} - export interface ValidateProjectFormResponse { isValid: boolean; errorMessage?: string; @@ -253,13 +236,3 @@ export enum ValidateProjectFormErrorField { PATH = 'path', NAME = 'name' } - -export interface UpdateProjectTitleRequest { - projectPath: string; - title: string; -} - -export interface UpdatePackageTitleRequest { - packagePath: string; - title: string; -} diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/rpc-type.ts index 8d31d5a386..7ea1ecaf60 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/bi-diagram/rpc-type.ts @@ -117,7 +117,6 @@ import { BIAiSuggestionsRequest, BIAiSuggestionsResponse, AIChatRequest, - InlineAgentChatRequest, BreakpointRequest, CurrentBreakpointsResponse, FormDidOpenParams, @@ -133,10 +132,7 @@ import { DeleteProjectRequest, OpenReadmeRequest, ValidateProjectFormRequest, - ValidateProjectFormResponse, - SuggestedProjectDefaultsResponse, - UpdateProjectTitleRequest, - UpdatePackageTitleRequest + ValidateProjectFormResponse } from "./interfaces"; import { RequestType, NotificationType } from "vscode-messenger-common"; @@ -158,7 +154,6 @@ export const getNodeTemplate: RequestType = { method: `${_preFix}/getAiSuggestions` }; export const createProject: NotificationType = { method: `${_preFix}/createProject` }; export const validateProjectPath: RequestType = { method: `${_preFix}/validateProjectPath` }; -export const getSuggestedProjectDefaults: RequestType<{ isInProject: boolean }, SuggestedProjectDefaultsResponse> = { method: `${_preFix}/getSuggestedProjectDefaults` }; export const deleteProject: NotificationType = { method: `${_preFix}/deleteProject` }; export const addProjectToWorkspace: NotificationType = { method: `${_preFix}/addProjectToWorkspace` }; export const getWorkspaces: RequestType = { method: `${_preFix}/getWorkspaces` }; @@ -180,8 +175,6 @@ export const renameIdentifier: NotificationType = { met export const deployProject: RequestType = { method: `${_preFix}/deployProject` }; export const deployWorkspace: RequestType = { method: `${_preFix}/deployWorkspace` }; export const openAIChat: NotificationType = { method: `${_preFix}/openAIChat` }; -export const startInlineAgentChat: NotificationType = { method: `${_preFix}/startInlineAgentChat` }; -export const cleanupAgentChatServices: RequestType = { method: `${_preFix}/cleanupAgentChatServices` }; export const getSignatureHelp: RequestType = { method: `${_preFix}/getSignatureHelp` }; export const buildProject: NotificationType = { method: `${_preFix}/buildProject` }; export const runProject: NotificationType = { method: `${_preFix}/runProject` }; @@ -228,5 +221,3 @@ export const getOpenApiGeneratedModules: RequestType = { method: `${_preFix}/deleteOpenApiGeneratedModules` }; export const openConfigToml: RequestType = { method: `${_preFix}/openConfigToml` }; export const getExpressionTokens: RequestType = { method: `${_preFix}/getExpressionTokens` }; -export const updateProjectTitle: RequestType = { method: `${_preFix}/updateProjectTitle` }; -export const updatePackageTitle: RequestType = { method: `${_preFix}/updatePackageTitle` }; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/common/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/common/index.ts index bb2b270d7a..4124e8601c 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/common/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/common/index.ts @@ -67,6 +67,4 @@ export interface CommonRPCAPI { getDefaultOrgName: () => Promise; publishToCentral: () => Promise; hasCentralPATConfigured: () => Promise; - getPreferredTryItOption: () => Promise; - setPreferredTryItOption: (option: string) => Promise; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/common/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/common/interfaces.ts index 2940f34e67..1129944e02 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/common/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/common/interfaces.ts @@ -86,8 +86,6 @@ export interface FileOrDirResponse { } export interface FileOrDirRequest { isFile?: boolean; - filters?: Record; - allowOutsideProject?: boolean; } export interface ShowErrorMessageRequest { diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/common/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/common/rpc-type.ts index f529151a1a..0e18954e54 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/common/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/common/rpc-type.ts @@ -68,5 +68,3 @@ export const downloadSelectedSampleFromGithub: RequestType = { method: `${_preFix}/getDefaultOrgName` }; export const publishToCentral: RequestType = { method: `${_preFix}/publishToCentral` }; export const hasCentralPATConfigured: RequestType = { method: `${_preFix}/hasCentralPATConfigured` }; -export const getPreferredTryItOption: RequestType = { method: `${_preFix}/getPreferredTryItOption` }; -export const setPreferredTryItOption: RequestType = { method: `${_preFix}/setPreferredTryItOption` }; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/index.ts index c40a400ab2..2d3e1ef6f8 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/index.ts @@ -17,7 +17,7 @@ */ import { ImportIntegrationResponse } from "../../interfaces/extended-lang-client"; -import { ActiveMigrationSession, GetMigrationToolsResponse, ImportIntegrationRPCRequest, MigrateRequest, MigrationToolPullRequest, OpenMigrationReportRequest, OpenSubProjectReportRequest, SaveMigrationReportRequest, StoreSubProjectReportsRequest } from "./interfaces"; +import { GetMigrationToolsResponse, ImportIntegrationRPCRequest, MigrateRequest, MigrationToolPullRequest, OpenMigrationReportRequest, OpenSubProjectReportRequest, SaveMigrationReportRequest, StoreSubProjectReportsRequest } from "./interfaces"; export interface MigrateIntegrationAPI { getMigrationTools: () => Promise; @@ -28,15 +28,4 @@ export interface MigrateIntegrationAPI { storeSubProjectReports: (params: StoreSubProjectReportsRequest) => void; saveMigrationReport: (params: SaveMigrationReportRequest) => void; migrateProject: (params: MigrateRequest) => void; - getActiveMigrationSession: () => Promise; - /** Starts (or resumes) the AI enhancement pipeline from AI Chat. */ - startMigrationEnhancement: () => Promise; - /** Seeds any persisted migration conversation history into the chat state. */ - seedMigrationHistory: () => Promise; - /** Triggers the wizard-level streaming enhancement pipeline (called from AI Chat after startMigrationEnhancement). */ - wizardEnhancementReady: () => Promise; - /** Aborts the currently running migration AI agent. */ - abortMigrationAgent: () => Promise; - /** Retrieves the persisted migration conversation history messages for display in AI Chat. */ - getMigrationHistoryMessages: () => Promise>; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/interfaces.ts index 30cee17735..5656071c9b 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/interfaces.ts @@ -65,29 +65,12 @@ export interface SaveMigrationReportRequest { }; } -/** - * Describes the current state of a migration AI enhancement session. - * Returned by `getActiveMigrationSession` so the UI can show a live banner. - */ -export interface ActiveMigrationSession { - /** `true` while the enhancement pipeline is running. */ - isActive: boolean; - /** `true` when the AI enhancement feature was used (wizard or post-wizard). */ - aiFeatureUsed: boolean; - /** `true` once the AI enhancement pipeline has completed successfully. */ - fullyEnhanced: boolean; -} - export interface MigrateRequest { project: ProjectRequest; textEdits: { [key: string]: string; }; projects?: ProjectMigrationResult[]; - /** `true` when the AI enhancement feature was used (wizard or post-wizard). */ - aiFeatureUsed?: boolean; - /** Absolute path to the original source project (e.g. Mule XML directory). */ - sourcePath?: string; } export interface OpenSubProjectReportRequest { diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/rpc-type.ts index bef4d4d3cb..ebead0af2b 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/migrate-integration/rpc-type.ts @@ -18,7 +18,7 @@ * THIS FILE INCLUDES AUTO GENERATED CODE */ import { ImportIntegrationResponse } from "../../interfaces/extended-lang-client"; -import { ActiveMigrationSession, GetMigrationToolsResponse, ImportIntegrationRPCRequest, MigrateRequest, MigrationToolPullRequest, OpenMigrationReportRequest, OpenSubProjectReportRequest, SaveMigrationReportRequest, StoreSubProjectReportsRequest } from "./interfaces"; +import { GetMigrationToolsResponse, ImportIntegrationRPCRequest, MigrateRequest, MigrationToolPullRequest, OpenMigrationReportRequest, OpenSubProjectReportRequest, SaveMigrationReportRequest, StoreSubProjectReportsRequest } from "./interfaces"; import { RequestType, NotificationType } from "vscode-messenger-common"; const _preFix = "migrate-integration"; @@ -30,5 +30,3 @@ export const openSubProjectReport: NotificationType export const storeSubProjectReports: NotificationType = { method: `${_preFix}/storeSubProjectReports` }; export const saveMigrationReport: NotificationType = { method: `${_preFix}/saveMigrationReport` }; export const migrateProject: NotificationType = { method: `${_preFix}/migrateProject` }; -export const getActiveMigrationSession: RequestType = { method: `${_preFix}/getActiveMigrationSession` }; -export const seedMigrationHistory: RequestType = { method: `${_preFix}/seedMigrationHistory` }; diff --git a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts index 83e0732d7d..f2ee116e87 100644 --- a/workspaces/ballerina/ballerina-core/src/state-machine-types.ts +++ b/workspaces/ballerina/ballerina-core/src/state-machine-types.ts @@ -21,7 +21,7 @@ import { NodePosition, STNode } from "@wso2/syntax-tree"; import { Command } from "./interfaces/ai-panel"; import { LinePosition } from "./interfaces/common"; import { ProjectInfo, ProjectMigrationResult, Type } from "./interfaces/extended-lang-client"; -import { CodeData, DIRECTORY_MAP, FlowNode, ProjectStructureArtifactResponse, ProjectStructureResponse } from "./interfaces/bi"; +import { CodeData, DIRECTORY_MAP, ProjectStructureArtifactResponse, ProjectStructureResponse } from "./interfaces/bi"; import { DiagnosticEntry, DocumentationGeneratorIntermediaryState, SourceFile, CodeContext, FileAttatchment } from "./rpc-types/ai-panel/interfaces"; export type MachineStateValue = @@ -369,12 +369,11 @@ export type ChatNotify = | GeneratedSourcesEvent | ConnectorGenerationNotification | ConfigurationCollectionEvent - | ClarifyEvent | ChatComponentEvent | PlanUpdated | CompactionStartEvent | CompactionEndEvent - | CompactionDisabledEvent + | CompactionFailedEvent | ConfigChangeEvent; export interface ChatStart { @@ -448,7 +447,6 @@ export interface EvalsToolResult { export interface UsageMetricsEvent { type: "usage_metrics"; isRepair?: boolean; - model?: string; usage: { inputTokens: number; cacheCreationInputTokens: number; @@ -459,7 +457,6 @@ export interface UsageMetricsEvent { systemInstructions: number; toolDefinitions: number; reservedOutput: number; - files: number; messages: number; toolResults: number; }; @@ -472,7 +469,6 @@ export interface TaskApprovalRequest { tasks: Task[]; taskDescription?: string; message?: string; - autoApproved?: boolean; } export interface WebToolApprovalEvent { @@ -531,21 +527,6 @@ export interface ConfigurationCollectionEvent { }; } -export interface ClarifyQuestion { - question: string; - tabLabel: string; - options: Array<{ label: string; value: string }>; - selectionType: "single" | "multiple"; -} - -export interface ClarifyEvent { - type: "clarify_event"; - requestId: string; - stage: "asking" | "answered" | "skipped"; - questions: ClarifyQuestion[]; - answers?: Array<{ question: string; answers: string[] }>; -} - export interface ChatComponentEvent { type: "chat_component"; id?: string; @@ -559,24 +540,24 @@ export interface PlanUpdated { } // ================================== -// Server-side Compaction Events +// Mid-Stream Compaction Events // ================================== -/** Fired when the server starts compacting (detected mid-stream via providerMetadata) */ +/** Fired when mid-stream compaction starts (stream naturally pauses) */ export interface CompactionStartEvent { type: 'compaction_start'; } -/** Fired when server-side compaction completes; carries the extracted summary */ +/** Fired when mid-stream compaction completes and streaming resumes */ export interface CompactionEndEvent { type: 'compaction_end'; - /** Extracted content from the compaction block */ - summary?: string; + metadata?: GenerationCompactionMetadata; } -/** Fired once per session when compaction is disabled because the codebase floor exceeds the trigger */ -export interface CompactionDisabledEvent { - type: 'compaction_disabled'; +/** Fired when mid-stream compaction fails and context cannot be reduced */ +export interface CompactionFailedEvent { + type: 'compaction_failed'; + reason: string; } /** Fired when a VS Code configuration setting relevant to the AI panel changes */ @@ -825,8 +806,9 @@ export enum TaskStatus { export enum TaskTypes { SERVICE_DESIGN = "service_design", + CONNECTIONS_INIT = "connections_init", IMPLEMENTATION = "implementation", - EXECUTION = "execution" + TESTING = "testing" } /** diff --git a/workspaces/ballerina/ballerina-extension/CHANGELOG.md b/workspaces/ballerina/ballerina-extension/CHANGELOG.md index 72bead505f..1ce8994f91 100644 --- a/workspaces/ballerina/ballerina-extension/CHANGELOG.md +++ b/workspaces/ballerina/ballerina-extension/CHANGELOG.md @@ -9,26 +9,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ### Added -- **Persist Database Support** — Added support for persist database workflows in BI, including multiple database connections, edit flows, and dependent type validation. -- **Library Projects** — Added end-to-end support for library projects, including creation and validation improvements, redesigned overview experiences, `lib.bal` validator import, publishing to Ballerina Central, and deployment enforcement when deploying workspaces to WSO2 Cloud. -- **BI Copilot** — Added new agent capabilities including library search/get tools, ConfigCollector, test-runner integration, clarify/web tooling, plan mode, redesigned review experiences, inline chat, telemetry insights, and support for agent evaluations. -- **Data Mapper** — Added support for JSON/XML mappings, DSS query input/output mapping generation, module-level construct consolidation, backward mapping, and diagnostics support in clause forms. -- **Platform & Tooling** — Added support for WSO2 Cloud and Integration Control Plane workflows in BI, WSO2 HTTP/Hurl client integration, and remote server debugging improvements. +- **Persist Database Support** — Added support for persist database workflows in BI, including multiple database connections. +- **Library Projects** — Added end-to-end support for library projects, including creation improvements, new overview page, `lib.bal` validator import, publishing to Ballerina Central, and deployment enforcement when deploying workspaces to Devant. +- **BI Copilot** — Added new agent capabilities including library search/get tools, ConfigCollector, test-runner integration, plan-mode toggle, new/old review preview, telemetry insights, and support for agent evaluations. +- **Data Mapper** — Added support for JSON/XML mappings, DSS query input/output mapping generation, module-level construct consolidation, and diagnostics support in clause forms. +- **Developer Experience** — Added support for Devant connections in BI and remote server debugging improvements. ### Changed -- **Copilot Authentication & Config** — Migrated Copilot to the WSO2 Cloud auth flow, updated environment keys and pipeline inputs, and improved BI Copilot configuration, context compaction, and prompt handling. -- **Editor & Forms** — Implemented new array/map editor experience, introduced dependent type editor behavior for persist forms, and improved record configuration, project creation, and integration-creation UX. -- **Platform Naming & Extension Flow** — Reworked platform integration around WSO2 Integrator, refreshed WSO2 Cloud terminology, and updated related workflows and dependencies. -- **Service Designer & Connectors** — Updated FTP service-designer flows, improved connection creation/edit experiences, and reordered WSO2 Cloud marketplace placement in connector selection views. +- **Copilot Authentication & Config** — Migrated Copilot to the Devant auth flow, updated environment keys and pipeline inputs, and improved BI Copilot configuration handling. +- **Editor & Forms** — Implemented new array/map editor experience, introduced dependent type editor behavior for persist forms, and improved record configuration modal UX and layout. +- **Service Designer & Connectors** — Updated FTP service-designer flows and reordered Devant marketplace placement in connector selection views. ### Fixed -- **Forms & Validation** — Fixed project create-form validation regressions, if/match form behavior, response editor checkbox issues, save-state edge cases, loader styling, and form diagnostics handling edge cases. -- **Expression & Type Editing** — Fixed SQL editor rendering for query fields, imported-type import insertion, helper and diagnostics behavior across editors, user-defined type visibility in non-workspace projects, and function-call related create-function action visibility. -- **Service & Resource Flows** — Fixed service designer/configurable view issues, resource header value handling, path sanitization for `.` resource paths, XML corruption during data-service editing, and project/workspace refresh and migration issues. -- **Copilot & Agent Flow** — Fixed multi-turn chat state persistence, chat agent creation with listener support, config-collector placeholder handling, trigger/context issues, and login notification issues for default model provider configuration. -- **Cross-Platform & Security** — Improved Windows build and path handling, CLI/test-environment reliability, and applied vulnerability and dependency security fixes across BI extension components. +- **Forms & Validation** — Fixed project create-form validation regressions, if/match form behavior, response editor checkbox issues, loader styling, and form diagnostics handling edge cases. +- **Expression & Type Editing** — Fixed SQL editor rendering for query fields, imported-type import insertion, user-defined type visibility in non-workspace projects, and function-call related create-function action visibility. +- **Service & Resource Flows** — Fixed service designer/configurable view issues, resource header value handling, path sanitization for `.` resource paths, and XML corruption during data-service editing. +- **Copilot & Agent Flow** — Fixed multi-turn chat state persistence, chat agent creation with listener support, config-collector placeholder handling, and login notification issues for default model provider configuration. +- **Security** — Applied vulnerability and dependency security fixes across BI extension components. ## [5.8.2](https://github.com/wso2/vscode-extensions/compare/ballerina-5.8.1...ballerina-5.8.2) - 2026-03-11 diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index bd8532f0c7..d90b8d0dba 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -2,7 +2,7 @@ "name": "ballerina", "displayName": "Ballerina", "description": "Ballerina Language support, debugging, graphical visualization, AI-based data-mapping and many more.", - "version": "5.10.0", + "version": "5.9.326022015", "publisher": "wso2", "icon": "resources/images/ballerina.png", "homepage": "https://wso2.com/ballerina/vscode/docs", @@ -91,7 +91,7 @@ }, "ballerina.icpUrl": { "type": "string", - "default": "https://localhost:9446", + "default": "https://localhost:9445", "description": "URL of the Integration Control Plane (ICP) server." }, "ballerina.icpUsername": { @@ -771,6 +771,11 @@ "category": "Ballerina", "enablement": "isSupportedProject" }, + { + "command": "ballerina.startTraceServer", + "title": "Start Trace Server", + "category": "Ballerina" + }, { "command": "ballerina.enableTracing", "title": "Enable Tracing", @@ -784,12 +789,6 @@ "icon": "$(circle-slash)", "enablement": "isSupportedProject" }, - { - "command": "ballerina.startTraceServer", - "title": "Start Trace Server", - "category": "Ballerina", - "enablement": "isSupportedProject && ballerina.tracingEnabled" - }, { "command": "ballerina.clearTraces", "title": "Clear Traces", @@ -1223,14 +1222,14 @@ "description": "start", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f248" + "fontCharacter": "\\f21f" } }, "ballerina-debug": { "description": "debug", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f1c0" + "fontCharacter": "\\f17a" } }, "ballerina-source-view": { @@ -1244,7 +1243,7 @@ "description": "persist-diagram", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f21f" + "fontCharacter": "\\f1f9" } }, "ballerina-cached-rounded": { @@ -1342,7 +1341,7 @@ "description": "design-view", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f1c5" + "fontCharacter": "\\f1bf" } }, "distro-file-submodule": { @@ -1422,12 +1421,12 @@ "build": "pnpm run compile && pnpm run lint && pnpm run postbuild", "rebuild": "pnpm run clean && pnpm run compile && pnpm run postbuild", "postbuild": "pnpm run download-ls && pnpm run copyFonts && pnpm run copyJSLibs && pnpm run package && pnpm run copyVSIX", - "copyJSLibs": "copyfiles -f ../ballerina-visualizer/build/*.js resources/jslibs && copyfiles -f ../ballerina-visualizer/build/images/* resources/jslibs/images && copyfiles -f ../trace-visualizer/build/*.js resources/jslibs" + "copyJSLibs": "copyfiles -f ../ballerina-visualizer/build/*.js resources/jslibs && copyfiles -f ../trace-visualizer/build/*.js resources/jslibs" }, "dependencies": { - "@ai-sdk/amazon-bedrock": "4.0.83", - "@ai-sdk/anthropic": "3.0.64", - "@ai-sdk/google-vertex": "4.0.94", + "@ai-sdk/amazon-bedrock": "4.0.67", + "@ai-sdk/anthropic": "3.0.48", + "@ai-sdk/google-vertex": "4.0.66", "@iarna/toml": "2.2.5", "@types/lodash": "4.14.200", "@vscode/test-electron": "2.5.2", @@ -1469,8 +1468,7 @@ "vscode-uri": "3.0.8", "xml-js": "1.6.11", "xstate": "4.38.3", - "zod": "4.1.8", - "yaml": "2.8.0" + "zod": "4.1.8" }, "devDependencies": { "@sentry/webpack-plugin": "1.20.1", diff --git a/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts b/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts index 269e4508bb..843fe5307c 100644 --- a/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts +++ b/workspaces/ballerina/ballerina-extension/src/RPCLayer.ts @@ -19,7 +19,7 @@ import { WebviewView, WebviewPanel, window } from 'vscode'; import { Messenger } from 'vscode-messenger'; import { StateMachine } from './stateMachine'; -import { stateChanged, getVisualizerLocation, VisualizerLocation, projectContentUpdated, aiStateChanged, sendAIStateEvent, popupStateChanged, getPopupVisualizerState, PopupVisualizerLocation, breakpointChanged, AIMachineEventType, ArtifactData, onArtifactUpdatedNotification, onArtifactUpdatedRequest, currentThemeChanged, AIMachineSendableEvent, checkpointCaptured, CheckpointCapturedPayload, promptUpdated, approvalOverlayState, ApprovalOverlayState, onIdentifierUpdated, ProjectStructureArtifactResponse, runningServicesChanged, RunningServiceInfo } from '@wso2/ballerina-core'; +import { stateChanged, getVisualizerLocation, VisualizerLocation, projectContentUpdated, aiStateChanged, sendAIStateEvent, popupStateChanged, getPopupVisualizerState, PopupVisualizerLocation, breakpointChanged, AIMachineEventType, ArtifactData, onArtifactUpdatedNotification, onArtifactUpdatedRequest, currentThemeChanged, AIMachineSendableEvent, checkpointCaptured, CheckpointCapturedPayload, promptUpdated, approvalOverlayState, ApprovalOverlayState, onIdentifierUpdated, ProjectStructureArtifactResponse } from '@wso2/ballerina-core'; import { VisualizerWebview } from './views/visualizer/webview'; import { registerVisualizerRpcHandlers } from './rpc-managers/visualizer/rpc-handler'; import { registerLangClientRpcHandlers } from './rpc-managers/lang-client/rpc-handler'; @@ -47,7 +47,6 @@ import { activeAgentChanged } from '@wso2/ballerina-core'; import { ArtifactsUpdated, ArtifactNotificationHandler } from './utils/project-artifacts-handler'; import { registerMigrateIntegrationRpcHandlers } from './rpc-managers/migrate-integration/rpc-handler'; import { registerPlatformExtRpcHandlers } from './rpc-managers/platform-ext/rpc-handler'; -import { MigrationPanelWebview } from './views/migration-panel/webview'; export class RPCLayer { static _messenger: Messenger = new Messenger(); @@ -65,9 +64,6 @@ export class RPCLayer { window.onDidChangeActiveColorTheme((theme) => { RPCLayer._messenger.sendNotification(currentThemeChanged, { type: 'webview', webviewType: VisualizerWebview.viewType }, theme.kind); }); - } else if (isMigrationPanel(webViewPanel)) { - // Migration panel is a WebviewPanel but does not use the AI state machine. - RPCLayer._messenger.registerWebviewPanel(webViewPanel as WebviewPanel); } else { RPCLayer._messenger.registerWebviewView(webViewPanel as WebviewView); AIStateMachine.service().onTransition((state) => { @@ -186,10 +182,6 @@ function isWebviewPanel(webview: WebviewPanel | WebviewView): boolean { return title === VisualizerWebview.webviewTitle; } -function isMigrationPanel(webview: WebviewPanel | WebviewView): boolean { - return 'reveal' in webview && webview.title === "Migration Assistant"; -} - export function notifyCurrentWebview() { RPCLayer._messenger.sendNotification(projectContentUpdated, { type: 'webview', webviewType: VisualizerWebview.viewType }, true); } @@ -214,12 +206,8 @@ export function notifyApprovalOverlayState(state: ApprovalOverlayState) { RPCLayer._messenger.sendNotification(approvalOverlayState, { type: 'webview', webviewType: AiPanelWebview.viewType }, state); } -export function notifyRunningServicesChanged(services: RunningServiceInfo[]) { - RPCLayer._messenger.sendNotification(runningServicesChanged, { type: 'webview', webviewType: AiPanelWebview.viewType }, services); -} - -export function notifyOnIdentifierUpdated(artifacts: ProjectStructureArtifactResponse[]) { - RPCLayer._messenger.sendNotification(onIdentifierUpdated, { type: 'webview', webviewType: VisualizerWebview.viewType }, artifacts); +export function notifyOnIdentifierUpdated(response: ProjectStructureArtifactResponse[]) { + RPCLayer._messenger.sendNotification(onIdentifierUpdated, { type: 'webview', webviewType: VisualizerWebview.viewType }, response); } export function sendAgentChangedNotification(agentName: string) { diff --git a/workspaces/ballerina/ballerina-extension/src/core/extension.ts b/workspaces/ballerina/ballerina-extension/src/core/extension.ts index 0a6341f132..ba4e62b2ca 100644 --- a/workspaces/ballerina/ballerina-extension/src/core/extension.ts +++ b/workspaces/ballerina/ballerina-extension/src/core/extension.ts @@ -2149,8 +2149,8 @@ export class BallerinaExtension { debug("[AUTO_DETECT] Windows platform detected, using cmd.exe to run .bat files"); // On Windows, use cmd.exe to run .bat files execOptions.shell = true; - response = spawnSync('cmd.exe', ['/c', quoteShellPath(this.ballerinaCmd), ...args], execOptions); - debug(`[AUTO_DETECT] Windows command executed: cmd.exe /c ${quoteShellPath(this.ballerinaCmd)} ${args.join(' ')}`); + response = spawnSync('cmd.exe', ['/c', this.ballerinaCmd, ...args], execOptions); + debug(`[AUTO_DETECT] Windows command executed: cmd.exe /c ${this.ballerinaCmd} ${args.join(' ')}`); } else if (isWSL()) { debug("[AUTO_DETECT] WSL environment detected"); // In WSL, try to use native 'bal' command first @@ -2165,8 +2165,8 @@ export class BallerinaExtension { if (this.ballerinaCmd.endsWith('.bat')) { // Fallback to .bat file with shell execution execOptions.shell = true; - response = spawnSync(quoteShellPath(this.ballerinaCmd), args, execOptions); - debug(`[AUTO_DETECT] WSL command executed: ${quoteShellPath(this.ballerinaCmd)} ${args.join(' ')}`); + response = spawnSync(this.ballerinaCmd, args, execOptions); + debug(`[AUTO_DETECT] WSL command executed: ${this.ballerinaCmd} ${args.join(' ')}`); } else { // Use the configured command response = spawnSync(this.ballerinaCmd, args, execOptions); diff --git a/workspaces/ballerina/ballerina-extension/src/extension.ts b/workspaces/ballerina/ballerina-extension/src/extension.ts index 831455bdf7..50f23a581b 100644 --- a/workspaces/ballerina/ballerina-extension/src/extension.ts +++ b/workspaces/ballerina/ballerina-extension/src/extension.ts @@ -36,7 +36,6 @@ import { activate as activateLibraryBrowser } from './features/library-browser'; import { activate as activateBIFeatures } from './features/bi'; import { activate as activateERDiagram } from './views/persist-layer-diagram'; import { activateAiPanel } from './views/ai-panel'; -import { activateMigrationPanel } from './views/migration-panel'; import { debug, handleResolveMissingDependencies, log } from './utils'; import { activateUriHandlers } from './utils/uri-handlers'; import { StateMachine } from './stateMachine'; @@ -46,13 +45,11 @@ import { extension } from './BalExtensionContext'; import { ExtendedClientCapabilities } from '@wso2/ballerina-core'; import { RPCLayer } from './RPCLayer'; import { activateAIFeatures } from './features/ai/activator'; -import { runningServicesManager } from './features/ai/agent/tools/running-service-manager'; import { activateTryItCommand } from './features/tryit/activator'; import { activate as activateNPFeatures } from './features/natural-programming/activator'; import { activateAgentChatPanel } from './views/agent-chat/activate'; import { activateTracing } from './features/tracing'; import { activateICP } from './features/icp'; -import { onWizardChatNotify, setWizardProjectRoot, runWizardMigrationEnhancement, abortMigrationAgent, openMigratedProject, isAIAuthenticated, signInForAI } from './features/ai/migration/orchestrator'; let langClient: ExtendedLangClient; export let isPluginStartup = true; @@ -129,25 +126,16 @@ export async function activate(context: ExtensionContext) { extension.context = context; // Init RPC Layer methods RPCLayer.init(); - + // Wait for the ballerina extension to be ready await StateMachine.initialize(); - + // Then return the ballerina extension context - return { - ballerinaExtInstance: extension.ballerinaExtInstance, + return { + ballerinaExtInstance: extension.ballerinaExtInstance, projectPath: StateMachine.context().projectPath, VisualizerWebview, - BallerinaExtensionState, - migration: { - setWizardProjectRoot, - wizardEnhancementReady: runWizardMigrationEnhancement, - abortAgent: abortMigrationAgent, - openMigratedProject, - onChatNotify: onWizardChatNotify, - isAIAuthenticated, - signInForAI, - }, + BallerinaExtensionState }; } @@ -214,9 +202,6 @@ export async function activateBallerina(): Promise { //activate ai panel activateAiPanel(ballerinaExtInstance); - // Activate migration enhancement panel - activateMigrationPanel(ballerinaExtInstance); - // Activate AI features activateAIFeatures(ballerinaExtInstance); @@ -306,14 +291,12 @@ async function updateCodeServerConfig() { await config.update('enableRunFast', true); } -export async function deactivate(): Promise { +export function deactivate(): Thenable | undefined { debug('Deactive the Ballerina VS Code extension.'); - await runningServicesManager.dispose(); - if (!langClient) { return; } extension.ballerinaExtInstance.telemetryReporter.dispose(); - await langClient.stop(); + return langClient.stop(); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/AgentExecutor.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/AgentExecutor.ts index 84e5e773a8..298b4e1e90 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/AgentExecutor.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/AgentExecutor.ts @@ -17,7 +17,7 @@ */ import { AICommandExecutor, AICommandConfig, AIExecutionResult } from '../executors/base/AICommandExecutor'; -import { Command, GenerateAgentCodeRequest, ProjectSource, ExecutionContext, SemanticDiff, ReviewModeData, PROJECT_KIND, LoginMethod } from '@wso2/ballerina-core'; +import { Command, GenerateAgentCodeRequest, ProjectSource, ExecutionContext, SemanticDiff, ReviewModeData, PROJECT_KIND } from '@wso2/ballerina-core'; import { StateMachine } from '../../../stateMachine'; import { ModelMessage, stepCountIs, streamText, TextStreamPart } from 'ai'; import { getAnthropicClient, getProviderCacheControl, addCacheControlToMessages, ANTHROPIC_SONNET_4 } from '../utils/ai-client'; @@ -31,20 +31,13 @@ import { integrateCodeToWorkspace } from './utils'; import { getWorkspaceTomlValues } from '../../../utils'; import { StreamContext } from './stream-handlers/stream-context'; import { checkCompilationErrors } from './tools/diagnostics-utils'; -import { updateAndSaveChat, calculateTotalCost } from '../utils/events'; +import { updateAndSaveChat } from '../utils/events'; import { chatStateStorage } from '../../../views/ai-panel/chatStateStorage'; import * as path from 'path'; import { approvalViewManager } from '../state/ApprovalViewManager'; -import { - buildContextManagementOptions, - buildBedrockContextManagementOptions, - detectAppliedCompaction, - estimateFloorTokens, - extractCompactionSummary, - stripAnalysisFromCompactionBlocks, - COMPACTION_BLOCK_PREFIX, -} from '@wso2/copilot-utilities/context-management'; -import { getLoginMethod } from '../../../utils/ai/auth'; +import { compactionManager } from '../compaction-manager'; +import { CompactionGuard } from './compaction/CompactionGuard'; +import { contextExhausted } from './compaction/contextExhausted'; import { sendTelemetryEvent, sendTelemetryException, @@ -59,48 +52,8 @@ import { getHashedProjectId } from "../../telemetry/common/project-id"; import { workspace } from 'vscode'; import { runningServicesManager } from './tools/running-service-manager'; - const RESERVED_OUTPUT_TOKENS = 8_192; -/** - * Tracks threads that have already received a compaction_disabled warning this session. - * Keyed by `${projectRootPath}:${threadId}`. Cleared on chat clear. - */ -const compactionDisabledWarnedThreads = new Set(); - -/** Called by clearChat to reset the warned state for a workspace. */ -export function clearCompactionDisabledWarning(projectRootPath: string, threadId: string): void { - compactionDisabledWarnedThreads.delete(`${projectRootPath}:${threadId}`); -} - -function supportsCompaction(loginMethod: LoginMethod): boolean { - return loginMethod === LoginMethod.ANTHROPIC_KEY - || loginMethod === LoginMethod.BI_INTEL - || loginMethod === LoginMethod.VERTEX_AI - || loginMethod === LoginMethod.AWS_BEDROCK; -} - -function buildCompactionProviderOptions(loginMethod: LoginMethod, floorTokens: number) { - if (!supportsCompaction(loginMethod)) { return undefined; } - const config = { estimatedFloorTokens: floorTokens }; - const options = loginMethod === LoginMethod.AWS_BEDROCK - ? buildBedrockContextManagementOptions(config) - : buildContextManagementOptions(config); - return options ?? undefined; -} - -function warnCompactionDisabledOnce(projectRootPath: string, eventHandler: (e: any) => void): void { - const warnKey = `${projectRootPath}:default`; - if (!compactionDisabledWarnedThreads.has(warnKey)) { - compactionDisabledWarnedThreads.add(warnKey); - eventHandler({ type: 'compaction_disabled' }); - } -} - -function usesContentBasedCompactionDetection(loginMethod: LoginMethod): boolean { - return loginMethod === LoginMethod.AWS_BEDROCK; -} - /** Estimate character length of a message's content for proportional token breakdown. */ function msgCharLen(msg: ModelMessage): number { return JSON.stringify(msg.content).length; @@ -110,10 +63,6 @@ function msgCharLen(msg: ModelMessage): number { * Estimates per-category token breakdown by scaling character-based proportions to the * actual API-reported inputTokens total. The grand total is always exact; per-category * values are ~75-85% accurate. - * - * codebaseCharsPerTurn: char length of the codebase structure block injected as the first - * content block of each user message. Multiplied by user message count to estimate total - * file content across the full conversation history. */ function computeTokenBreakdown( baseMessages: ModelMessage[], @@ -121,8 +70,7 @@ function computeTokenBreakdown( accToolCallChars: number, accToolResultChars: number, inputTokens: number, - codebaseCharsPerTurn: number, -): { systemInstructions: number; toolDefinitions: number; reservedOutput: number; files: number; messages: number; toolResults: number } { +): { systemInstructions: number; toolDefinitions: number; reservedOutput: number; messages: number; toolResults: number } { const systemChars = baseMessages.filter(m => m.role === 'system').reduce((s, m) => s + msgCharLen(m), 0); const baseConvChars = baseMessages.filter(m => m.role === 'user' || m.role === 'assistant').reduce((s, m) => s + msgCharLen(m), 0); const baseToolChars = baseMessages.filter(m => m.role === 'tool').reduce((s, m) => s + msgCharLen(m), 0); @@ -132,17 +80,11 @@ function computeTokenBreakdown( const toolDefsChars = JSON.stringify(tools ?? {}).length; const totalChars = systemChars + convChars + toolChars + toolDefsChars || 1; - // Estimate total file content chars: codebase block appears in every user message turn - const userMsgCount = baseMessages.filter(m => m.role === 'user').length; - const totalCodebaseChars = Math.min(codebaseCharsPerTurn * userMsgCount, convChars); - const pureConvChars = convChars - totalCodebaseChars; - const systemInstructions = Math.round(inputTokens * systemChars / totalChars); - const files = Math.round(inputTokens * totalCodebaseChars / totalChars); - const messages = Math.round(inputTokens * pureConvChars / totalChars); + const messages = Math.round(inputTokens * convChars / totalChars); const toolResults = Math.round(inputTokens * toolChars / totalChars); - const toolDefinitions = Math.max(0, inputTokens - systemInstructions - files - messages - toolResults); - return { systemInstructions, toolDefinitions, reservedOutput: RESERVED_OUTPUT_TOKENS, files, messages, toolResults }; + const toolDefinitions = Math.max(0, inputTokens - systemInstructions - messages - toolResults); + return { systemInstructions, toolDefinitions, reservedOutput: RESERVED_OUTPUT_TOKENS, messages, toolResults }; } /** @@ -215,9 +157,6 @@ async function determineAffectedPackages( * - Plan approval workflow (via ApprovalManager in TaskWrite tool) */ export class AgentExecutor extends AICommandExecutor { - /** Tracks in-flight tool-call start times keyed by toolCallId for duration logging. */ - private readonly _pendingToolCalls = new Map(); - constructor(config: AICommandConfig) { super(config); } @@ -266,21 +205,25 @@ export class AgentExecutor extends AICommandExecutor { workingDirectory: workspaceId, }; - // Resolve model and login method - const loginMethod = await getLoginMethod(); + // Resolve model ONCE — reused for both agent streaming and compaction (M02) const model = await getAnthropicClient(ANTHROPIC_SONNET_4); - const userMessageContent = getUserPrompt(params, tempProjectPath, projects); + // Bind the authenticated model to the compaction manager + compactionManager.bindModel(model); - // Estimate fixed overhead (system prompt + codebase) to decide if compaction is viable - const systemPromptText = getSystemPrompt(projects, params.operationType); - const floorTokens = estimateFloorTokens(systemPromptText, JSON.stringify(userMessageContent)); + const userMessageContent = getUserPrompt(params, tempProjectPath, projects); - const projectRootPath = this.config.executionContext.workspacePath || this.config.executionContext.projectPath || ''; - const providerOptions = buildCompactionProviderOptions(loginMethod, floorTokens); - if (supportsCompaction(loginMethod) && providerOptions === undefined) { - warnCompactionDisabledOnce(projectRootPath, this.config.eventHandler); - } + // PRE-TURN compaction: compact if context is already above threshold + // failures are handled gracefully inside checkAndCompact (returns without throwing) + // abortSignal ensures the summarization LLM call is also cancelled on user abort + await compactionManager.checkAndCompact( + workspaceId, + threadId, + projectState, + this.config.abortController.signal, + this.config.eventHandler, + [ { role: "user", content: userMessageContent } ] + ); // 3. Add generation to chat storage (if enabled) this.addGeneration(params.usecase, { @@ -310,14 +253,9 @@ export class AgentExecutor extends AICommandExecutor { }, ]; - // Accumulator for token usage from tool-internal LLM calls (e.g. Haiku filtering) - // These are separate API calls NOT included in the main agent loop's totalUsage - const toolModelUsage: Record = {}; - // Create tools const tools = createToolRegistry({ eventHandler: this.config.eventHandler, - toolModelUsage, tempProjectPath, modifiedFiles, allModifiedFiles, @@ -326,7 +264,6 @@ export class AgentExecutor extends AICommandExecutor { projectRootPath: this.config.executionContext.workspacePath || this.config.executionContext.projectPath || '', generationId: this.config.generationId, threadId: 'default', - migrationSourcePath: this.config.toolOptions?.migrationSourcePath, runningServices: runningServicesManager, webSearchEnabled: params.webSearchEnabled ?? false, ctx: this.config.executionContext, @@ -336,34 +273,46 @@ export class AgentExecutor extends AICommandExecutor { let accToolCallChars = 0; let accToolResultChars = 0; - // === SERVER-SIDE COMPACTION STATE === - // Primary detection: providerMetadata on text-start (Anthropic/BI_INTEL/Vertex). - // Fallback detection: content-based ( prefix) for Bedrock, which emits - // bare text-start events with no providerMetadata. - const useContentBasedDetection = usesContentBasedCompactionDetection(loginMethod); - let isCompactionBlock = false; - let compactionContent = ''; - let cleanedCompactionSummary: string | undefined; - - // Stream LLM response with server-side context management - const { fullStream, response, usage, totalUsage } = streamText({ + // === MID-STREAM COMPACTION GUARD === + // Watches actual inputTokens between steps and compacts when threshold is reached. + // Uses 80% of the context window as the mid-stream trigger point. + const compactionGuard = new CompactionGuard({ + engine: compactionManager.getEngine(), + tokenThreshold: Math.floor(200_000 * 0.80), // 160K tokens = 80% of context window + maxCompactionAttempts: 3, + preserveRecentMessageCount: 6, // Keep last 3 tool-call + tool-result pairs + eventHandler: this.config.eventHandler, + originalUserMessage: Array.isArray(userMessageContent) + ? userMessageContent.map((c: any) => c.text || '').join('\n') + : String(userMessageContent), + projectState, + abortSignal: this.config.abortController.signal, + persistCallback: (compactedMessages, metadata) => + compactionManager.persistMidStreamCompaction( + workspaceId, + threadId, + this.config.generationId, + compactedMessages, + metadata + ), + }); + + // Stream LLM response with mid-stream compaction and dual stop conditions + const { fullStream, response, totalUsage } = streamText({ model, maxOutputTokens: 8192, temperature: 0, messages: allMessages, tools, abortSignal: this.config.abortController.signal, - providerOptions: providerOptions as any, - // Strip blocks from compaction entries before each subsequent step - // to avoid re-sending thousands of reasoning tokens. - // Also apply incremental cache control to the last message so Anthropic caches the + // MID-STREAM COMPACTION + PROMPT CACHING: compact if needed, then apply + // incremental cache control to the last message so Anthropic caches the // growing conversation history on each step. - prepareStep: async ({ messages: stepMessages }) => { - if (cleanedCompactionSummary) { - stripAnalysisFromCompactionBlocks(stepMessages); - } - return { messages: addCacheControlToMessages({ messages: stepMessages, model }) }; + prepareStep: async ({ steps, stepNumber, messages }) => { + const compacted = await compactionGuard.maybeCompact({ steps, stepNumber, messages }); + const resolvedMessages = compacted ? compacted.messages : messages; + return { messages: addCacheControlToMessages({ messages: resolvedMessages, model }) }; }, // Emit per-step token usage for context usage widget + observability @@ -372,18 +321,6 @@ export class AgentExecutor extends AICommandExecutor { accToolCallChars += JSON.stringify(step.toolCalls ?? []).length; accToolResultChars += JSON.stringify(step.toolResults ?? []).length; - console.log( - `[AgentExecutor] Step ${step.stepNumber} complete: ` + - `${step.usage?.inputTokens ?? 0} input tokens, ` + - `finishReason: ${step.finishReason}` - ); - - // Detect tool-use clearing (no mid-stream signal for this edit type) - const appliedCompaction = detectAppliedCompaction(step.providerMetadata); - if (appliedCompaction?.clearedToolUses) { - console.log(`[AgentExecutor] Server cleared ${appliedCompaction.clearedToolUses} tool uses`); - } - // Persist partial modelMessages after each step so chat is recoverable mid-stream const stepMessages = step.response?.messages ?? []; if (stepMessages.length > 0) { @@ -411,19 +348,19 @@ export class AgentExecutor extends AICommandExecutor { ); this.config.eventHandler({ type: "usage_metrics", - model: ANTHROPIC_SONNET_4, usage: { inputTokens, - cacheCreationInputTokens: cacheWriteTokens, - cacheReadInputTokens: cacheReadTokens, - outputTokens, + cacheCreationInputTokens: (step.usage as any).cacheCreationInputTokens || 0, + cacheReadInputTokens: (step.usage as any).cacheReadInputTokens || 0, + outputTokens: step.usage.outputTokens || 0, }, - breakdown: computeTokenBreakdown(allMessages, tools, accToolCallChars, accToolResultChars, inputTokens, (userMessageContent[0] as any)?.text?.length ?? 0), + breakdown: computeTokenBreakdown(allMessages, tools, accToolCallChars, accToolResultChars, inputTokens), }); } }, - stopWhen: [stepCountIs(50)], + // DUAL STOP CONDITIONS: step limit OR context exhaustion + stopWhen: [stepCountIs(50), contextExhausted(compactionGuard)], }); // Send start event to frontend @@ -443,74 +380,17 @@ export class AgentExecutor extends AICommandExecutor { ctx: this.config.executionContext, generationStartTime, projectId, - toolModelUsage, }; // Process stream events - NATIVE V6 PATTERN try { for await (const part of fullStream) { - // Handle compaction block detection inline (text-start/text-delta) - if (part.type === 'text-start') { - const isCompaction = (part as any).providerMetadata?.anthropic?.type === 'compaction'; - if (isCompaction) { - isCompactionBlock = true; - compactionContent = ''; - this.config.eventHandler({ type: 'compaction_start' }); - } else { - if (isCompactionBlock) { - // Compaction block just ended — flush it - isCompactionBlock = false; - const summary = extractCompactionSummary(compactionContent); - cleanedCompactionSummary = summary || compactionContent; - this.config.eventHandler({ type: 'compaction_end', summary: summary ?? undefined }); - // Reset context widget to near-zero after compaction - this.config.eventHandler({ - type: 'usage_metrics', - usage: { inputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0, outputTokens: 0 }, - }); - // Inline notice before the continuing response - this.config.eventHandler({ - type: 'content_block', - content: 'Context compacted — key context preserved, conversation continues below.', - }); - } - // Normal text-start: emit paragraph break - this.config.eventHandler({ type: 'content_block', content: ' \n' }); - } - continue; - } - - if (part.type === 'text-delta') { - if (isCompactionBlock) { - compactionContent += part.text; - } else if (useContentBasedDetection && !isCompactionBlock && compactionContent === '' && part.text.trimStart().startsWith(COMPACTION_BLOCK_PREFIX)) { - // Bedrock: no providerMetadata on text-start, detect via content - isCompactionBlock = true; - compactionContent = part.text; - this.config.eventHandler({ type: 'compaction_start' }); - } else { - this.config.eventHandler({ type: 'content_block', content: part.text }); - } - continue; - } - await this.handleStreamPart(part, streamContext); } - // Flush compaction block if still open at stream end (e.g. compaction was last block) - if (isCompactionBlock) { - isCompactionBlock = false; - const summary = extractCompactionSummary(compactionContent); - cleanedCompactionSummary = summary || compactionContent; - this.config.eventHandler({ type: 'compaction_end', summary: summary ?? undefined }); - this.config.eventHandler({ - type: 'usage_metrics', - usage: { inputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0, outputTokens: 0 }, - }); - this.config.eventHandler({ - type: 'content_block', - content: 'Context compacted — key context preserved.', - }); + // Check if context was exhausted mid-stream and update context BEFORE handleStreamFinish logic + if (compactionGuard.lastCompactionFailed) { + streamContext.compactionFailedMidStream = true; } // Check if abort was called after stream completed @@ -575,14 +455,6 @@ Generation stopped by user. The last in-progress task was not saved. Files have } ); - // Notify caller with partial messages (wizard migration history persistence) - if (this.config.onMessagesAvailable) { - this.config.onMessagesAvailable( - [{ role: "user", content: streamContext.userMessageContent }, ...partialLLMMessages], - 'aborted' - ); - } - // Note: Abort event is sent by base class handleExecutionError() } @@ -590,6 +462,21 @@ Generation stopped by user. The last in-progress task was not saved. Files have throw error; } + // Update token estimation context with actual total usage + try { + const resolvedUsage = await totalUsage; + if (resolvedUsage) { + const toolDefinitionsChars = JSON.stringify(tools).length; + compactionManager.updateTokenContext( + resolvedUsage.inputTokens ?? 0, + Math.ceil(getSystemPrompt(projects, params.operationType).length / 4), + Math.ceil(toolDefinitionsChars / 4) // Dynamic estimate for tool definitions + ); + } + } catch (usageError) { + console.warn('[AgentExecutor] Could not retrieve usage for token context update:', usageError); + } + return { tempProjectPath, modifiedFiles, @@ -600,17 +487,9 @@ Generation stopped by user. The last in-progress task was not saved. Files have throw error; } - console.error("[AgentExecutor] Non-abort error in execute():", error); - - // Abort the controller so that any in-flight tool executions - // managed by the AI SDK are cancelled and stop emitting events. - if (!this.config.abortController.signal.aborted) { - this.config.abortController.abort(); - } - this.config.eventHandler({ type: "error", - content: getErrorMessage(error) + content: "An error occurred during agent execution. Please check the logs for details." }); // For other errors, return result with error @@ -619,6 +498,9 @@ Generation stopped by user. The last in-progress task was not saved. Files have modifiedFiles, error: error as Error, }; + } finally { + // Stop all services started during this agent loop + runningServicesManager.stopAll(); } } @@ -630,6 +512,20 @@ Generation stopped by user. The last in-progress task was not saved. Files have context: StreamContext ): Promise { switch (part.type) { + case "text-delta": + context.eventHandler({ + type: "content_block", + content: part.text + }); + break; + + case "text-start": + context.eventHandler({ + type: "content_block", + content: " \n" + }); + break; + case "error": const error = part.error instanceof Error ? part.error : new Error(String(part.error)); await this.handleStreamError(error, context); @@ -639,31 +535,8 @@ Generation stopped by user. The last in-progress task was not saved. Files have await this.handleStreamFinish(context); break; - case "tool-call": - if (this.config.debugLogger) { - this._pendingToolCalls.set((part as any).toolCallId, Date.now()); - } - break; - - case "tool-result": - if (this.config.debugLogger) { - const startMs = this._pendingToolCalls.get((part as any).toolCallId); - if (startMs !== undefined) { - const durationMs = Date.now() - startMs; - const rawResult = (part as any).result; - const raw = typeof rawResult === "string" - ? rawResult - : rawResult === undefined - ? "" - : JSON.stringify(rawResult) ?? ""; - this.config.debugLogger.logToolCall((part as any).toolName, durationMs, raw); - this._pendingToolCalls.delete((part as any).toolCallId); - } - } - break; - default: - // All other stream part types (step-finish, etc.) are handled by the SDK. + // Tool calls/results handled automatically by SDK break; } } @@ -746,6 +619,18 @@ Generation stopped by user. The last in-progress task was not saved. Files have const assistantMessages = finalResponse.messages || []; const tempProjectPath = context.ctx.tempProjectPath!; + // Check if mid-stream compaction forcefully stopped the model + if (context.compactionFailedMidStream) { + assistantMessages.push({ + role: 'assistant', + content: `\n\n> **Notice:** The context window limit was reached mid-task, and the generation was paused cleanly. Please review the current state and issue a new prompt to continue where I left off.` + }); + context.eventHandler({ + type: 'content_block', + content: `\n\n> **Notice:** The context window limit was reached mid-task, and the generation was paused cleanly. Please review the current state and issue a new prompt to continue where I left off.` + }); + } + // Run final diagnostics const finalDiagnostics = await checkCompilationErrors(tempProjectPath); context.eventHandler({ @@ -762,28 +647,16 @@ Generation stopped by user. The last in-progress task was not saved. Files have const totalTokenUsage = await context.totalUsage; const inputTokens = totalTokenUsage.inputTokens || 0; const outputTokens = totalTokenUsage.outputTokens || 0; + const totalTokens = totalTokenUsage.totalTokens || 0; const totalCacheRead = totalTokenUsage.inputTokenDetails?.cacheReadTokens || 0; const totalCacheWrite = totalTokenUsage.inputTokenDetails?.cacheWriteTokens || 0; - // Aggregate tool-internal LLM usage across all models - const toolTokens = Object.values(context.toolModelUsage).reduce( - (acc, u) => ({ input: acc.input + u.inputTokens, output: acc.output + u.outputTokens }), - { input: 0, output: 0 } - ); - - const totalCost = calculateTotalCost( - ANTHROPIC_SONNET_4, - { inputTokens, outputTokens, cacheReadTokens: totalCacheRead, cacheWriteTokens: totalCacheWrite }, - context.toolModelUsage - ); - console.log('[AgentExecutor] Generation complete — token usage:', { input: inputTokens, output: outputTokens, + total: totalTokens, cacheRead: totalCacheRead, cacheWrite: totalCacheWrite, cacheRatio: `${inputTokens > 0 ? (totalCacheRead / inputTokens * 100).toFixed(1) : '0'}%`, - toolModelUsage: context.toolModelUsage, - cost: `$${totalCost.toFixed(4)}`, }); // Send telemetry for generation complete @@ -794,57 +667,42 @@ Generation stopped by user. The last in-progress task was not saved. Files have { 'message.id': context.messageId, 'project.id': context.projectId, + 'generation.modified_files_count': context.modifiedFiles.length.toString(), 'generation.start_time': context.generationStartTime.toString(), 'generation.end_time': generationEndTime.toString(), 'plan_mode': isPlanModeEnabled.toString(), - }, - { - 'tokens.input': inputTokens, - 'tokens.output': outputTokens, - 'tokens.cache_read': totalCacheRead, - 'tokens.cache_creation': totalCacheWrite, - 'tokens.tool.input': toolTokens.input, - 'tokens.tool.output': toolTokens.output, - 'generation.modified_files_count': context.modifiedFiles.length, - 'project.files_after': finalProjectMetrics.fileCount, - 'project.lines_after': finalProjectMetrics.lineCount, - 'cost.total': totalCost, + 'project.files_after': finalProjectMetrics.fileCount.toString(), + 'project.lines_after': finalProjectMetrics.lineCount.toString(), + 'tokens.input': inputTokens.toString(), + 'tokens.output': outputTokens.toString(), + 'tokens.total': totalTokens.toString(), } ); // Update chat state storage await this.updateChatState(context, assistantMessages, tempProjectPath); - // Integrate generated code into workspace immediately so user sees changes during review. - // Skip only when the temp path IS the real workspace directory (migration wizard in-place - // editing). A review-continuation run also sets existingTempPath but to a temp dir, so we - // compare resolved paths rather than just checking existingTempPath presence. - const workspaceRoot = context.ctx.workspacePath || context.ctx.projectPath; - const isInPlaceEditing = - !!workspaceRoot && - path.resolve(tempProjectPath) === path.resolve(workspaceRoot); - if (!isInPlaceEditing) { - const workspaceId = workspaceRoot; - const pendingReview = chatStateStorage.getPendingReviewGeneration(workspaceId, 'default'); - if (pendingReview && pendingReview.reviewState.modifiedFiles.length > 0) { - const integrationCtx = { ...context.ctx }; - // In workspace mode, resolve project path from modified files if not set - if (!integrationCtx.projectPath && integrationCtx.workspacePath && pendingReview.reviewState.modifiedFiles.length > 0) { - const firstBalFile = pendingReview.reviewState.modifiedFiles.find(f => f.endsWith('.bal')); - if (firstBalFile) { - const packageName = firstBalFile.split('/')[0]; - if (packageName) { - integrationCtx.projectPath = path.join(integrationCtx.workspacePath, packageName); - StateMachine.context().projectPath = integrationCtx.projectPath; - } + // Integrate generated code into workspace immediately so user sees changes during review + const workspaceId = context.ctx.workspacePath || context.ctx.projectPath; + const pendingReview = chatStateStorage.getPendingReviewGeneration(workspaceId, 'default'); + if (pendingReview && pendingReview.reviewState.modifiedFiles.length > 0) { + const integrationCtx = { ...context.ctx }; + // In workspace mode, resolve project path from modified files if not set + if (!integrationCtx.projectPath && integrationCtx.workspacePath && pendingReview.reviewState.modifiedFiles.length > 0) { + const firstBalFile = pendingReview.reviewState.modifiedFiles.find(f => f.endsWith('.bal')); + if (firstBalFile) { + const packageName = firstBalFile.split('/')[0]; + if (packageName) { + integrationCtx.projectPath = path.join(integrationCtx.workspacePath, packageName); + StateMachine.context().projectPath = integrationCtx.projectPath; } } - await integrateCodeToWorkspace( - pendingReview.reviewState.tempProjectPath!, - new Set(pendingReview.reviewState.modifiedFiles), - integrationCtx - ); } + await integrateCodeToWorkspace( + pendingReview.reviewState.tempProjectPath!, + new Set(pendingReview.reviewState.modifiedFiles), + integrationCtx + ); } // Emit UI events diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/CompactionGuard.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/CompactionGuard.ts new file mode 100644 index 0000000000..1aaf783432 --- /dev/null +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/CompactionGuard.ts @@ -0,0 +1,486 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { CompactionEngine, ProjectStateContext } from '@wso2/copilot-utilities/compaction'; +import { ModelMessage } from 'ai'; +import { ChatNotify } from '@wso2/ballerina-core'; + +/** + * Configuration for CompactionGuard. + */ +export interface CompactionGuardConfig { + /** The shared CompactionEngine instance (from CompactionManager.getEngine()) */ + engine: CompactionEngine; + /** Token count at which mid-stream compaction is triggered (e.g. 160_000 = 80% of 200K) */ + tokenThreshold: number; + /** Maximum compaction attempts per generation before giving up (default: 3) */ + maxCompactionAttempts: number; + /** Number of recent messages to preserve verbatim after split (default: 6) */ + preserveRecentMessageCount: number; + /** Event handler for sending compaction status events to the UI */ + eventHandler: (event: ChatNotify) => void; + /** Original user request content — re-injected after compaction for task continuity */ + originalUserMessage: string; + /** Project state for continuation messages */ + projectState: ProjectStateContext; + /** AbortSignal to propagate cancellation into the summarization LLM call */ + abortSignal?: AbortSignal; + /** + * Called after successful mid-stream compaction to persist the compacted summary + * to chatStateStorage. Receives only compactionResult.compactedMessages (the LLM + * summary pair), NOT the full replacement array with recentMessages — those are + * saved normally when the generation completes via updateGeneration. + */ + persistCallback?: (compactedMessages: ModelMessage[], metadata?: any) => Promise; +} + +function areMessagesEqual(a: ModelMessage[], b: ModelMessage[]): boolean { + if (a.length !== b.length) { return false; } + for (let i = 0; i < a.length; i++) { + if (a[i].role !== b[i].role || a[i].content !== b[i].content) { + return false; + } + } + return true; +} + +/** + * CompactionGuard — mid-stream compaction logic for Vercel AI SDK's `prepareStep` hook. + * + * Called from `prepareStep` before each LLM step. Reads the actual inputTokens from + * the last completed step, and if above threshold, summarizes the old messages and + * returns a compacted replacement message array. + * + * The guard tracks compaction attempts and sets `lastCompactionFailed` when it can no + * longer reduce context, allowing the `contextExhausted` stop condition to halt gracefully. + */ +export class CompactionGuard { + private compactionCount: number = 0; + private _lastCompactionFailed: boolean = false; + /** + * Tracks the compacted message array returned by the last compaction and the + * length of the SDK's accumulated messages at that point. + */ + private lastCompactedMessages: ModelMessage[] | null = null; + private sdkMessagesCountAtLastCompaction: number = 0; + + constructor(private config: CompactionGuardConfig) {} + + /** + * Read by the `contextExhausted` StopCondition to halt generation gracefully. + */ + get lastCompactionFailed(): boolean { + return this._lastCompactionFailed; + } + + /** + * Called from `prepareStep`. Decides whether to compact and performs it. + * + * @returns Replacement `{ messages }` object for prepareStep, or `undefined` to proceed normally. + */ + async maybeCompact(options: { + steps: any[]; + stepNumber: number; + messages: ModelMessage[]; + }): Promise<{ messages: ModelMessage[] } | undefined> { + const { steps, messages } = options; + + // Skip on the very first step — no usage data available yet + if (steps.length === 0) { + return undefined; + } + + // Respect abort signal + if (this.config.abortSignal?.aborted) { + return undefined; + } + + // Build effective messages: if a previous compaction was done, merge the + // compacted base with any new additions the SDK has accumulated since then. + // This prevents the SDK from reverting to its full accumulated history. + const effectiveMessages = this.buildEffectiveMessages(messages); + + // Read actual token count from the most recent completed step. + // usage.inputTokens is the ground truth — it's what the LLM API actually consumed. + const lastStep = steps[steps.length - 1]; + const lastInputTokens: number = lastStep.usage?.inputTokens ?? 0; + + if (lastInputTokens < this.config.tokenThreshold) { + // Even when below threshold, return effectiveMessages if a previous compaction + // shrank the history — otherwise the SDK would silently undo our compaction by + // feeding its full accumulated messages to the next API call. + if (this.lastCompactedMessages && !areMessagesEqual(effectiveMessages, messages)) { + return { messages: effectiveMessages }; + } + return undefined; + } + + // Calculate the exact boundary we would use to split 'old' messages from 'recent' preserved messages + const targetSplitIndex = Math.max(0, effectiveMessages.length - this.config.preserveRecentMessageCount); + const summarizableMessages = effectiveMessages.slice(0, targetSplitIndex); + const summarizableUserMessages = summarizableMessages.filter(m => m.role === 'user').length; + + // If the portion of the chat we are allowed to summarize doesn't contain a sufficient batch of user messages, + // we skip compaction to avoid firing the summarizer on every single turn. This "batches" the summarizations. + const MIN_MESSAGES_TO_BATCH = 3; + if (summarizableUserMessages < MIN_MESSAGES_TO_BATCH) { + console.warn(`[CompactionGuard] Threshold reached (${lastInputTokens} >= ${this.config.tokenThreshold}), but skipping compaction due to insufficient older conversation history (${summarizableUserMessages}/${MIN_MESSAGES_TO_BATCH} summarizable user messages needed for a batch). Allowing generation to continue.`); + return undefined; + } + + console.log( + `[CompactionGuard] Token threshold reached: ${lastInputTokens} >= ${this.config.tokenThreshold} ` + + `(step ${options.stepNumber}, attempt ${this.compactionCount + 1}/${this.config.maxCompactionAttempts})` + ); + + // If we've exhausted all attempts, give up — let contextExhausted stop the generation + if (this.compactionCount >= this.config.maxCompactionAttempts) { + console.error( + `[CompactionGuard] Max compaction attempts (${this.config.maxCompactionAttempts}) reached.` + ); + this._lastCompactionFailed = true; + return undefined; + } + + try { + return await this.performCompaction(effectiveMessages, messages.length); + } catch (error) { + console.error('[CompactionGuard] Mid-stream compaction failed:', error); + this._lastCompactionFailed = true; + this.config.eventHandler({ + type: 'compaction_failed', + reason: error instanceof Error ? error.message : 'Unknown compaction error', + }); + return undefined; + } + } + + /** + * Builds the effective message array from the compacted base + new SDK additions. + * + * The Vercel AI SDK tracks `messages` as: + * initialMessages + allStepResponses (grows monotonically) + */ + private buildEffectiveMessages(sdkMessages: ModelMessage[]): ModelMessage[] { + if (!this.lastCompactedMessages) { + return sdkMessages; + } + const newAdditions = sdkMessages.slice(this.sdkMessagesCountAtLastCompaction); + return [...this.lastCompactedMessages, ...newAdditions]; + } + + /** + * Core compaction: splits messages, summarizes old portion, preserves recent messages. + */ + private async performCompaction( + messages: ModelMessage[], + sdkMessagesCount: number + ): Promise<{ messages: ModelMessage[] }> { + this.config.eventHandler({ type: 'compaction_start' }); + + // === SPLIT: determine boundary between old (to summarize) and recent (to keep) === + const preserveCount = this.config.preserveRecentMessageCount; + const targetSplitIndex = Math.max(0, messages.length - preserveCount); + let cleanSplitIndex = this.findCleanSplitPoint(messages, targetSplitIndex); + + if (cleanSplitIndex === messages.length) { + console.warn('[CompactionGuard] No safe split point found (too few messages to summarize). Treating as compaction failure.'); + this._lastCompactionFailed = true; + this.config.eventHandler({ + type: 'compaction_failed', + reason: 'No safe split point found' + }); + return { messages }; + } + + const oldMessages = messages.slice(0, cleanSplitIndex); + const recentMessages = messages.slice(cleanSplitIndex); + + console.log( + `[CompactionGuard] Split: ${oldMessages.length} messages → summarize, ` + + `${recentMessages.length} messages → preserve verbatim` + ); + + // === SUMMARIZE old messages === + // MID_STREAM_INSTRUCTIONS injected via customInstructions — flows through + // SummarizationService as "## Additional Summarization Instructions from User". + // This keeps a single prompt source of truth (Section 9.6). + const MID_STREAM_INSTRUCTIONS = `## Mid-Stream Compaction Context + +CRITICAL: This compaction is happening MID-TASK. The assistant is in the middle of executing a task and will continue immediately after reading this summary. Prioritize: + +1. **Original User Request**: Include the EXACT user request verbatim +2. **Task Progress**: What has been accomplished vs what remains +3. **Files Modified**: List ALL file paths created, read, or modified +4. **Current State**: What was being worked on at the moment of compaction +5. **Pending Work**: Specific next steps needed to complete the task +6. **Errors**: Any unresolved errors or blockers + +The assistant MUST be able to seamlessly continue the task from this summary alone.`; + + let compactionResult = await this.config.engine.compact(oldMessages, { + mode: 'auto', + projectState: this.config.projectState, + abortSignal: this.config.abortSignal, + customInstructions: MID_STREAM_INSTRUCTIONS, + }); + + if (!compactionResult.success) { + throw new Error('CompactionEngine.compact() returned success: false'); + } + + // === EXTRACT unresolved failures from the compacted portion === + // Tool failures that happened in oldMessages won't be in the verbatim preserved + // window, so the model might blindly repeat the same failing input. We extract + // them deterministically (not relying on LLM summarization) and inject them as + // an explicit warning block. + const failureNotes = this.extractFailureNotes(oldMessages); + + // === BUILD replacement message array === + // Structure: [summary pair] + [failure notes?] + [task reminder] + [recent tool interactions] + + const taskReminderMessages: ModelMessage[] = [ + { + role: 'user' as const, + content: `[Mid-stream compaction occurred. The context was approaching token limits. ` + + `Your conversation history has been compacted. Continue working on the original task below.]\n\n` + + `Original request: ${this.config.originalUserMessage}`, + }, + { + role: 'assistant' as const, + content: 'Understood. I will continue working on the task. Let me pick up where I left off based on the recent context.', + } + ]; + + let compactedMessages: ModelMessage[] = [ + ...compactionResult.compactedMessages, + // Inject unresolved tool failures so the model avoids repeating them + ...(failureNotes + ? [{ + role: 'user' as const, + content: failureNotes, + }] + : []), + // Re-inject original request so the model remembers what it was working on + ...taskReminderMessages, + // Preserved recent messages — verbatim (last N tool interactions) + ...recentMessages, + ]; + + let tokenStatus = await this.config.engine.getTokenStatus(compactedMessages); + let recompactAttempts = 0; + const maxRecompactIterations = 3; + + while (tokenStatus.currentTokens >= this.config.tokenThreshold) { + if (recompactAttempts >= maxRecompactIterations) { + console.warn(`[CompactionGuard] Exceeded max recompaction iterations (${maxRecompactIterations}). Breaking loop.`); + break; + } + + // NEW CHECK: If the summarizer barely shrank the payload (less than 5% reduction), + // it means we hit an unshrinkable static block (like massive codebase files). + // Break immediately. + if (compactionResult.reductionPercentage < 5) { + console.warn(`[CompactionGuard] Only ${compactionResult.reductionPercentage.toFixed(1)}% reduction achieved. Payload is likely unshrinkable static context. Breaking loop.`); + break; + } + + recompactAttempts++; + console.warn(`[CompactionGuard] Built messages exceed threshold (${tokenStatus.currentTokens} >= ${this.config.tokenThreshold}). Re-compacting full content (attempt ${recompactAttempts}).`); + + compactionResult = await this.config.engine.compact(compactedMessages, { + mode: 'auto', + projectState: this.config.projectState, + abortSignal: this.config.abortSignal, + customInstructions: MID_STREAM_INSTRUCTIONS, + }); + + if (!compactionResult.success) { + throw new Error('Fallback CompactionEngine.compact() returned success: false'); + } + + // Do NOT discard supplemental reminders and recent messages + compactedMessages = [ + ...compactionResult.compactedMessages, + ...(failureNotes + ? [{ + role: 'user' as const, + content: failureNotes, + }] + : []), + ...taskReminderMessages, + ...recentMessages, + ]; + tokenStatus = await this.config.engine.getTokenStatus(compactedMessages); + } + + // Persist the summary of old history to chatStateStorage so the next turn + // doesn't need to compact again (eliminates double compaction). + // Non-fatal: if this fails, the in-memory compaction still works for this turn. + if (this.config.persistCallback) { + try { + await this.config.persistCallback(compactionResult.compactedMessages, compactionResult.metadata); + } catch (err) { + console.error('[CompactionGuard] Failed to persist mid-stream compaction to storage:', err); + } + } + + this.compactionCount++; + + // Persist the compacted base so buildEffectiveMessages() can prepend it to + // any new SDK additions on subsequent prepareStep calls. + this.lastCompactedMessages = compactedMessages; + this.sdkMessagesCountAtLastCompaction = sdkMessagesCount; + + this.config.eventHandler({ + type: 'compaction_end', + metadata: compactionResult.metadata, + }); + + console.log( + `[CompactionGuard] Mid-stream compaction #${this.compactionCount} complete. ` + + `Messages: ${messages.length} → ${compactedMessages.length} ` + + `(${compactionResult.reductionPercentage.toFixed(1)}% reduction)` + ); + + return { messages: compactedMessages }; + } + + /** + * Scans the compacted (old) message slice for tool failures that were NOT + * subsequently resolved by a successful call to the same tool on the same target. + * + * Returns a formatted warning string to inject into the replacement messages, or + * null if there are no unresolved failures. This is deterministic — it does not + * rely on the LLM summarization picking up error details. + */ + private extractFailureNotes(messages: ModelMessage[]): string | null { + // Build toolCallId → { toolName, args } from assistant messages + const toolCallMap = new Map }>(); + for (const msg of messages) { + if (msg.role !== 'assistant') { continue; } + const parts = Array.isArray(msg.content) ? msg.content : []; + for (const part of parts as any[]) { + if (part?.type === 'tool-call' && part.toolCallId) { + toolCallMap.set(part.toolCallId, { + toolName: part.toolName ?? '(unknown)', + args: part.args ?? {}, + }); + } + } + } + + // Collect all tool results in order, tagging each as error or success + type ToolEvent = { toolCallId: string; toolName: string; target: string; isError: boolean; errorText: string }; + const events: ToolEvent[] = []; + for (const msg of messages) { + if (msg.role !== 'tool') { continue; } + const parts = Array.isArray(msg.content) ? msg.content : []; + for (const part of parts as any[]) { + if (part?.type !== 'tool-result') { continue; } + const call = toolCallMap.get(part.toolCallId ?? ''); + const toolName: string = part.toolName ?? call?.toolName ?? '(unknown)'; + const args: Record = call?.args ?? {}; + const target = this.extractTarget(toolName, args); + const resultText = typeof part.result === 'string' + ? part.result + : JSON.stringify(part.result ?? ''); + events.push({ + toolCallId: part.toolCallId ?? '', + toolName, + target, + isError: !!part.isError, + errorText: resultText.slice(0, 300), + }); + } + } + + // A failure is "resolved" if the same (toolName, target) pair succeeded later + const resolvedKeys = new Set(); + const unresolvedFailures: ToolEvent[] = []; + for (let i = events.length - 1; i >= 0; i--) { + const key = `${events[i].toolName}::${events[i].target}`; + if (!events[i].isError) { + resolvedKeys.add(key); + } else if (!resolvedKeys.has(key)) { + unresolvedFailures.unshift(events[i]); + } + } + + if (unresolvedFailures.length === 0) { return null; } + + const lines = [ + '[Tool failures from compacted history — these exact inputs previously failed.', + ' Read the current file state before retrying rather than repeating the same call.]', + '', + ]; + for (const f of unresolvedFailures) { + const call = toolCallMap.get(f.toolCallId); + const argSummary = call ? this.formatFailureArgs(f.toolName, call.args) : f.target; + lines.push(`• ${f.toolName}: ${argSummary}`); + lines.push(` Error: ${f.errorText}`); + } + + return lines.join('\n'); + } + + /** Extracts a short human-readable "target" string used for dedup (e.g. file path). */ + private extractTarget(toolName: string, args: Record): string { + const pathKey = ['path', 'filePath', 'file_path', 'target_file', 'file', 'name'] + .find(k => typeof args[k] === 'string'); + return pathKey ? String(args[pathKey]) : toolName; + } + + /** Formats the key failure args for display in the warning block. */ + private formatFailureArgs(toolName: string, args: Record): string { + const target = this.extractTarget(toolName, args); + // For edit tools, also show the old_string that failed to match + const oldStr = args['old_string'] ?? args['old_str'] ?? args['original_snippet']; + if (oldStr && typeof oldStr === 'string') { + const preview = oldStr.replace(/\n/g, '\\n').slice(0, 100); + return `${target} — old_string: "${preview}${oldStr.length > 100 ? '...' : ''}"`; + } + return target; + } + + /** + * Find a clean split point that doesn't break tool-call / tool-result pairs. + * + * Walks backward from targetIndex until a 'user' role message is found. + * User messages are safe split boundaries — they are never mid-tool-call. + * + * Returns `messages.length` to skip compaction if too few messages (<4) remain. + */ + private findCleanSplitPoint(messages: ModelMessage[], targetIndex: number): number { + let index = targetIndex; + + while (index > 0) { + if (messages[index].role === 'user') { + break; + } + index--; + } + + // Don't summarize fewer than 4 messages — not worth the latency + if (index < 4) { + return messages.length; // Skip compaction + } + + return index; + } +} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/contextExhausted.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/contextExhausted.ts new file mode 100644 index 0000000000..a67baf205d --- /dev/null +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/compaction/contextExhausted.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { StopCondition } from 'ai'; +import { CompactionGuard } from './CompactionGuard'; + +/** + * Creates a StopCondition that gracefully stops generation when mid-stream + * compaction has permanently failed and the context cannot be reduced further. + * + * This prevents a context overflow API error from crashing the stream. + * Instead, the agent stops cleanly after completing the current step, + * and the user sees the partial result. + * + * Usage: + * ```typescript + * stopWhen: [stepCountIs(50), contextExhausted(compactionGuard)] + * ``` + * The array form stops when ANY condition returns true. + */ +export function contextExhausted(guard: CompactionGuard): StopCondition { + return ({ steps }) => { + if (!guard.lastCompactionFailed) { + return false; // Compaction is healthy, don't stop + } + + console.warn( + '[contextExhausted] Stopping generation: mid-stream compaction failed and context is near limit. ' + + `Completed ${steps.length} steps before stopping.` + ); + return true; + }; +} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/prompts.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/prompts.ts index 30cd7790bb..8af91e3fa1 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/prompts.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/prompts.ts @@ -21,23 +21,20 @@ import { TASK_WRITE_TOOL_NAME } from "./tools/task-writer"; import { FILE_BATCH_EDIT_TOOL_NAME, FILE_SINGLE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME } from "./tools/text-editor"; import { CONNECTOR_GENERATOR_TOOL } from "./tools/connector-generator"; import { CONFIG_COLLECTOR_TOOL } from "./tools/config-collector"; -import { CLARIFY_TOOL } from "./tools/clarify"; import { TEST_RUNNER_TOOL_NAME } from "./tools/test-runner"; import { getLanglibInstructions } from "../utils/libs/langlibs"; import { formatCodebaseStructure, formatCodeContext } from "./utils"; import { GenerateAgentCodeRequest, OperationType, ProjectSource } from "@wso2/ballerina-core"; import { getRequirementAnalysisCodeGenPrefix, getRequirementAnalysisTestGenPrefix } from "./np/prompts"; import { extractResourceDocumentContent, flattenProjectToFiles } from "../utils/ai-utils"; -import { BALLERINA_RUN_TOOL_NAME } from "./tools/ballerina-run"; -import { BALLERINA_STOP_TOOL_NAME } from "./tools/ballerina-stop"; /** * Generates the system prompt for the design agent */ export function getSystemPrompt(projects: ProjectSource[], op: OperationType): string { - return `You are WSO2 Integrator Copilot, an expert assistant specialized in Ballerina help with relavant integration usecases. You will be helping with designing a solution for user query in a step-by-step manner. + return `You are an expert assistant to help with writing ballerina integrations. You will be helping with designing a solution for user query in a step-by-step manner. -Answer queries related to Ballerina and integrations. If a query is unrelated, politely decline. +Answer queries related to Ballerina integrations. If a query is unrelated to Ballerina, politely decline. tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result. therefore avoid responding using them. # Generation Modes @@ -49,29 +46,41 @@ In the tags, you will see if Plan mode is enabled. When its en Create a very high-level and concise design plan for the given user requirement. ### Step 2: Break Down Into Tasks and Execute + **REQUIRED: Use Task Management** -You must use ${TASK_WRITE_TOOL_NAME} tool to create and manage tasks. +You have access to ${TASK_WRITE_TOOL_NAME} tool to create and manage tasks. This plan will be visible to the user and the execution will be guided on the tasks you create. -- Break down the implementation into specific, actionable tasks. Each task should be concise and high level as they are visible to a very high level user. +- Break down the implementation into specific, actionable tasks. - Each task should have a type. This type will be used to guide the user through the generation proccess. -- Track each task as you work through them and mark tasks as you start and complete them +- Track each task as you work through them +- Mark tasks as you start and complete them +- This ensures you don't miss critical steps +- Each task should be concise and high level as they are visible to a very high level user. During the implementation, you will break them down further as needed and implement them. #### Task Types 1. 'service_design' -- Responsible for creating the listener, service, and its resource function signatures. -- In case of HTTP follow below guidelines, in other services follow according to the trigger. +- Responsible for creating the http listener, service, and its resource function signatures. - The signature should only have path, query, payload, header paramters and the return types. This step should contain types relevant to the service contract as well. - Create resource function signatures with comprehensive return types covering all possible scenarios - In this state, include http:NotImplemented as a union member in the return type of each resource function and return http:NOT_IMPLEMENTED in the body as a placeholder since this will be implemented in the next steps. - Eg: resource function get hello() returns http:NotImplemented { return http:NOT_IMPLEMENTED; } -2. 'implementation' + +2. 'connections_init' +- Responsible for initializing connections/clients +- This step should only contain the Client initialization. +3. 'implementation' - for all the other implementations. Have resource function implementations in its own task. -3. 'execution' -- Responsible for running the code, trying the apis out running tests. -- Include this task only if the user has explicitly asked to do any operations involving execution. Skip it otherwise. +4. 'testing' +- Responsible for writing test cases that cover the core logic of the implementation. +- Include this task only if the user has explicitly asked for tests. Skip it otherwise. + +#### Task Breakdown Example +1. Create the HTTP service contract +2. Create the MYSQL Connection +3. Implement the resource functions **Critical Rules**: - Task management is MANDATORY for all implementations @@ -91,7 +100,7 @@ This plan will be visible to the user and the execution will be guided on the ta - When implementing external API integrations: - First use ${LIBRARY_SEARCH_TOOL} with relevant keywords to discover available libraries - Then use ${LIBRARY_GET_TOOL} to fetch full details for the discovered libraries - - If you think user is refering to an ambiguous API, or internal API, call ${CONNECTOR_GENERATOR_TOOL} to request for the API spec from the user and to generate a connector for it. + - If NO suitable library is found, call ${CONNECTOR_GENERATOR_TOOL} to generate connector from OpenAPI spec - Before marking the task as completed, use ${DIAGNOSTICS_TOOL_NAME} to check for compilation errors and fix them. - Mark task as completed using ${TASK_WRITE_TOOL_NAME} (send ALL tasks, no approval flags) — the agent continues automatically. **IMPORTANT: When marking a task as completed in a message with other tool calls, ${TASK_WRITE_TOOL_NAME} MUST always be the LAST tool call in the message.** - After completing a logical unit of work (a set of related tasks), set **requestReview: true** on the TaskWrite call to let the user review before continuing. Do NOT set this after every single task. @@ -124,12 +133,6 @@ Once compilation is clean and if the project contains test cases, run the tests. ### Step 5: Provide a consise summary Once the code is written and validated, provide a very concise summary of the overall changes made. Avoid adding detailed explanations and NEVER create documentations files via ${FILE_WRITE_TOOL_NAME}. -# Clarifying Questions - -Before starting implementation, use ${CLARIFY_TOOL} to resolve genuine requirement gaps — apply smart defaults where reasonable, but do not silently assume a specific technology when the user's intent or infrastructure determines the right choice. - -Use ${CLARIFY_TOOL} AT MOST ONCE — batch all questions into a single call. In the case of plan mode, you need to call call this tool before first ${TASK_WRITE_TOOL_NAME} call if you have any clarifying questions. - # Code Generation Guidelines When generating Ballerina code strictly follow these syntax and structure guidelines: @@ -141,19 +144,19 @@ When generating Ballerina code strictly follow these syntax and structure guidel - For packages with dots in names, use aliases: \`import org/package.one as one;\` - Treat generated connectors/clients inside the generated folder as submodules. - A submodule MUST BE imported before being used. The import statement should only contain the package name and submodule name. For package my_pkg, folder structure generated/fooApi, the import should be \`import my_pkg.fooApi;\`. +- In the library API documentation, if the service type is specified as generic, adhere to the instructions specified there on writing the service. - For GraphQL service related queries, if the user hasn't specified their own GraphQL Schema, write the proposed GraphQL schema for the user query right after the explanation before generating the Ballerina code. Use the same names as the GraphQL Schema when defining record types. -- Some libaries has instructions fields in their API documentation. Follow those instructions strictly when using those libraries. +- Some libaries has instructions field in their API documentation. Follow those instructions strictly when using those libraries. - You should only generate tests if the user explicitly asks for them in the query. You must use the 'ballerina/test' and whatever services associated when writing tests. Respect the instructions field in ballerina/test library and testGenerationInstruction field in whatever library associated with the service in the library API documentation when writing tests. - For workflow-based requirements involving long-running processes, state management, or orchestration of multiple steps, use the 'ballerina/workflow' module. - When writing tests, use the 'ballerina/test' module and any service-specific test libraries. Respect the instructions field in ballerina/test library and the testGenerationInstruction field in the associated service library API documentation when writing tests. -- Some libraries may contain Readme field. This is generic information about the library. Avoid following links from the readme contents. + ${getLanglibInstructions()} ### Local Connectors - If the codebase structure shows connector modules in generated/moduleName, import using: import packageName.moduleName ## Code Structure -- In WSO2 Integrator, Automation is simply an app with a main method unless user specifically mentions a service. Cron Job kind of requirements are handled in the deployment level for Kubernetes or Integration platform level. - Define required configurables for the query. Use only string, int, decimal, boolean types in configurable variables. Never assign hardcoded default values to configurables. - Initialize any necessary clients with the correct configuration based on the retrieved libraries at the module level (before any function or service declarations). - Implement the main function OR service to address the query requirements. @@ -164,15 +167,14 @@ ${getLanglibInstructions()} - Use dot notation to access a normal function. Use -> to access a remote function or resource function. - Do not use dynamic listener registrations. - Do not write code in a way that requires updating/assigning values of function parameters. -- ALWAYS use two-word camel case all the identifiers (variables, function parameter, resource function parameter, and field names). -- If a type paramter is specified as record {|anydata...;|} which means you can pass any record into that. In those scenarios, Use existing records or declare explict records and pass it to the paramter. -- If the return type refers to a paramter with the type record {|anydata...;|} as the default value, it means it can be assigned to any records. You can decide the structured, declare and use it. +- ALWAYS use two-word camel case all the identifiers (ex- variables, function parameter, resource function parameter, and field names). +- If the return parameter typedesc default value is marked as <> in the given API docs, define a custom record in the code that represents the data structure based on the use case and assign to it. - Whenever you have a Json variable, NEVER access or manipulate Json variables. ALWAYS define a record and convert the Json to that record and use it. - When invoking resource functions from a client, use the correct paths with accessor and parameters (e.g., exampleClient->/path1/["param"]/path2.get(key="value")). - When accessing a field of a record, always assign it to a new variable and use that variable in the next statement. - Avoid long comments in the code. Use // for single line comments. - Always use named arguments when providing values to any parameter (e.g., .get(key="value")). -- Mention types EXPLICITLY in variable declarations and foreach statements. (Avoid var at all costs) +- Mention types EXPLICITLY in variable declarations and foreach statements. - To narrow down a union type(or optional type), always declare a separate variable and then use that variable in the if condition. ## File modifications @@ -206,12 +208,12 @@ When working with Ballerina workspace projects (projects with a root Ballerina.t - You should only Run or write tests if the user explicitly asks to do so. - Providing values to configurables is a runtime task and should only do it before running or executing the tests. - For Config.toml configuration value management, use ${CONFIG_COLLECTOR_TOOL} to request for values. Check the different modes of the tool for various usecases. -- You can call ${BALLERINA_STOP_TOOL_NAME} when you need to restart a service (e.g. after code changes) or when the user explicitly asks to stop it. +- Make sure to stop service once you are done using it. ## Test Runner When running tests: 1. Tell the user what is being tested in one line. -2. Use ${TEST_RUNNER_TOOL_NAME} to run the test suite. Note that you don't have to use ${BALLERINA_RUN_TOOL_NAME} prior to using ${TEST_RUNNER_TOOL_NAME} as the tool will automatically run the app and then run the tests. +2. Use ${TEST_RUNNER_TOOL_NAME} to run the test suite. 3. Only if there are failures or errors, briefly mention what failed and fix them, then re-run. # Web Tools @@ -274,14 +276,10 @@ export function getUserPrompt(params: GenerateAgentCodeRequest, tempProjectPath: } } - const queryParts = [params.usecase]; - if (params.hiddenContext) { - queryParts.push(params.hiddenContext); - } content.push({ type: 'text' as const, text: ` -${queryParts.join('\n\n')} +${params.usecase} ` }); diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/stream-handlers/stream-context.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/stream-handlers/stream-context.ts index 9a31bb2ac5..01cdcb2e5d 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/stream-handlers/stream-context.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/stream-handlers/stream-context.ts @@ -15,7 +15,7 @@ // under the License. import { ExecutionContext, ProjectSource } from "@wso2/ballerina-core"; -import { CopilotEventHandler, ToolModelUsage } from "../../utils/events"; +import { CopilotEventHandler } from "../../utils/events"; import { StreamTextResult } from 'ai'; /** @@ -51,7 +51,4 @@ export interface StreamContext { // Mid-stream compaction status compactionFailedMidStream?: boolean; - - // Accumulated token usage from tool-internal LLM calls, keyed by model name - toolModelUsage: ToolModelUsage; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tool-registry.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tool-registry.ts index 86dce456fb..adce31b469 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tool-registry.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tool-registry.ts @@ -42,23 +42,15 @@ import { createConnectorGeneratorTool, CONNECTOR_GENERATOR_TOOL } from './tools/ import { LIBRARY_SEARCH_TOOL, getLibrarySearchTool } from './tools/library-search'; import { createConfigCollectorTool, CONFIG_COLLECTOR_TOOL } from './tools/config-collector'; import { createTestRunnerTool, TEST_RUNNER_TOOL_NAME } from './tools/test-runner'; -import { - createMigrationSourceListTool, - createMigrationSourceReadTool, - MIGRATION_SOURCE_LIST_TOOL, - MIGRATION_SOURCE_READ_TOOL, -} from './tools/migration-source-reader'; import { createBallerinaRunTool, BALLERINA_RUN_TOOL_NAME } from './tools/ballerina-run'; import { createBallerinaGetLogsTool, BALLERINA_GET_LOGS_TOOL_NAME } from './tools/ballerina-get-logs'; import { createBallerinaStopTool, BALLERINA_STOP_TOOL_NAME } from './tools/ballerina-stop'; import { RunningServicesManager } from './tools/running-service-manager'; import { createHurlTool, HURL_TOOL_NAME } from './tools/hurl-tool'; import { createWebSearchTool, WEB_SEARCH_TOOL_NAME, createWebFetchTool, WEB_FETCH_TOOL_NAME } from './tools/web-tools'; -import { createClarifyTool, CLARIFY_TOOL } from './tools/clarify'; export interface ToolRegistryOptions { eventHandler: CopilotEventHandler; - toolModelUsage: Record; tempProjectPath: string; modifiedFiles: string[]; allModifiedFiles: Set; @@ -67,15 +59,13 @@ export interface ToolRegistryOptions { projectRootPath: string; generationId: string; threadId?: string; - /** Absolute path to the original migration source project (Mule, Tibco, etc.). */ - migrationSourcePath?: string; runningServices: RunningServicesManager; webSearchEnabled: boolean; ctx: ExecutionContext; } export function createToolRegistry(opts: ToolRegistryOptions) { - const { eventHandler, toolModelUsage, tempProjectPath, modifiedFiles, allModifiedFiles, projects, generationType, projectRootPath, generationId, threadId, migrationSourcePath, webSearchEnabled, ctx } = opts; + const { eventHandler, tempProjectPath, modifiedFiles, allModifiedFiles, projects, generationType, projectRootPath, generationId, threadId, webSearchEnabled, ctx } = opts; return { [TASK_WRITE_TOOL_NAME]: createTaskWriteTool( eventHandler, @@ -87,15 +77,13 @@ export function createToolRegistry(opts: ToolRegistryOptions) { ), [LIBRARY_GET_TOOL]: getLibraryGetTool( generationType, - eventHandler, - toolModelUsage + eventHandler ), [LIBRARY_SEARCH_TOOL]: getLibrarySearchTool( eventHandler ), [HEALTHCARE_LIBRARY_PROVIDER_TOOL]: getHealthcareLibraryProviderTool( - eventHandler, - toolModelUsage + eventHandler ), [CONNECTOR_GENERATOR_TOOL]: createConnectorGeneratorTool( eventHandler, @@ -125,17 +113,11 @@ export function createToolRegistry(opts: ToolRegistryOptions) { ), [DIAGNOSTICS_TOOL_NAME]: createDiagnosticsTool(tempProjectPath, eventHandler), [TEST_RUNNER_TOOL_NAME]: createTestRunnerTool(tempProjectPath, eventHandler, modifiedFiles, allModifiedFiles, ctx), - // Migration source tools — registered only when a source project path is available - ...(migrationSourcePath ? { - [MIGRATION_SOURCE_LIST_TOOL]: createMigrationSourceListTool(eventHandler, migrationSourcePath), - [MIGRATION_SOURCE_READ_TOOL]: createMigrationSourceReadTool(eventHandler, migrationSourcePath), - } : {}), [HURL_TOOL_NAME]: createHurlTool(eventHandler), [BALLERINA_RUN_TOOL_NAME]: createBallerinaRunTool(tempProjectPath, opts.runningServices, eventHandler, modifiedFiles, allModifiedFiles, ctx), [BALLERINA_GET_LOGS_TOOL_NAME]: createBallerinaGetLogsTool(opts.runningServices, eventHandler), [BALLERINA_STOP_TOOL_NAME]: createBallerinaStopTool(opts.runningServices, eventHandler), [WEB_SEARCH_TOOL_NAME]: createWebSearchTool(eventHandler, webSearchEnabled), [WEB_FETCH_TOOL_NAME]: createWebFetchTool(eventHandler, webSearchEnabled), - [CLARIFY_TOOL]: createClarifyTool(eventHandler), }; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-run.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-run.ts index 2b8eaf2539..36918202b9 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-run.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-run.ts @@ -23,7 +23,6 @@ import { CopilotEventHandler } from '../../utils/events'; import { extension } from '../../../../BalExtensionContext'; import { RunningServicesManager, spawnProcess, killProcessGroup } from './running-service-manager'; import { DIAGNOSTICS_TOOL_NAME } from './diagnostics'; -import { resolvePackageBasePath } from './path-utils'; import { getRunCommand } from '../../../project/cmds/cmd-runner'; import { integrateAndClearModifiedFiles } from '../utils'; @@ -91,22 +90,9 @@ async function executeRun( tempProjectPath: string, runningServices: RunningServicesManager ): Promise> { - // Validate and resolve packagePath. The helper rejects directory traversal - // and absolute paths, and requires packagePath when running inside a - // workspace project — without this, an agent-supplied path could escape - // tempProjectPath and `bal run` would execute in an arbitrary directory. - let cwd: string; - try { - cwd = resolvePackageBasePath(tempProjectPath, input.packagePath); - } catch (e: any) { - console.error("[BallerinaRun] Invalid packagePath:", e?.message); - return { - status: "error", - exitCode: -1, - output: "", - message: e?.message ?? "Invalid packagePath", - }; - } + const cwd = input.packagePath + ? path.resolve(tempProjectPath, input.packagePath) + : tempProjectPath; const balCmd = extension.ballerinaExtInstance.getBallerinaCmd(); const runCmd = getRunCommand(); @@ -132,13 +118,19 @@ async function executeRun( exitCode: -1, }; + // Track process exit + proc.on('close', (code) => { + service.exited = true; + service.exitCode = code ?? -1; + }); + runningServices.register(service); if (input.runType === "service") { const readyResult = await waitForServiceReady(service, DEFAULT_SERVICE_READY_TIMEOUT); if (!readyResult.ready) { - await killProcessGroup(proc, 'SIGTERM'); + killProcessGroup(proc, 'SIGTERM'); runningServices.remove(taskId); return { status: "error", @@ -163,7 +155,7 @@ async function executeRun( const completionResult = await waitForCompletion(service, timeout); if (completionResult.timedOut) { - await killProcessGroup(proc, 'SIGTERM'); + killProcessGroup(proc, 'SIGTERM'); runningServices.remove(taskId); return { status: "timeout", diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-stop.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-stop.ts index 5e9b758254..9c04908f59 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-stop.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/ballerina-stop.ts @@ -81,7 +81,7 @@ async function stopService( } if (!service.process.killed) { - await killProcessGroup(service.process, 'SIGTERM'); + killProcessGroup(service.process, 'SIGTERM'); } await waitForExit(service.process); diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/clarify.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/clarify.ts deleted file mode 100644 index d3bce2b81c..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/clarify.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import { tool } from 'ai'; -import { z } from 'zod'; -import * as crypto from "crypto"; -import { CopilotEventHandler } from '../../utils/events'; -import { approvalManager } from '../../state/ApprovalManager'; - -export const CLARIFY_TOOL = "Clarify"; - -const ClarifyInputSchema = z.object({ - questions: z.array( - z.object({ - question: z.string().describe('The question to ask the user.'), - tabLabel: z.string().describe('Short 1-2 word label for the navigation tab, e.g. "Integration", "Systems", "Tests".'), - options: z.array( - z.object({ - label: z.string().describe('Display label for the option.'), - value: z.string().describe('Value returned if this option is selected.'), - }) - ).describe('Predefined answer options for the user to choose from. Do NOT include an "Other" option — a free-text input is always provided automatically.'), - selectionType: z.enum(["single", "multiple"]).describe( - 'Use "single" when only one answer applies, "multiple" when several can.' - ), - }) - ).describe('All questions to ask the user in this single interaction.'), -}); - -export type ClarifyInput = z.infer; - -export function createClarifyTool(eventHandler: CopilotEventHandler) { - return tool({ - description: `Use this tool to ask the user clarifying questions when the request is ambiguous at the requirements level — where a wrong assumption would produce an integration the user did not want. - -Call this ONLY for genuine requirement gaps. Use smart defaults first: - -1. Entry point: Default to HTTP for simple service requests. Ask only when the trigger type is ambiguous and the choice materially affects the design. - -2. Storage / external system: Default to in-memory for simple apps. When a category is explicitly required but the specific technology is unspecified, infer from context — otherwise ask. - -When asking, include a recommended option labelled with "(recommended)". -Do NOT ask about library/connector selection or implementation details that can be defaulted. -Call AT MOST ONCE per task — batch all questions into a single call.`, - inputSchema: ClarifyInputSchema, - execute: async (input, context?: { toolCallId?: string }): Promise<{ answers: Array<{ question: string; answers: string[] }> } | { skipped: boolean }> => { - const toolCallId = context?.toolCallId || `fallback-${Date.now()}`; - const requestId = crypto.randomUUID(); - - eventHandler({ type: "tool_call", toolName: CLARIFY_TOOL, toolInput: input, toolCallId }); - - const response = await approvalManager.requestClarify(requestId, input, eventHandler); - - if (!response.answered) { - eventHandler({ - type: "clarify_event", - requestId, - stage: "skipped", - questions: input.questions, - }); - eventHandler({ type: "tool_result", toolName: CLARIFY_TOOL, toolOutput: { skipped: true }, toolCallId }); - return { skipped: true }; - } - - eventHandler({ - type: "clarify_event", - requestId, - stage: "answered", - questions: input.questions, - answers: response.answers, - }); - eventHandler({ type: "tool_result", toolName: CLARIFY_TOOL, toolOutput: { answers: response.answers }, toolCallId }); - return { answers: response.answers ?? [] }; - }, - }); -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/config-collector.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/config-collector.ts index 2701a8bbad..f9ff1914c2 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/config-collector.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/config-collector.ts @@ -29,7 +29,6 @@ import { createStatusMetadata, readExistingConfigValues, } from "../../../../utils/toml-utils"; -import { resolveContained, resolvePackageBasePath } from "./path-utils"; export const CONFIG_COLLECTOR_TOOL = "ConfigCollector"; @@ -50,11 +49,6 @@ const ConfigCollectorSchema = z.object({ variables: z.array(ConfigVariableSchema).optional().describe("Configuration variables"), variableNames: z.array(z.string()).optional().describe("Variable names for check mode"), isTestConfig: z.boolean().optional().describe("Set to true when collecting configuration for tests. Tool will automatically read from Config.toml and write to tests/Config.toml"), - packagePath: z.string().optional().describe( - "Relative path to the target package within the workspace project (e.g., \"pkg1\"). " + - "Required for workspace projects so Config.toml is written inside the correct package, not the workspace root. " + - "Omit for single-package (non-workspace) projects." - ), }); interface ConfigCollectorInput { @@ -63,7 +57,6 @@ interface ConfigCollectorInput { variables?: ConfigVariable[]; variableNames?: string[]; isTestConfig?: boolean; - packagePath?: string; } export interface ConfigCollectorResult { @@ -130,16 +123,12 @@ Operation Modes: - Shows a form; nothing is written until the user confirms. If skipped, no file is created or modified - Pre-populates from existing Config.toml if it exists - When running tests, use isTestConfig: true — this is the only collect call needed; writes to tests/Config.toml after user confirms - - For workspace projects, you MUST pass packagePath so the file is written inside the target package (not the workspace root) - Example: { mode: "collect", variables: [{ name: "stripeApiKey", description: "Stripe API key", secret: true }] } - Example (test): { mode: "collect", variables: [...], isTestConfig: true } - - Example (workspace): { mode: "collect", variables: [...], packagePath: "pkg1" } 2. CHECK: Inspect which values are filled or missing — can be called at any time - Returns status only, never actual values - - For workspace projects, pass packagePath to inspect the Config.toml of a specific package - Example: { mode: "check", variableNames: ["dbPassword", "apiKey"], filePath: "Config.toml" } - - Example (workspace): { mode: "check", variableNames: ["dbPassword"], packagePath: "pkg1" } - Returns: { status: { dbPassword: "filled", apiKey: "missing" } } VARIABLE NAMING: @@ -223,8 +212,7 @@ export async function ConfigCollectorTool( eventHandler, requestId, input.isTestConfig, - modifiedFiles, - input.packagePath + modifiedFiles ); case "check": @@ -232,8 +220,7 @@ export async function ConfigCollectorTool( input.variableNames, input.filePath, paths, - input.isTestConfig, - input.packagePath + input.isTestConfig ); default: @@ -251,24 +238,17 @@ async function handleCollectMode( eventHandler: CopilotEventHandler, requestId: string, isTestConfig?: boolean, - modifiedFiles?: string[], - packagePath?: string + modifiedFiles?: string[] ): Promise { // Validate variable names const validationError = validateConfigVariables(variables); if (validationError) { return validationError; } - // Resolve and validate the package base path. For workspace projects, the - // agent must pass packagePath so Config.toml lands inside the target - // package rather than the workspace root. The helper rejects directory - // traversal attempts and missing-but-required values. - const packageBasePath = resolvePackageBasePath(paths.tempPath, packagePath); - // Determine paths based on isTestConfig flag - const configPath = getConfigPath(packageBasePath, isTestConfig); + const configPath = getConfigPath(paths.tempPath, isTestConfig); // Priority: tests/Config.toml → Config.toml → empty - const mainConfigPath = path.join(packageBasePath, "Config.toml"); + const mainConfigPath = path.join(paths.tempPath, "Config.toml"); const sourceConfigPath = isTestConfig ? (fs.existsSync(configPath) ? configPath : mainConfigPath) : configPath; @@ -327,15 +307,11 @@ async function handleCollectMode( // Write actual configuration values to determined config path writeConfigValuesToConfig(configPath, userResponse.configValues!, variables); - // Track modified file for syncing to workspace. - // Path is relative to tempProjectPath, so prefix with packagePath for workspace projects. + // Track modified file for syncing to workspace if (modifiedFiles) { const configFileName = getConfigFileName(isTestConfig); - const relativeConfigPath = packagePath - ? path.join(packagePath, configFileName) - : configFileName; - if (!modifiedFiles.includes(relativeConfigPath)) { - modifiedFiles.push(relativeConfigPath); + if (!modifiedFiles.includes(configFileName)) { + modifiedFiles.push(configFileName); } } @@ -368,22 +344,15 @@ async function handleCheckMode( variableNames: string[], filePath: string | undefined, paths: ConfigCollectorPaths, - isTestConfig?: boolean, - packagePath?: string + isTestConfig?: boolean ): Promise { - // Resolve and validate the package base path. For workspace projects the - // agent must pass packagePath to inspect a specific package's Config.toml. - const packageBasePath = resolvePackageBasePath(paths.tempPath, packagePath); - let configPath: string; let configFileName: string; if (filePath) { - // filePath is also untrusted agent input — validate containment so - // it cannot escape the package directory via `..` segments. - configPath = resolveContained(packageBasePath, filePath); + configPath = path.join(paths.tempPath, filePath); configFileName = path.basename(filePath); } else { - configPath = getConfigPath(packageBasePath, isTestConfig); + configPath = getConfigPath(paths.tempPath, isTestConfig); configFileName = getConfigFileName(isTestConfig); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/diagnostics.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/diagnostics.ts index bbaee4c68c..3c5f725c8a 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/diagnostics.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/diagnostics.ts @@ -1,7 +1,7 @@ import { tool } from 'ai'; import { z } from 'zod'; +import * as path from 'path'; import { checkCompilationErrors, DiagnosticsCheckResult } from './diagnostics-utils'; -import { resolvePackageBasePath } from './path-utils'; import { CopilotEventHandler } from '../../utils/events'; export const DIAGNOSTICS_TOOL_NAME = "getCompilationErrors"; @@ -56,44 +56,13 @@ The tool analyzes the entire Ballerina package and returns: toolName: DIAGNOSTICS_TOOL_NAME, }); - // Validate and resolve packagePath. The helper rejects directory - // traversal and requires packagePath for workspace projects, so an - // agent-supplied path can never escape tempProjectPath and steer - // the language server at an unrelated directory on disk. - let targetPath: string; - try { - targetPath = resolvePackageBasePath(tempProjectPath, packagePath); - } catch (e: any) { - console.error("[Diagnostics] Invalid packagePath:", e?.message); - const errorResult: DiagnosticsCheckResult = { - diagnostics: [], - message: e?.message ?? "Invalid packagePath", - }; - eventHandler({ - type: "tool_result", - toolName: DIAGNOSTICS_TOOL_NAME, - toolOutput: errorResult, - }); - return errorResult; - } + // Resolve the target path: append packagePath for workspace projects + const targetPath = packagePath + ? path.join(tempProjectPath, packagePath) + : tempProjectPath; - - const DIAGNOSTICS_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes - const timeoutResult: DiagnosticsCheckResult = { - diagnostics: [], - message: - "Diagnostics check timed out — the Language Server is still compiling the workspace " + - "(large multi-package project). Treat the current code as potentially having compilation " + - "errors and continue fixing any issues you can identify from the source. " + - "You may call this tool again later to recheck.", - }; - - const result = await Promise.race([ - checkCompilationErrors(targetPath), - new Promise((resolve) => - setTimeout(() => resolve(timeoutResult), DIAGNOSTICS_TIMEOUT_MS) - ), - ]); + // Use shared utility to check compilation errors + const result = await checkCompilationErrors(targetPath); // Emit tool_result event to visualizer (shows result in UI) eventHandler({ diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/healthcare-library.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/healthcare-library.ts index 2a44c5400a..61cb3c7aa1 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/healthcare-library.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/healthcare-library.ts @@ -19,11 +19,11 @@ import { GenerationType, getAllLibraries } from "../../utils/libs/libraries"; import { LIBRARY_GET_TOOL } from "./library-get"; import { jsonSchema } from "ai"; import { Library } from "../../utils/libs/library-types"; -import { selectRequiredFunctions, ModelUsage, mergeUsage } from "../../utils/libs/function-registry"; +import { selectRequiredFunctions } from "../../utils/libs/function-registry"; import { MinifiedLibrary } from "@wso2/ballerina-core"; import { ANTHROPIC_SONNET_4, getAnthropicClient, getProviderCacheControl } from "../../utils/ai-client"; import { z } from "zod"; -import { CopilotEventHandler, emitModelUsage, ToolModelUsage } from "../../utils/events"; +import { CopilotEventHandler } from "../../utils/events"; export const HEALTHCARE_LIBRARY_PROVIDER_TOOL = "HealthcareLibraryProviderTool"; @@ -57,8 +57,7 @@ const HealthcareLibraryProviderToolSchema = jsonSchema<{ export async function HealthcareLibraryProviderTool( params: { userPrompt: string }, - eventHandler: CopilotEventHandler, - toolModelUsage: ToolModelUsage + eventHandler: CopilotEventHandler ): Promise { try { // Emit tool_call event @@ -69,16 +68,14 @@ export async function HealthcareLibraryProviderTool( const startTime = Date.now(); - const { libraries, usage } = await getRelevantLibrariesAndFunctions(params.userPrompt, GenerationType.HEALTHCARE_GENERATION); + const libraries = await getRelevantLibrariesAndFunctions(params.userPrompt, GenerationType.HEALTHCARE_GENERATION); console.log( `[HealthcareLibraryProviderTool] Fetched ${libraries.length} libraries: ${libraries .map((lib) => lib.name) - .join(", ")}, took ${(Date.now() - startTime) / 1000}s, Usage:`, usage + .join(", ")}, took ${(Date.now() - startTime) / 1000}s` ); - emitModelUsage(eventHandler, usage, toolModelUsage); - // Emit tool_result event with all library names (no filtering) emitHealthcareLibraryToolResult(eventHandler, libraries); @@ -99,8 +96,7 @@ export async function HealthcareLibraryProviderTool( //TODO: Improve this description export function getHealthcareLibraryProviderTool( - eventHandler: CopilotEventHandler, - toolModelUsage: ToolModelUsage + eventHandler: CopilotEventHandler ) { return tool({ description: `Fetches detailed information about healthcare-specific Ballerina libraries along with their API documentation, including services, clients, functions, and filtered type definitions. @@ -131,7 +127,7 @@ You should only use this tool if the user query mentions, console.log( `[HealthcareLibraryProviderTool] Called with prompt: ${input.userPrompt}` ); - return await HealthcareLibraryProviderTool(input, eventHandler, toolModelUsage); + return await HealthcareLibraryProviderTool(input, eventHandler); }, }); } @@ -160,17 +156,17 @@ const LibraryListSchema = z.object({ export async function getRelevantLibrariesAndFunctions( query: string, generationType: GenerationType -): Promise<{ libraries: Library[], usage: ModelUsage[] }> { - const { libraries: selectedLibs, usage: selectionUsage } = await getSelectedLibraries(query, generationType); +): Promise { + const selectedLibs: string[] = await getSelectedLibraries(query, generationType); const allLibraries = ensureMandatoryHealthcareLibraries(selectedLibs); - const { libraries, usage: filteringUsage } = await selectRequiredFunctions(query, allLibraries, generationType); - return { libraries, usage: mergeUsage(selectionUsage, ...filteringUsage) }; + const relevantTrimmedFuncs: Library[] = await selectRequiredFunctions(query, allLibraries, generationType); + return relevantTrimmedFuncs; } -export async function getSelectedLibraries(prompt: string, libraryType: GenerationType): Promise<{ libraries: string[], usage: ModelUsage }> { +export async function getSelectedLibraries(prompt: string, libraryType: GenerationType): Promise { const allLibraries = await getAllLibraries(libraryType); if (allLibraries.length === 0) { - return { libraries: [], usage: { model: ANTHROPIC_SONNET_4, inputTokens: 0, outputTokens: 0 } }; + return []; } const cacheOptions = await getProviderCacheControl(); const messages: ModelMessage[] = [ @@ -187,7 +183,7 @@ export async function getSelectedLibraries(prompt: string, libraryType: Generati //TODO: Add thinking and test with claude haiku const startTime = Date.now(); - const { object, usage } = await generateObject({ + const { object } = await generateObject({ model: await getAnthropicClient(ANTHROPIC_SONNET_4), maxOutputTokens: 4096, temperature: 0, @@ -196,11 +192,10 @@ export async function getSelectedLibraries(prompt: string, libraryType: Generati abortSignal: new AbortController().signal, }); const endTime = Date.now(); - const callUsage: ModelUsage = { model: ANTHROPIC_SONNET_4, inputTokens: usage.inputTokens || 0, outputTokens: usage.outputTokens || 0 }; - console.log(`Library selection took ${endTime - startTime}ms, Usage:`, callUsage); + console.log(`Library selection took ${endTime - startTime}ms`); console.log("Selected libraries:", object.libraries); - return { libraries: object.libraries, usage: callUsage }; + return object.libraries; } function getSystemPrompt(libraryList: MinifiedLibrary[]): string { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/hurl-tool.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/hurl-tool.ts index 95da0d3a7d..8db82eed61 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/hurl-tool.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/hurl-tool.ts @@ -21,20 +21,36 @@ import { CopilotEventHandler } from '../../utils/events'; export const HURL_TOOL_NAME = "hurlRunnerTool"; const HURL_LM_TOOL_NAME = "run-hurl-test"; -const TOOL_DESCRIPTION = `The hurl script to execute. Hurl is a command-line tool for running HTTP requests written in a simple text format. A script can contain one or more requests. -Example Script: -GET http://example.com/api/resource -Accept: application/json +const TOOL_DESCRIPTION = `The hurl script to execute. Hurl is a command-line tool and DSL for running HTTP requests defined in a simple text format. The script can contain one or more HTTP requests, along with optional assertions to validate the responses. You can capture values from previous requests and use them in subsequent requests. -POST http://example.com/api +Example Hurl script: +GET http://api.example.com/users +HTTP 200 + +[Captures] +userId: jsonpath("$.id") + +POST http://api.example.com/posts Content-Type: application/json { - "name": "try-it" + "userId": "{{userId}}", + "title": "New Post", + "content": "This post belongs to the user captured from the previous request" } +HTTP 201 When defining a request body in Hurl, you must follow strict syntax rules. For simple bodies (e.g., JSON), you can write them directly after a blank line. However, for complex or multi-line raw bodies (such as multipart/form-data, raw HTTP payloads, or content containing special characters), you MUST wrap the entire body inside triple backticks (\`\`\`). -Failing to do this will result in parsing errors (e.g., "invalid HTTP method"). + +This ensures the Hurl parser treats the content as a literal body instead of interpreting lines as new requests or syntax elements. Failing to do this will result in parsing errors (e.g., "invalid HTTP method"). + +Example (standard JSON body): +POST http://example.com/api +Content-Type: application/json + +{ + "name": "test" +} Example (raw multipart body using triple backticks): POST http://example.com/upload @@ -42,7 +58,7 @@ Content-Type: multipart/form-data; boundary=----Boundary123 \`\`\` ------Boundary123 -Content-Disposition: form-data; name="file"; filename="sample.txt" +Content-Disposition: form-data; name="file"; filename="test.txt" Content-Type: text/plain hello world @@ -53,10 +69,14 @@ Use triple backticks whenever the body spans multiple structured lines or includ Avoid using unnecessary newlines in the hurl script, as they can lead to parsing issues. `; +function prepareHurlScript(input: HURLInput): string { + // Attaching the test scenario as a comment at the top of the Hurl script + return `# @collectionName ${input.testScenario}\n${input.hurlScript}`; +} export const HURLInputSchema = z.object({ hurlScript: z.string().describe(TOOL_DESCRIPTION), - tryItScenario: z.string().max(30).describe("A short description of the try-it scenario being executed. This is used for logging and reporting purposes to provide context about the Hurl script execution.") + testScenario: z.string().max(30).describe("A short description of the test scenario being executed. This is used for logging and reporting purposes to provide context about the Hurl script execution.") }); export type HURLInput = z.infer; @@ -76,6 +96,11 @@ type HurlToolOutput = { output: { status: string; durationMs: number; + summary: { + totalEntries: number; + passedEntries: number; + failedEntries: number; + }; entries: Array<{ name: string; method?: string; @@ -98,28 +123,10 @@ type HurlToolOutput = { }; }; -type RawHurlToolOutput = HurlToolOutput & { - output: HurlToolOutput["output"] & { - summary?: { - totalEntries: number; - passedEntries: number; - failedEntries: number; - }; - }; -}; - -function removeSummaryFromHurlOutput(response: RawHurlToolOutput): HurlToolOutput { - const { summary: _summary, ...outputWithoutSummary } = response.output; - return { - ...response, - output: outputWithoutSummary - }; -} - export function createHurlTool(eventHandler: CopilotEventHandler) { return tool({ - description: `A tool to execute Hurl scripts. The input is a Hurl script as a string. The output includes the execution results, including response details. Use this tool to try out HTTP endpoints. Prefer requests without assertions for simple try-it scenarios ( without including status code assertions such as HTTP 200 or other types of assertions)`, + description: `A tool to execute Hurl scripts. The input is a Hurl script as a string. The output includes the execution results, including request details, response details, assertion results, and any warnings. Use this tool to try out endpoints, or to execute HTTP test scenarios.`, inputSchema: HURLInputSchema, execute: async (input): Promise => await executeHurlRequest(input, eventHandler) }); @@ -127,19 +134,18 @@ export function createHurlTool(eventHandler: CopilotEventHandler) { export const executeHurlRequest = async (input: HURLInput, eventHandler: CopilotEventHandler, context?: { toolCallId?: string }): Promise => { const toolCallId = context?.toolCallId || `fallback-${Date.now()}`; - const hurlScript = input.hurlScript; + const hurlScript = prepareHurlScript(input); try { eventHandler({ type: "tool_call", toolName: HURL_TOOL_NAME, - toolInput: { hurlScript, scenario: input.tryItScenario }, + toolInput: { hurlScript: input.hurlScript, scenario: input.testScenario }, toolCallId }); const lmToolResult = await vscode.lm.invokeTool(HURL_LM_TOOL_NAME, { input: { hurlScript }, toolInvocationToken: undefined }); const resultTextPart = (lmToolResult.content[0] as vscode.LanguageModelTextPart); - const rawResponse: RawHurlToolOutput = JSON.parse(resultTextPart.value); - const response = removeSummaryFromHurlOutput(rawResponse); - const toolOutput = { hurlScript: input.hurlScript, scenario: input.tryItScenario, runResult: response }; + const response: HurlToolOutput = JSON.parse(resultTextPart.value); + const toolOutput = { hurlScript: input.hurlScript, scenario: input.testScenario, runResult: response }; eventHandler({ type: "tool_result", toolName: HURL_TOOL_NAME, @@ -155,11 +161,16 @@ export const executeHurlRequest = async (input: HURLInput, eventHandler: Copilot output: { status: "error", durationMs: 0, + summary: { + totalEntries: 0, + passedEntries: 0, + failedEntries: 0 + }, entries: [], warnings: [`Failed to execute Hurl script. Error: ${error instanceof Error ? error.message : String(error)}`] } }; - const toolOutput = { hurlScript: input.hurlScript, scenario: input.tryItScenario, runResult: genericErrorOutput }; + const toolOutput = { hurlScript: input.hurlScript, scenario: input.testScenario, runResult: genericErrorOutput }; eventHandler({ type: "tool_result", toolName: HURL_TOOL_NAME, diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-get.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-get.ts index 50403f66ab..ad80cc2a92 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-get.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-get.ts @@ -19,8 +19,7 @@ import { GenerationType } from "../../utils/libs/libraries"; import { jsonSchema } from "ai"; import { Library } from "../../utils/libs/library-types"; import { selectRequiredFunctions } from "../../utils/libs/function-registry"; -import { toSyntaxString } from "../../utils/libs/to-syntax-string"; -import { CopilotEventHandler, emitModelUsage, ToolModelUsage } from "../../utils/events"; +import { CopilotEventHandler } from "../../utils/events"; export const LIBRARY_GET_TOOL = "LibraryGetTool"; @@ -68,7 +67,6 @@ export async function LibraryGetTool( params: { libraryNames: string[]; userPrompt: string }, generationType: GenerationType, eventHandler: CopilotEventHandler, - toolModelUsage: ToolModelUsage, toolCallId: string ): Promise { try { @@ -80,15 +78,13 @@ export async function LibraryGetTool( }); const startTime = Date.now(); - const { libraries, usage } = await selectRequiredFunctions(params.userPrompt, params.libraryNames, generationType); + const libraries = await selectRequiredFunctions(params.userPrompt, params.libraryNames, generationType); console.log( `[LibraryGetTool] Fetched ${libraries.length} libraries: ${libraries .map((lib) => lib.name) - .join(", ")}, took ${(Date.now() - startTime) / 1000}s, Usage:`, usage + .join(", ")}, took ${(Date.now() - startTime) / 1000}s` ); - emitModelUsage(eventHandler, usage, toolModelUsage); - // Emit tool_result event with filtered library names and ID emitLibraryToolResult(eventHandler, LIBRARY_GET_TOOL, libraries, params.libraryNames, toolCallId); @@ -110,8 +106,7 @@ export async function LibraryGetTool( export function getLibraryGetTool( generationType: GenerationType, - eventHandler: CopilotEventHandler, - toolModelUsage: ToolModelUsage + eventHandler: CopilotEventHandler ) { return tool({ description: `Fetches detailed information about Ballerina libraries along with their API documentation, including services, clients, functions, and types. @@ -143,10 +138,7 @@ name, description, type definitions (records, objects, enums, type aliases), cli ", " )} and prompt: ${input.userPrompt} [toolCallId: ${toolCallId}]` ); - const rawLibraries: Library[] = await LibraryGetTool(input, generationType, eventHandler, toolModelUsage, toolCallId); - const afterContent = toSyntaxString(rawLibraries); - - return afterContent; + return await LibraryGetTool(input, generationType, eventHandler, toolCallId); }, }); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-search.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-search.ts index 8e32451712..e47a3b0067 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-search.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/library-search.ts @@ -146,7 +146,6 @@ This tool discovers relevant Ballerina libraries using keyword-based search. It - First keyword = most important (highest weight in search) - Subsequent keywords = less important (decreasing weight) - Use specific terms (e.g., "Stripe", "GitHub", "PostgreSQL") before generic ones (e.g., "payment", "API", "database") -- Include 'trigger' keyword to indicate webhook related libraries. **When to use this tool:** - To discover which libraries are available for a specific use case or integration diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/migration-source-reader.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/migration-source-reader.ts deleted file mode 100644 index 0119380526..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/migration-source-reader.ts +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -/** - * Read-only tools that let the AI agent explore and read files from the - * original migration source project (Mule, Tibco, or any future platform). - * - * These tools are registered **only** when a `migrationSourcePath` is provided - * in the tool registry options — i.e. during migration enhancement runs. - * They are scoped exclusively to the source project directory and cannot - * modify files or escape via path traversal. - */ - -import { tool } from 'ai'; -import { z } from 'zod'; -import * as fs from 'fs'; -import * as path from 'path'; -import { CopilotEventHandler } from '../../utils/events'; - -// ============================================================================ -// Tool name constants -// ============================================================================ - -export const MIGRATION_SOURCE_LIST_TOOL = 'migration_source_list'; -export const MIGRATION_SOURCE_READ_TOOL = 'migration_source_read'; - -// ============================================================================ -// Constants -// ============================================================================ - -/** - * Broad set of file extensions that may appear in migration source projects - * across all supported platforms (Mule, Tibco, and future). - */ -const VALID_SOURCE_EXTENSIONS = new Set([ - // XML / config / schema - '.xml', '.xsd', '.wsdl', '.xslt', '.xsl', - // Mule-specific - '.dwl', - // Tibco-specific - '.bwp', '.process', '.substvar', '.bw', - // Property / config - '.properties', '.yaml', '.yml', '.json', '.cfg', '.conf', '.ini', '.toml', - // Script / code - '.groovy', '.java', '.js', '.py', '.sh', '.bat', - // Data / doc - '.csv', '.sql', '.txt', '.md', '.html', '.htm', - // Build - '.gradle', -]); - -/** - * Filenames that are always allowed regardless of extension - * (e.g. extensionless build/config files at the project root). - */ -const ALLOWED_FILENAMES = new Set([ - 'pom.xml', 'build.xml', 'build.gradle', 'Makefile', 'Dockerfile', - 'Rakefile', 'Gemfile', 'Jenkinsfile', '.env', -]); - -const MAX_READ_BYTES = 80_000; // ~20k tokens — truncate anything larger -const MAX_LINE_LENGTH = 2000; -const MAX_LIST_ENTRIES = 500; // safety cap for very large directories - -// ============================================================================ -// Validation helpers -// ============================================================================ - -interface ValidationResult { - valid: boolean; - error?: string; -} - -function validateSourcePath( - userPath: string, - sourcePath: string, -): ValidationResult { - if (!userPath || typeof userPath !== 'string') { - return { valid: false, error: 'Path is required and must be a string.' }; - } - - // Block obvious traversal attempts - if (userPath.includes('..') || userPath.includes('~')) { - return { valid: false, error: 'Path contains invalid characters (.., ~).' }; - } - - // Resolve and verify the path is still under sourcePath - const resolved = path.resolve(sourcePath, userPath); - const resolvedRoot = path.resolve(sourcePath); - if (!resolved.startsWith(resolvedRoot + path.sep) && resolved !== resolvedRoot) { - return { valid: false, error: 'Path escapes the source project directory.' }; - } - - return { valid: true }; -} - -function isAllowedFile(fileName: string): boolean { - if (ALLOWED_FILENAMES.has(fileName)) { - return true; - } - const ext = path.extname(fileName).toLowerCase(); - return VALID_SOURCE_EXTENSIONS.has(ext); -} - -function truncateLongLines(content: string): string { - return content - .split('\n') - .map(line => - line.length > MAX_LINE_LENGTH - ? line.substring(0, MAX_LINE_LENGTH) + '… [truncated]' - : line - ) - .join('\n'); -} - -// ============================================================================ -// Event helpers -// ============================================================================ - -function emitToolCall( - eventHandler: CopilotEventHandler, - toolName: string, - input: Record, -): void { - eventHandler({ - type: 'tool_call', - toolName, - toolInput: input, - }); -} - -function emitToolResult( - eventHandler: CopilotEventHandler, - toolName: string, - success: boolean, -): void { - eventHandler({ - type: 'tool_result', - toolName, - toolOutput: { success }, - }); -} - -// ============================================================================ -// migration_source_list -// ============================================================================ - -export function createMigrationSourceListTool( - eventHandler: CopilotEventHandler, - sourcePath: string, -) { - return tool({ - description: - `Lists files and directories in the original migration source project (e.g. MuleSoft, TIBCO, or other platform). ` + - `Use this to explore the source project structure and find relevant configuration files, scripts, and resources. ` + - `Returns immediate children only — call again with a subdirectory path to drill deeper.`, - inputSchema: z.object({ - directory_path: z - .string() - .default('.') - .describe( - 'Relative path within the source project to list. Defaults to the project root (".").', - ), - }), - execute: async (args: { directory_path?: string }) => { - const dirPath = args.directory_path || '.'; - - emitToolCall(eventHandler, MIGRATION_SOURCE_LIST_TOOL, { directory_path: dirPath }); - - // Validate - const validation = validateSourcePath(dirPath, sourcePath); - if (!validation.valid) { - const result = { success: false, message: validation.error! }; - emitToolResult(eventHandler, MIGRATION_SOURCE_LIST_TOOL, false); - return result; - } - - const fullPath = path.resolve(sourcePath, dirPath); - - if (!fs.existsSync(fullPath)) { - const result = { success: false, message: `Directory not found: '${dirPath}'.` }; - emitToolResult(eventHandler, MIGRATION_SOURCE_LIST_TOOL, false); - return result; - } - - const stat = fs.statSync(fullPath); - if (!stat.isDirectory()) { - const result = { success: false, message: `'${dirPath}' is not a directory.` }; - emitToolResult(eventHandler, MIGRATION_SOURCE_LIST_TOOL, false); - return result; - } - - let entries: fs.Dirent[]; - try { - entries = fs.readdirSync(fullPath, { withFileTypes: true }); - } catch (error) { - const result = { success: false, message: `Cannot read directory: '${dirPath}'.` }; - emitToolResult(eventHandler, MIGRATION_SOURCE_LIST_TOOL, false); - return result; - } - - // Build the listing — cap at MAX_LIST_ENTRIES to prevent huge responses - const listing: string[] = []; - let truncated = false; - for (const entry of entries) { - if (listing.length >= MAX_LIST_ENTRIES) { - truncated = true; - break; - } - if (entry.isDirectory()) { - listing.push(`${entry.name}/`); - } else if (entry.isFile()) { - listing.push(entry.name); - } - // Skip symlinks, sockets, etc. - } - - const header = `Contents of '${dirPath}' (${listing.length} entries${truncated ? `, truncated to ${MAX_LIST_ENTRIES}` : ''}):\n`; - const body = listing.join('\n'); - - console.log(`[MigrationSourceList] Listed ${listing.length} entries in: ${dirPath}`); - - emitToolResult(eventHandler, MIGRATION_SOURCE_LIST_TOOL, true); - return { success: true, message: header + body }; - }, - }); -} - -// ============================================================================ -// migration_source_read -// ============================================================================ - -export function createMigrationSourceReadTool( - eventHandler: CopilotEventHandler, - sourcePath: string, -) { - return tool({ - description: - `Reads a file from the original migration source project (e.g. MuleSoft, TIBCO, or other platform). ` + - `Use this to examine the original source code, configurations, transforms, and property files ` + - `when implementing TODO/FIXME items or performing fidelity checks. ` + - `Supports optional line-range pagination for large files.`, - inputSchema: z.object({ - file_path: z - .string() - .describe('Relative path to the file within the source project.'), - offset: z - .number() - .optional() - .describe('Line number to start reading from (1-based). Only needed for large files.'), - limit: z - .number() - .optional() - .describe('Number of lines to read. Only needed for large files.'), - }), - execute: async (args: { file_path: string; offset?: number; limit?: number }) => { - const { file_path, offset, limit } = args; - - emitToolCall(eventHandler, MIGRATION_SOURCE_READ_TOOL, { file_path }); - - // Validate path - const pathValidation = validateSourcePath(file_path, sourcePath); - if (!pathValidation.valid) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { success: false, message: pathValidation.error! }; - } - - // Validate extension / filename - const fileName = path.basename(file_path); - if (!isAllowedFile(fileName)) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { - success: false, - message: `File type not supported for reading. Allowed extensions: ${[...VALID_SOURCE_EXTENSIONS].join(', ')}`, - }; - } - - const fullPath = path.resolve(sourcePath, file_path); - - if (!fs.existsSync(fullPath)) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { success: false, message: `File not found: '${file_path}'.` }; - } - - const stat = fs.statSync(fullPath); - if (!stat.isFile()) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { success: false, message: `'${file_path}' is not a file.` }; - } - - // Read content - let content: string; - try { - content = fs.readFileSync(fullPath, 'utf-8'); - } catch (error) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { success: false, message: `Cannot read file: '${file_path}'.` }; - } - - // Truncate if too large - if (Buffer.byteLength(content, 'utf-8') > MAX_READ_BYTES) { - content = content.substring(0, MAX_READ_BYTES) + '\n\n[TRUNCATED — file exceeds size limit]'; - } - - // Handle empty file - if (content.trim().length === 0) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, true); - return { success: true, message: `File '${file_path}' is empty.` }; - } - - const lines = content.split('\n'); - const totalLines = lines.length; - - // Handle ranged read - if (offset !== undefined && limit !== undefined) { - if (offset < 1 || offset > totalLines) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { - success: false, - message: `Invalid offset ${offset}. File has ${totalLines} lines.`, - }; - } - if (limit < 1) { - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, false); - return { success: false, message: `Invalid limit ${limit}. Must be at least 1.` }; - } - - const startIndex = offset - 1; - const endIndex = Math.min(startIndex + limit, totalLines); - const rangedContent = truncateLongLines( - lines.slice(startIndex, endIndex).join('\n'), - ); - - console.log( - `[MigrationSourceRead] Read lines ${offset}-${endIndex} from: ${file_path}`, - ); - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, true); - return { - success: true, - message: `Read lines ${offset} to ${endIndex} from '${file_path}' (${endIndex - startIndex} lines).\nContent:\n${rangedContent}`, - }; - } - - // Full file read - const truncatedContent = truncateLongLines(content); - - console.log( - `[MigrationSourceRead] Read entire file: ${file_path} (${totalLines} lines)`, - ); - emitToolResult(eventHandler, MIGRATION_SOURCE_READ_TOOL, true); - return { - success: true, - message: `Read '${file_path}' (${totalLines} lines).\nContent:\n${truncatedContent}`, - }; - }, - }); -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/path-utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/path-utils.ts deleted file mode 100644 index a7e82427a1..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/path-utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -import * as fs from "fs"; -import * as path from "path"; - -/** - * Safely resolve a relative path against a base directory, ensuring the - * result remains contained within the base. Throws on absolute paths, null - * bytes, or `..` segments that escape the base — these can only come from - * untrusted agent input and indicate an attempt to leave the project root. - */ -export function resolveContained(basePath: string, relativePath: string): string { - if (path.isAbsolute(relativePath) || relativePath.includes("\0")) { - throw new Error( - `Invalid path "${relativePath}": must be a relative path within the project` - ); - } - const baseResolved = path.resolve(basePath); - const resolved = path.resolve(baseResolved, relativePath); - const rel = path.relative(baseResolved, resolved); - if (rel === ".." || rel.startsWith(".." + path.sep) || path.isAbsolute(rel)) { - throw new Error( - `Invalid path "${relativePath}": escapes the project root` - ); - } - return resolved; -} - -/** - * Detect whether the temp project at `tempPath` is a Ballerina workspace - * (multi-package). Workspace roots have a Ballerina.toml without a [package] - * section (typically with a [workspace] section listing packages instead). - * A single-package project has [package] in its root Ballerina.toml. - */ -export function isWorkspaceTempProject(tempPath: string): boolean { - const rootToml = path.join(tempPath, "Ballerina.toml"); - if (!fs.existsSync(rootToml)) { - // No root Ballerina.toml → packages must live in subdirectories - return true; - } - try { - const content = fs.readFileSync(rootToml, "utf8"); - return !/^\s*\[package\]/m.test(content); - } catch { - return false; - } -} - -/** - * Resolve the package base path from agent input. Validates `packagePath` - * against directory traversal and ensures workspace projects always supply - * one so callers never silently fall back to the workspace root. - * - * Throws with an actionable message if the input is missing-but-required, - * escapes the project, or names a directory that does not exist. Callers - * should catch these and surface them as tool errors to the agent. - */ -export function resolvePackageBasePath(tempPath: string, packagePath?: string): string { - if (packagePath) { - const resolved = resolveContained(tempPath, packagePath); - if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) { - throw new Error( - `Invalid packagePath: directory "${packagePath}" does not exist in the project` - ); - } - return resolved; - } - - if (isWorkspaceTempProject(tempPath)) { - throw new Error( - "packagePath is required for workspace projects. " + - "Pass the relative package path (e.g., \"pkg1\") so the operation targets the correct package." - ); - } - return tempPath; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/running-service-manager.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/running-service-manager.ts index fcc9541f65..70446f8366 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/running-service-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/running-service-manager.ts @@ -15,7 +15,6 @@ // under the License. import * as child_process from 'child_process'; -import { quoteShellPath } from '../../../../utils'; const isWindows = process.platform === 'win32'; @@ -28,42 +27,11 @@ export interface RunningService { startedAt: number; exited: boolean; exitCode: number; - /** - * Timer that auto-removes the entry after EXITED_SERVICE_TTL_MS once - * the process has closed. Tracked here so it can be cleared when the - * entry is removed earlier (e.g. via stopOne) — otherwise the timer - * keeps the closure (and the logs buffer) alive for the full TTL. - */ - exitTimer?: NodeJS.Timeout; } -/** - * Serializable view of a running service for cross-process boundaries (RPC). - * Excludes the process handle and log buffer, which cannot cross to the webview. - */ -export interface RunningServiceInfo { - taskId: string; - packagePath: string; - startedAt: number; - exited: boolean; - exitCode: number; -} - -/** Auto-remove an exited service from the manager after this many ms. */ -const EXITED_SERVICE_TTL_MS = 30_000; - -/** Hard ceiling on dispose() so extension deactivation can't be blocked by a stuck child. */ -const DISPOSE_TIMEOUT_MS = 2_000; - /** * Spawns a child process, capturing stdout/stderr into `logs`. * No VS Code terminal is created — output is consumed by the inline UI card. - * - * Because we run with `shell: true`, the shell would otherwise split the - * command on whitespace — breaking executable paths that contain spaces - * (e.g. `/Applications/WSO2 Integrator.app/.../bin/bal`). We pre-quote the - * command path and pass a single command string so the shell sees the path - * as one token. */ export function spawnProcess( command: string, @@ -71,11 +39,7 @@ export function spawnProcess( cwd: string, logs: string[] ): { process: child_process.ChildProcess } { - const fullCommand = args.length > 0 - ? `${quoteShellPath(command)} ${args.join(' ')}` - : quoteShellPath(command); - - const proc = child_process.spawn(fullCommand, { + const proc = child_process.spawn(command, args, { cwd, shell: true, detached: !isWindows, @@ -97,112 +61,48 @@ export function spawnProcess( return { process: proc }; } -/** - * Portable "is this PID still alive?" check. - * Uses signal 0 which is a no-op that throws if the process doesn't exist — - * supported on both Unix and Windows by Node's `process.kill`. - */ -function isProcessAlive(pid: number): boolean { - try { - process.kill(pid, 0); - return true; - } catch (err) { - const code = (err as NodeJS.ErrnoException).code; - // ESRCH = dead. EPERM = alive-but-unsignalable (shouldn't happen for our own child). - return code === 'EPERM'; - } -} - /** * Kills the entire process group (shell + child processes). - * - * Returns a promise that resolves once the kill command completes. Note: - * completion of the kill command does NOT guarantee the process tree is - * actually gone — callers should use `waitForExit` which verifies and - * escalates if the root PID is still alive. - * - * stderr from `taskkill` / failures of `process.kill` are logged (not - * silently swallowed) so orphan-process issues can be diagnosed from - * extension host logs in the field. + * Falls back to killing just the process if the group kill fails. */ -export function killProcessGroup(proc: child_process.ChildProcess, signal: NodeJS.Signals = 'SIGTERM'): Promise { +export function killProcessGroup(proc: child_process.ChildProcess, signal: NodeJS.Signals = 'SIGTERM'): void { if (proc.pid == null) { - return Promise.resolve(); + return; } - const pid = proc.pid; if (isWindows) { - return new Promise((resolve) => { - // /T kills the process tree, /F forces termination. - // Use async exec so the extension host isn't blocked by antivirus-slow taskkill. - child_process.exec(`taskkill /T /F /PID ${pid}`, (err, _stdout, stderr) => { - if (err) { - const msg = (stderr || err.message || '').trim(); - // Exit code 128 / "not found" just means the process already died. - const alreadyGone = /not found|not running|could not be found|no tasks/i.test(msg); - if (!alreadyGone) { - console.warn( - `[RunningServicesManager] taskkill /T /F /PID ${pid} failed: ${msg || '(no stderr)'}` - ); - } - } - resolve(); - }); - }); - } - - // Unix: kill the entire process group via negative PID. - return new Promise((resolve) => { + // On Windows, use taskkill with /T to kill the entire process tree try { - process.kill(-pid, signal); - } catch (err) { - const code = (err as NodeJS.ErrnoException).code; - if (code !== 'ESRCH') { - // Process group gone is fine; anything else is worth logging. - console.warn( - `[RunningServicesManager] process.kill(-${pid}, ${signal}) failed: ${(err as Error).message}` - ); - } - // Fall back to killing just the direct child. - try { - proc.kill(signal); - } catch (err2) { - const code2 = (err2 as NodeJS.ErrnoException).code; - if (code2 !== 'ESRCH') { - console.warn( - `[RunningServicesManager] proc.kill(${signal}) fallback failed: ${(err2 as Error).message}` - ); - } - } + child_process.execSync(`taskkill /T /F /PID ${proc.pid}`, { stdio: 'ignore' }); + } catch { + // Process may already be gone + try { proc.kill(signal); } catch { /* already dead */ } } - resolve(); - }); + } else { + // On Unix, kill the process group via negative PID + try { + process.kill(-proc.pid, signal); + } catch { + // Process group may already be gone; try killing just the process + try { proc.kill(signal); } catch { /* already dead */ } + } + } } const STOP_TIMEOUT_MS = 5000; -/** - * After `close` fires or the timeout elapses, we verify the PID is actually - * gone. On Windows, `taskkill /T` can fail to walk the full tree (e.g. when - * the shell exits before grandchildren are registered), leaving orphans. - * This constant controls how long we wait for each post-kill verification - * pass before giving up. - */ -const VERIFY_POLL_INTERVAL_MS = 100; -const VERIFY_MAX_WAIT_MS = 1_000; -/** - * Waits for a process to exit, with escalation if the root PID is still - * alive afterwards. Resolves when either (a) the PID is confirmed dead or - * (b) we've exhausted our escalation attempts. - */ -export async function waitForExit(proc: child_process.ChildProcess, timeoutMs: number = STOP_TIMEOUT_MS): Promise { - if (proc.exitCode !== null) { - return; - } +export function waitForExit(proc: child_process.ChildProcess, timeoutMs: number = STOP_TIMEOUT_MS): Promise { + return new Promise((resolve) => { + if (proc.exitCode !== null) { + resolve(); + return; + } - await new Promise((resolve) => { const timeout = setTimeout(() => { proc.removeListener('close', onClose); + if (proc.exitCode === null) { + killProcessGroup(proc, 'SIGKILL'); + } resolve(); }, timeoutMs); @@ -213,167 +113,36 @@ export async function waitForExit(proc: child_process.ChildProcess, timeoutMs: n proc.once('close', onClose); }); - - await verifyPidGone(proc); -} - -/** - * Verifies the root PID is actually gone. If not, escalates with a second - * kill pass and briefly polls. Logs loudly if the PID is still alive - * after escalation — that's the orphan-process smell. - */ -async function verifyPidGone(proc: child_process.ChildProcess): Promise { - if (proc.pid == null) { - return; - } - const pid = proc.pid; - if (!isProcessAlive(pid)) { - return; - } - - console.warn( - `[RunningServicesManager] PID ${pid} still alive after close/timeout; escalating kill` - ); - await killProcessGroup(proc, 'SIGKILL'); - - // Poll briefly for the PID to disappear after the escalation. - const deadline = Date.now() + VERIFY_MAX_WAIT_MS; - while (Date.now() < deadline) { - if (!isProcessAlive(pid)) { - return; - } - await new Promise((r) => setTimeout(r, VERIFY_POLL_INTERVAL_MS)); - } - - if (isProcessAlive(pid)) { - console.error( - `[RunningServicesManager] PID ${pid} is still alive after escalation — ` + - `child process (or descendants) may be orphaned. This can happen on ` + - `Windows when taskkill /T cannot walk the process tree.` - ); - } } export class RunningServicesManager { private services = new Map(); - /** - * Optional callback invoked whenever the service list changes - * (registered, removed, or process exited). Used to push updates - * to the webview UI. - */ - onChange?: (services: RunningServiceInfo[]) => void; - get(taskId: string): RunningService | undefined { return this.services.get(taskId); } - /** Returns a serializable snapshot of all tracked services. */ - getAll(): RunningServiceInfo[] { - return Array.from(this.services.values()).map((s) => ({ - taskId: s.taskId, - packagePath: s.packagePath, - startedAt: s.startedAt, - exited: s.exited, - exitCode: s.exitCode, - })); - } - register(service: RunningService): void { this.services.set(service.taskId, service); - - service.process.once('close', (code) => { - service.exited = true; - service.exitCode = code ?? -1; - this.notifyChange(); - // Hold the exited entry briefly so the UI can show the final state - // before it disappears from the list. - service.exitTimer = setTimeout(() => { - if (this.services.get(service.taskId) === service) { - this.services.delete(service.taskId); - this.notifyChange(); - } - }, EXITED_SERVICE_TTL_MS); - }); - - this.notifyChange(); } remove(taskId: string): boolean { - const service = this.services.get(taskId); - if (!service) { - return false; - } - this.dropService(service); - this.notifyChange(); - return true; - } - - async stopOne(taskId: string): Promise { - const service = this.services.get(taskId); - if (!service) { - return false; - } - if (!service.exited && !service.process.killed) { - await killProcessGroup(service.process, 'SIGTERM'); - await waitForExit(service.process); - } - // The 'close' listener may have already removed the entry and fired - // notifyChange. Only fire again if we're the ones doing the removal. - if (this.dropService(service)) { - this.notifyChange(); - } - return true; + return this.services.delete(taskId); } - /** - * Awaitable teardown of all services. Capped at DISPOSE_TIMEOUT_MS so - * extension deactivation never blocks indefinitely on a stuck process. - */ - async dispose(): Promise { - const services = Array.from(this.services.values()); - for (const service of services) { - this.dropService(service); - } - this.notifyChange(); - - const killAll = Promise.all( - services.map(async (service) => { - if (service.process.killed) { - return; - } - await killProcessGroup(service.process, 'SIGTERM'); - await waitForExit(service.process); - }) - ); - - // Race against a timeout so a stuck child can't hold up VS Code shutdown. - await Promise.race([ - killAll, - new Promise((resolve) => setTimeout(resolve, DISPOSE_TIMEOUT_MS)), - ]); - } - - /** - * Removes a service entry and clears its TTL timer if one is pending. - * Without clearing, the timer's closure pins the logs buffer for the - * full TTL even after the entry is gone. Returns whether the entry - * was actually present in the map. - */ - private dropService(service: RunningService): boolean { - if (service.exitTimer) { - clearTimeout(service.exitTimer); - service.exitTimer = undefined; + stopAll(): void { + for (const service of this.services.values()) { + if (!service.process.killed) { + killProcessGroup(service.process, 'SIGTERM'); + } + // Best-effort SIGKILL fallback — fire-and-forget for teardown + waitForExit(service.process); } - return this.services.delete(service.taskId); + this.services.clear(); } - private notifyChange(): void { - try { - this.onChange?.(this.getAll()); - } catch (err) { - console.error('[RunningServicesManager] onChange handler threw:', err); - } + dispose(): void { + this.stopAll(); } } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/task-writer.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/task-writer.ts index 535c737b77..2394f989e1 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/task-writer.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/task-writer.ts @@ -31,8 +31,8 @@ export interface TaskWriteResult { export const TaskInputSchema = z.object({ description: z.string().min(1).describe("Clear, actionable description of the task to be implemented"), - status: z.enum([TaskStatus.PENDING, TaskStatus.IN_PROGRESS, TaskStatus.COMPLETED]).describe("Current status of the task. Make sure mark it as completed only after the task is fully done and diagnostics are clean."), - type: z.enum([TaskTypes.SERVICE_DESIGN, TaskTypes.IMPLEMENTATION, TaskTypes.EXECUTION]).describe("Type of the implementation task. service_design: creates the service contract only (no implementation). implementation: all other implementation tasks. execution: executing the implemented logic(run, try it, test) — include only if the user has explicitly asked for execution.") + status: z.enum([TaskStatus.PENDING, TaskStatus.IN_PROGRESS, TaskStatus.COMPLETED]).describe("Current status of the task. Use 'pending' for tasks not started, 'in_progress' when actively working on it, 'completed' when work is finished."), + type: z.enum([TaskTypes.SERVICE_DESIGN, TaskTypes.CONNECTIONS_INIT, TaskTypes.IMPLEMENTATION, TaskTypes.TESTING]).describe("Type of the implementation task. service_design: creates the HTTP service contract only (no implementation). connections_init: creates connection/client initializations only. implementation: all other implementation tasks. testing: writing test cases for the implemented logic — include only if the user has explicitly asked for tests.") }); const TaskWriteInputSchema = z.object({ @@ -63,7 +63,7 @@ export function createTaskWriteTool( description: `Create and update implementation tasks for the design plan. ## Task Ordering: - Tasks should be ordered sequentially as they need to be executed. -- Prioritize service design, then implementation tasks. +- Prioritize service design, then connection initializations, then implementation tasks. ## CRITICAL RULE - ALWAYS SEND ALL TASKS: This tool is STATELESS. Every call MUST include ALL tasks. @@ -97,6 +97,7 @@ Send ALL tasks with status "pending". Example: [ {"description": "Create the HTTP service contract", "status": "pending", "type": "service_design"}, + {"description": "Create the MYSQL Connection", "status": "pending", "type": "connections_init"}, {"description": "Implement the resource functions", "status": "pending", "type": "implementation"} ] @@ -115,19 +116,22 @@ Example (3 tasks total): Start task 1 - Send ALL: [ {"description": "Create the HTTP service contract", "status": "in_progress", "type": "service_design"}, + {"description": "Create the MYSQL Connection", "status": "pending", "type": "connections_init"}, {"description": "Implement the resource functions", "status": "pending", "type": "implementation"} ] Complete task 1 - Send ALL: [ {"description": "Create the HTTP service contract", "status": "completed", "type": "service_design"}, + {"description": "Create the MYSQL Connection", "status": "pending", "type": "connections_init"}, {"description": "Implement the resource functions", "status": "pending", "type": "implementation"} ] After approval, start task 2 - Send ALL: [ {"description": "Create the HTTP service contract", "status": "completed", "type": "service_design"}, - {"description": "Implement the resource functions", "status": "in_progress", "type": "implementation"} + {"description": "Create the MYSQL Connection", "status": "in_progress", "type": "connections_init"}, + {"description": "Implement the resource functions", "status": "pending", "type": "implementation"} ] Rules: @@ -150,9 +154,9 @@ Rules: if (eventHandler) { if (input.isPlanApproval === true) { + // Explicit plan approval gate — show full task list to user and wait for approval approvalType = "plan"; - const autoApprove = !!(process.env.INITIAL_SCAFFOLD_PROMPT && process.env.INITIAL_SCAFFOLD_STEPS); - approvalResult = await handlePlanApproval(allTasks, eventHandler, projectRootPath, generationId, threadId, autoApprove); + approvalResult = await handlePlanApproval(allTasks, eventHandler, projectRootPath, generationId, threadId); } else if (input.requestReview === true) { // TODO: Re-enable approval gate (handleTaskCompletion) when review flow is ready // Skip review gate — mark as completed and continue autonomously @@ -257,29 +261,15 @@ async function handlePlanApproval( eventHandler: CopilotEventHandler, projectRootPath: string, generationId: string, - threadId: string, - autoApprove: boolean = false + threadId: string ): Promise<{ approved: boolean; comment?: string }> { - console.log(`[TaskWrite Tool] Plan approval requested, autoApprove=${autoApprove}`); + console.log(`[TaskWrite Tool] Plan approval requested`); const plan = createPlan(allTasks); // Store plan in ChatStateStorage with the generation chatStateStorage.updateGeneration(projectRootPath, threadId, generationId, { plan }); - if (autoApprove) { - // Emit event so the plan card renders in the UI, marked as auto-approved - eventHandler({ - type: "task_approval_request", - requestId: `plan-auto-${Date.now()}`, - approvalType: "plan", - tasks: allTasks, - message: "Plan auto-approved", - autoApproved: true, - }); - return { approved: true }; - } - // Use ApprovalManager for plan approval (replaces state machine subscription) const requestId = `plan-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/test-runner.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/test-runner.ts index 8d947add91..b4271ec541 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/test-runner.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/test-runner.ts @@ -129,7 +129,7 @@ async function runBallerinaTests(cwd: string): Promise { const output = logs.join(''); if (!exited) { - await killProcessGroup(proc, 'SIGTERM'); + killProcessGroup(proc, 'SIGTERM'); return { output: output + `\n\nTest execution timed out after ${DEFAULT_TEST_TIMEOUT}ms.`, exitCode: -1, diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/text-editor.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/text-editor.ts index 8a017cf0e8..495edbd18c 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/text-editor.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/tools/text-editor.ts @@ -24,7 +24,6 @@ import { StateMachine } from "../../../../stateMachine"; import { sendAISchemaDidChange, sendAiSchemaDidOpen } from "../../utils/project/ls-schema-notifications"; import { CopilotEventHandler } from "../../utils/events"; import { normalizeInvisibleChars } from "../../utils/string-utils"; -import { normalizeToLf, readAndNormalize, restoreEol } from "../../utils/eol-utils"; // ============================================================================ // Display Helper Functions @@ -86,7 +85,7 @@ interface TextEditorResult { // ============================================================================ const VALID_FILE_EXTENSIONS = [ - '.bal', '.toml', '.md', '.sql', ".yaml", ".yml", ".json", ".graphql", ".txt" + '.bal', '.toml', '.md', '.sql' ]; const RESTRICTED_READ_FILES = ['Config.toml']; @@ -424,9 +423,8 @@ export function createEditExecute( return result; } - // Read file content and normalize line endings (CRLF → LF) - const rawContent = fs.readFileSync(fullPath, 'utf-8'); - const [content, originalEol] = readAndNormalize(rawContent); + // Read file content (keep original for exact matching) + const content = fs.readFileSync(fullPath, 'utf-8'); // Try exact match first (99% case - no normalization needed) const exactOccurrenceCount = countOccurrences(content, old_string); @@ -486,8 +484,8 @@ export function createEditExecute( } } - // Write back to temp directory, restoring original line endings - fs.writeFileSync(fullPath, restoreEol(newContent, originalEol), 'utf-8'); + // Write back to temp directory + fs.writeFileSync(fullPath, newContent, 'utf-8'); if (modifiedFiles) { insertIntoUpdateFileNames(modifiedFiles, file_path); @@ -573,9 +571,8 @@ export function createMultiEditExecute( return result; } - // Read file content and normalize line endings (CRLF → LF) - const rawContent = fs.readFileSync(fullPath, 'utf-8'); - const [originalContent, originalEol] = readAndNormalize(rawContent); + // Read file content (keep original for exact matching) + const originalContent = fs.readFileSync(fullPath, 'utf-8'); // First pass: check if all edits work with exact matching (99% case) let useNormalizedMatching = false; @@ -657,8 +654,8 @@ export function createMultiEditExecute( } // All validations passed, content already has all edits applied - // Write back to temp directory, restoring original line endings - fs.writeFileSync(fullPath, restoreEol(content, originalEol), 'utf-8'); + // Write back to temp directory + fs.writeFileSync(fullPath, content, 'utf-8'); if (modifiedFiles) { insertIntoUpdateFileNames(modifiedFiles, file_path); @@ -732,8 +729,8 @@ export function createReadExecute( // Emit tool_call event now that we know the file exists emitFileToolCall(eventHandler, FILE_READ_TOOL_NAME, file_path); - // Read file content and normalize line endings (CRLF → LF) - const content = normalizeToLf(fs.readFileSync(fullPath, 'utf-8')); + // Read file content + const content = fs.readFileSync(fullPath, 'utf-8'); // Handle empty file if (content.trim().length === 0) { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/utils.ts index 926352b71a..40a6198b02 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/agent/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/agent/utils.ts @@ -20,7 +20,6 @@ import * as fs from "fs"; import * as path from "path"; import type { TextEdit } from "vscode-languageserver-protocol"; import { StateMachine } from "../../../stateMachine"; -import { normalizeToLf, readAndNormalize, restoreEol } from "../utils/eol-utils"; /** * Files that require path sanitization (temp paths replaced with workspace paths) @@ -307,10 +306,8 @@ export async function applyTextEdits(filePath: string, textEdits: TextEdit[]): P // Read existing content or start with empty string let content = ''; - let originalEol: import("../utils/eol-utils").EolSequence = '\n'; if (fs.existsSync(filePath)) { - const raw = fs.readFileSync(filePath, 'utf-8'); - [content, originalEol] = readAndNormalize(raw); + content = fs.readFileSync(filePath, 'utf-8'); } // If file is new and empty, ensure at least empty content @@ -337,8 +334,8 @@ export async function applyTextEdits(filePath: string, textEdits: TextEdit[]): P result = result.substring(0, edit.start) + edit.newText + result.substring(edit.end); } - // Write the modified content back to the file, restoring original line endings - fs.writeFileSync(filePath, restoreEol(result, originalEol), 'utf-8'); + // Write the modified content back to the file + fs.writeFileSync(filePath, result, 'utf-8'); } catch (error) { console.error(`[applyTextEdits] Error applying edits to ${filePath}:`, error); throw error; @@ -354,7 +351,7 @@ export async function applyTextEdits(filePath: string, textEdits: TextEdit[]): P export function formatCodeContext(codeContext: CodeContext, tempProjectPath: string): string { const absolutePath = path.join(tempProjectPath, codeContext.filePath); - const fileContent = normalizeToLf(fs.readFileSync(absolutePath, "utf-8")); + const fileContent = fs.readFileSync(absolutePath, "utf-8"); const lines = fileContent.split("\n"); const totalLines = lines.length; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/ask/index.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/ask/index.ts index 7dad0e2cee..96962a9c66 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/ask/index.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/ask/index.ts @@ -121,8 +121,8 @@ async function fetchDocumentationFromVectorStore(query: string): Promise { - const { libraries: selectedLibs } = await getSelectedLibraries(query, GenerationType.CODE_GENERATION); - const { libraries: relevantTrimmedFuncs } = await selectRequiredFunctions(query, selectedLibs, GenerationType.CODE_GENERATION); + const selectedLibs: string[] = await getSelectedLibraries(query, GenerationType.CODE_GENERATION); + const relevantTrimmedFuncs: Library[] = await selectRequiredFunctions(query, selectedLibs, GenerationType.CODE_GENERATION); const apiDocs: LibraryWithUrl[] = relevantTrimmedFuncs.map(lib => { return { ...lib, diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/compaction-manager.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/compaction-manager.ts new file mode 100644 index 0000000000..f264790d04 --- /dev/null +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/compaction-manager.ts @@ -0,0 +1,414 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { CompactionEngine, CompactionMetadata, CompactionResult, ProjectStateContext } from '@wso2/copilot-utilities/compaction'; +import { generateText, LanguageModel } from 'ai'; +import * as path from 'path'; +import * as fs from 'fs'; +import { chatStateStorage } from '../../views/ai-panel/chatStateStorage'; + +/** + * CompactionManager — Ballerina-specific wrapper around CompactionEngine. + * + * Responsibilities: + * - Constructs a CompactionEngine with Ballerina-specific token counting + * - Bridges the compaction results into chatStateStorage (clear old generations, + * create synthetic compacted generation) + * - Backs up pre-compaction history to disk (M07) + * - Provides bindModel() for M02: model instance reuse + * + * Design note (M02): This class does NOT resolve its own model. + * The caller (AgentExecutor for auto-compact, rpc-manager for manual compact) + * passes in the already-authenticated model via bindModel() or manualCompact(). + * This avoids duplicate getAnthropicClient() calls and provider-specific ID mismatches. + */ +export class CompactionManager { + private engine: CompactionEngine; + + constructor() { + this.engine = new CompactionEngine({ + modelConfig: { + maxContextWindow: 200_000, + maxOutputTokens: 8_192, // C02: Must match AgentExecutor.ts streamText maxOutputTokens + autoCompactBuffer: 13_000, + }, + // Token counting using character estimation (fallback when no actual usage data) + tokenCountCallback: async (messages: any[]) => { + const totalChars = messages.reduce((sum, msg) => { + const content = typeof msg.content === 'string' + ? msg.content + : JSON.stringify(msg.content); + return sum + content.length; + }, 0); + return Math.ceil(totalChars / 4); // ~4 chars per token + }, + // M02: summarizationCallback is NOT set here — bound per-call via bindModel() + }); + } + + /** + * Expose the underlying CompactionEngine for CompactionGuard (Section 9). + */ + getEngine(): CompactionEngine { + return this.engine; + } + + /** + * C04: Update token estimation context with actual usage data from streamText. + * Call this after each step response to improve threshold accuracy. + */ + updateTokenContext( + actualInputTokens: number, + systemPromptEstimate: number, + toolDefinitionsEstimate: number + ): void { + this.engine.updateTokenContext({ + lastActualInputTokens: actualInputTokens, + systemPromptTokenEstimate: systemPromptEstimate, + toolDefinitionsTokenEstimate: toolDefinitionsEstimate, + }); + } + + /** + * M02: Bind the summarization callback with the caller's authenticated model instance. + * + * Must be called before checkAndCompact() or the engine will refuse to compact. + * This reuses the SAME model instance as the agent, avoiding: + * - Duplicate getAnthropicClient() calls (double rate limit exposure) + * - Concurrent token refresh in fetchWithAuth + * - Provider-specific model ID mismatches (Bedrock ARN, Vertex AI) + */ + bindModel(model: LanguageModel): void { + this.engine.setSummarizationCallback( + async (messages: any[], systemPrompt: string, abortSignal?: AbortSignal) => { + console.log(`[CompactionManager] Calling summarization LLM with ${messages.length} messages`); + console.log(`[CompactionManager] System prompt length: ${systemPrompt.length} chars`); + console.log(`[CompactionManager] Message roles: ${messages.map((m: any) => m.role).join(', ')}`); + try { + const result = await generateText({ + model, + maxOutputTokens: 8192, + temperature: 0, + system: systemPrompt, + messages, + abortSignal, // M05: Propagate abort + }); + console.log(`[CompactionManager] LLM returned ${result.text?.length ?? 0} chars, finishReason: ${result.finishReason}`); + if (!result.text || result.text.trim().length === 0) { + console.error(`[CompactionManager] LLM returned empty text! finishReason: ${result.finishReason}, usage: ${JSON.stringify(result.usage)}`); + } + return result.text; + } catch (error) { + console.error(`[CompactionManager] generateText threw:`, error); + throw error; + } + } + ); + } + + /** + * Auto-compaction check: called before streamText to prevent between-turn overflow. + * Does nothing if context is below threshold or if compaction fails (C10). + * + * @param workspaceId - Workspace path (project root) + * @param threadId - Thread identifier (usually 'default') + * @param projectState - Current agent project state (C09) + * @param abortSignal - M05: Propagated abort signal + */ + async checkAndCompact( + workspaceId: string, + threadId: string, + projectState?: ProjectStateContext, + abortSignal?: AbortSignal, + eventHandler?: (event: any) => void, + currentTurnMessages?: any[] + ): Promise { + const history = chatStateStorage.getChatHistoryForLLM(workspaceId, threadId); + if (!history || history.length === 0) { + return; + } + + const messagesToEstimate = currentTurnMessages + ? [...history, ...currentTurnMessages] + : history; + + const shouldCompact = await this.engine.shouldCompact(messagesToEstimate); + if (!shouldCompact) { + return; + } + + // M02: Ensure bindModel() was called before reaching here + if (!this.engine.hasSummarizationCallback()) { + console.error('[CompactionManager] No model bound — call bindModel() before checkAndCompact()'); + return; + } + + eventHandler?.({ type: 'compaction_start' }); + + // C09: Pass project state; M05: forward abortSignal; C10: handle failures + const result = await this.engine.compact(history, { + mode: 'auto', + projectState, + abortSignal, + }); + + if (!result.success) { + console.warn('[CompactionManager] Auto-compaction failed, continuing with uncompacted history'); + eventHandler?.({ type: 'compaction_failed', reason: 'Auto-compaction failed' }); + return; + } + + await this.replaceThreadHistory(workspaceId, threadId, result.compactedMessages, result.metadata); + + eventHandler?.({ type: 'compaction_end' }); + + // Emit usage_metrics so the context usage widget reflects the reduced token count + if (eventHandler && result.compactedTokens != null) { + eventHandler({ + type: 'usage_metrics', + usage: { + inputTokens: result.compactedTokens, + cacheCreationInputTokens: 0, + cacheReadInputTokens: 0, + outputTokens: 0, + }, + }); + } + + console.log( + `[CompactionManager] ${result.originalTokens} → ${result.compactedTokens} tokens ` + + `(${result.reductionPercentage.toFixed(1)}% reduction, ${result.retriesUsed} retries)` + ); + } + + /** + * Manual compaction: called from the RPC manager when user triggers /compact. + */ + async manualCompact( + workspaceId: string, + threadId: string, + model: LanguageModel, + userInstructions?: string, + projectState?: ProjectStateContext + ): Promise { + const history = chatStateStorage.getChatHistoryForLLM(workspaceId, threadId); + if (!history || history.length === 0) { + throw new Error('No conversation history to compact'); + } + + // M02: Bind the model for this manual compaction call + this.bindModel(model); + + const result = await this.engine.compact(history, { + mode: 'manual', + customInstructions: userInstructions, + projectState, + }); + + if (!result.success) { + throw new Error('Manual compaction failed'); + } + + await this.replaceThreadHistory(workspaceId, threadId, result.compactedMessages, result.metadata); + + return result; + } + + /** + * Get current token status for the thread. + */ + async getTokenStatus(workspaceId: string, threadId: string) { + const history = chatStateStorage.getChatHistoryForLLM(workspaceId, threadId); + if (!history) { + return null; + } + return this.engine.getTokenStatus(history); + } + + /** + * Appends `.ballerina/copilot/compaction-backups/` to the project's .gitignore + * if the file exists and the entry is not already present. + */ + private async ensureGitignoreEntry(workspaceId: string): Promise { + const gitignorePath = path.join(workspaceId, '.gitignore'); + const entry = '.ballerina/'; + try { + const existing = await fs.promises.readFile(gitignorePath, 'utf-8'); + if (existing.split('\n').some(line => line.trim() === entry)) { + return; // already present + } + const suffix = existing.endsWith('\n') ? '' : '\n'; + await fs.promises.appendFile(gitignorePath, `${suffix}${entry}\n`, 'utf-8'); + } catch (error: any) { + if (error?.code === 'ENOENT') { + // .gitignore does not exist — create it with the entry + await fs.promises.writeFile(gitignorePath, `${entry}\n`, 'utf-8'); + } else { + console.error(`[CompactionManager] Failed to update .gitignore:`, error); + } + } + } + + /** + * M07: Save pre-compaction thread history to a backup file. + * Stored in `.ballerina/copilot/compaction-backups/` under the project directory. + */ + private async backupPreCompactionHistory( + workspaceId: string, + threadId: string + ): Promise<{ backupPath: string; generationIds: string[] }> { + const thread = chatStateStorage.getOrCreateThread(workspaceId, threadId); + const generationIds = thread.generations.map((g: any) => g.id); + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const backupDir = path.join(workspaceId, '.ballerina', 'copilot', 'compaction-backups'); + const backupPath = path.join(backupDir, `${threadId}-${timestamp}.json`); + + await fs.promises.mkdir(backupDir, { recursive: true }); + await this.ensureGitignoreEntry(workspaceId); + + const backupData = { + backupVersion: 1, + threadId, + workspaceId, + createdAt: Date.now(), + generationCount: thread.generations.length, + generations: thread.generations, + }; + + await fs.promises.writeFile(backupPath, JSON.stringify(backupData, null, 2), 'utf-8'); + console.log(`[CompactionManager] Backed up ${generationIds.length} generations to ${backupPath}`); + + return { backupPath, generationIds }; + } + + /** + * Replace entire thread history with compacted messages. + * + * Steps: + * 1. M07: Back up pre-compaction history to disk + * 2. Clear all old generations from the thread + * 3. Create a synthetic 'compacted' generation with the new messages + * 4. C15: Store compaction metadata on the generation + * + * @param preserveGenerationId - If provided (mid-stream case), the in-progress generation + * with this ID is re-added after clearing so it can be populated when the turn ends. + */ + private async replaceThreadHistory( + workspaceId: string, + threadId: string, + compactedMessages: any[], + metadata?: CompactionMetadata, + preserveGenerationId?: string + ): Promise { + // M07: Backup before clearing + const { backupPath, generationIds } = await this.backupPreCompactionHistory(workspaceId, threadId); + + if (metadata) { + metadata.backupPath = backupPath; + metadata.compactedGenerationIds = generationIds; + } + + const thread = chatStateStorage.getOrCreateThread(workspaceId, threadId); + + // Capture in-progress generation before clearing (mid-stream case only) + const preservedGen = preserveGenerationId + ? thread.generations.find(g => g.id === preserveGenerationId) + : undefined; + + // Clear old generations (direct mutation — getOrCreateThread returns live reference) + thread.generations = []; + + const generationId = 'compact-' + Date.now(); + + // Create synthetic generation to hold the compacted history. + // skipCheckpoint=true: compacted generations must not create spurious checkpoints. + chatStateStorage.addGeneration( + workspaceId, + threadId, + '[Compacted History]', + { isPlanMode: false, generationType: 'agent' }, + generationId, + true + ); + + // Update with compacted messages, mark accepted so it's included in future LLM context + chatStateStorage.updateGeneration( + workspaceId, + threadId, + generationId, + { + modelMessages: compactedMessages, + reviewState: { status: 'accepted', modifiedFiles: [] }, + // C15 + M07: Store compaction metadata (including backup path) + metadata: { + isPlanMode: false, + generationType: 'agent', + compactionMetadata: metadata ? { + ...metadata, + isCompactedGeneration: true, + } : undefined, + }, + } + ); + + // Re-add the in-progress generation after the compacted summary (mid-stream case). + // Its modelMessages are still empty here — they'll be populated by updateGeneration + // when the turn ends via AgentExecutor's stream handler. + if (preservedGen) { + thread.generations.push(preservedGen); + console.log(`[CompactionManager] Re-added in-progress generation: ${preserveGenerationId}`); + } + + console.log( + `[CompactionManager] Replaced ${generationIds.length} generations with compacted history. ` + + `Backup: ${backupPath}` + ); + } + + /** + * Called by CompactionGuard after a successful mid-stream compaction. + * Persists the compacted summary of OLD history to chatStateStorage, + * preserving the current in-progress generation so it can be populated + * normally when the turn ends. + * + * This eliminates double compaction: because storage is updated here, + * the next turn's pre-turn compaction will see an already-compacted + * history and won't fire again. + * + * @param currentGenerationId - The in-progress generation ID to preserve + * @param compactedMessages - compactionResult.compactedMessages (summary only, + * NOT the full array with recentMessages — those are saved when the turn ends) + */ + async persistMidStreamCompaction( + workspaceId: string, + threadId: string, + currentGenerationId: string, + compactedMessages: any[], + metadata?: CompactionMetadata + ): Promise { + await this.replaceThreadHistory(workspaceId, threadId, compactedMessages, metadata, currentGenerationId); + console.log(`[CompactionManager] Mid-stream compaction persisted to storage. Preserved generation: ${currentGenerationId}`); + } +} + +/** + * Singleton instance — shared across AgentExecutor and RPC manager. + * This ensures a single engine with consistent token estimation state. + */ +export const compactionManager = new CompactionManager(); diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/context-api.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/context-api.ts index 9724fc253a..5cf644a8ea 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/context-api.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/context-api.ts @@ -14,10 +14,9 @@ // specific language governing permissions and limitations // under the License. -import { generateObject, generateText, ModelMessage } from "ai"; +import { generateText, ModelMessage } from "ai"; import { getAnthropicClient, ANTHROPIC_SONNET_4 } from "../utils/ai-client"; import { ContentPart, DataMapperRequest, DataMapperResponse, FileData, FileTypeHandler, ProcessType } from "./types"; -import { MappingInstructionSchema } from "./schema"; // Maybe have better names and types? @@ -38,7 +37,7 @@ async function processFiles(files: FileData[], processType: ProcessType, isRequi const fileContent = isRequirementAnalysis ? getRequirementsContent(message) - : extractBallerinaCode(message); + : extractBallerinaCode(message, processType); return { fileContent }; } catch (error) { @@ -46,13 +45,21 @@ async function processFiles(files: FileData[], processType: ProcessType, isRequi } } -// Extract Ballerina code from the response -function extractBallerinaCode(message: string): string { - const ballerinaCodeMatch = message.match(/([\s\S]*?)<\/ballerina_code>/); - if (ballerinaCodeMatch) { - return ballerinaCodeMatch[1].trim(); +// Extract Ballerina code or mapping fields from the response +function extractBallerinaCode(message: string, processType: ProcessType): string { + if (processType === "records") { + const ballerinaCodeMatch = message.match(/([\s\S]*?)<\/ballerina_code>/); + if (ballerinaCodeMatch) { + return ballerinaCodeMatch[1].trim(); + } + console.log("No Ballerina code found."); + } else { + const mappingFieldsMatch = message.match(/([\s\S]*?)<\/mapping_fields>/); + if (mappingFieldsMatch) { + return mappingFieldsMatch[1].trim(); + } + console.log("No mapping fields found."); } - console.log("No Ballerina code found."); return ""; } @@ -149,6 +156,7 @@ Important: - Do not take any assumptions based on data types and mappings. - Do not include any mappings you are unsure about. - Consider all provided information, including comments and conditions. + - Final output has only Ballerina code within tags. Please follow these instructions carefully: @@ -166,34 +174,66 @@ Please follow these instructions carefully: 12. Document any nested mappings, operations, or data transformations required for the mapping. 13. Do not map anything if you are unsure about the correct mapping. -Before generating the output, analyze the content thoroughly: -- List all input fields and their exact data types -- List all output fields and their exact data types -- Identify direct field mappings, fields requiring transformations, and complex mappings involving multiple input fields -- Note any array-to-array mappings and nested field mappings -- Verify that all provided information has been considered +Before generating the final output, wrap your thought process inside tags: + +1. Analyze the content: + - List all input fields and their exact data types (e.g., 1.1 field1: SI, 1.2 field2: int ). + - List all output fields and their exact data types (e.g., 1.1 field1: SI, 1.2 field2: int ) + - Note any comments, conditions, or additional information provided + +2. Plan the mappings: + - Identify direct field mappings + - Identify fields requiring transformations or type conversions + - Identify and list complex mappings involving multiple input fields + - Note any array to array mappings + - Consider nested field mappings -Example of expected field values: +3. Identify complex transformations: + - List and describe any complex transformations or mappings + - Provide examples of how these transformations would work + +4. Review the mapping plan: + - Ensure all input fields are accounted for + - Check for any ambiguities or uncertainties + - Verify that all provided information has been considered + +After your analysis, provide the mapping in the following JSON format in tags: { - "mapping_fields": [ - { - "output_field": "id", + "mapping_fields": { + "output_field_name": { + "MAPPING_TIP": "Describe the mapping, including any transformations or special considerations", + "INPUT_FIELDS": ["input_field_name_1", "input_field_name_2", "input_field_name_3", ...] // Add more input fields as needed + }, + // Add more output fields as needed + } +} + +Simple example for the required format: + +{ + "mapping_fields" : { + "id": { "MAPPING_TIP": "Direct mapping from Person.id to Student.id", "INPUT_FIELDS": ["person.id"] }, - { - "output_field": "name", + "name": { "MAPPING_TIP": "Direct mapping from Person.name to Student.name", "INPUT_FIELDS": ["person.name"] }, - { - "output_field": "weight", + "age": { + "MAPPING_TIP": "Direct mapping from Person.age to Student.age", + "INPUT_FIELDS": ["person.age"] + }, + "weight": { "MAPPING_TIP": "Direct mapping from Person.weight to Student.weight with type conversion from string to float", "INPUT_FIELDS": ["person.weight"] } - ] -}`; + } +} + +Generate only Ballerina code with in tags based on the provided content. +`; } function getRecordsPrompt(): string { @@ -292,6 +332,8 @@ Write your comprehensive explanation as a text. function getPromptForProcessType(processType: ProcessType): string { switch (processType) { + case "mapping_instruction": + return getMappingInstructionPrompt(); case "records": return getRecordsPrompt(); case "requirements": @@ -301,8 +343,11 @@ function getPromptForProcessType(processType: ProcessType): string { } } -// Build Claude messages from files and a prompt -function buildClaudeMessages(files: FileData[], promptText: string): ModelMessage[] { +// Process files with Claude (handles both single and multiple files) +async function processFilesWithClaude(files: FileData[], processType: ProcessType): Promise { + const promptText = getPromptForProcessType(processType); + + // Build content array with all files const contentParts: Array = []; const includeFileName = files.length > 1; @@ -310,48 +355,36 @@ function buildClaudeMessages(files: FileData[], promptText: string): ModelMessag contentParts.push(convertFileToContentPart(file, includeFileName)); } - contentParts.push({ type: "text", text: promptText }); - - return [{ role: "user", content: contentParts }]; -} + // Add the prompt at the end + contentParts.push({ + type: "text", + text: promptText + }); -// Process files with Claude using generateText (for free-form text responses) -async function processFilesWithClaude(files: FileData[], processType: ProcessType): Promise { - const messages = buildClaudeMessages(files, getPromptForProcessType(processType)); + const messages: ModelMessage[] = [ + { + role: "user", + content: contentParts + } + ]; const { text } = await generateText({ model: await getAnthropicClient(ANTHROPIC_SONNET_4), maxOutputTokens: 8192, temperature: 0, - messages, + messages: messages, abortSignal: new AbortController().signal }); return text; } -// Generate mapping instructions from files using structured output -export async function generateMappingInstructionFromFiles(files: FileData[]): Promise<{ mapping_fields: Record }> { - const messages = buildClaudeMessages(files, getMappingInstructionPrompt()); - - const { object } = await generateObject({ - model: await getAnthropicClient(ANTHROPIC_SONNET_4), - maxOutputTokens: 8192, - temperature: 0, - messages, - schema: MappingInstructionSchema, - abortSignal: new AbortController().signal, +// Utility functions for specific use cases +export async function generateMappingInstruction(input: { files: FileData[]; text?: string }): Promise { + return await processDataMapperInput({ + ...input, + processType: "mapping_instruction" }); - - // Transform array to record keyed by output_field - const mapping_fields: Record = {}; - for (const field of object.mapping_fields) { - mapping_fields[field.output_field] = { - MAPPING_TIP: field.MAPPING_TIP, - INPUT_FIELDS: field.INPUT_FIELDS, - }; - } - return { mapping_fields }; } export async function generateRecord(input: { files: FileData[]; text?: string }): Promise { diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/orchestrator.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/orchestrator.ts index f1f5387df3..552d073d43 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/orchestrator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/orchestrator.ts @@ -25,7 +25,7 @@ import { DMModelDiagnosticsResult, } from "./types"; import { GeneratedMappingSchema, RepairedMappingsSchema } from "./schema"; -import { DataMapperModelResponse, DMModel, Mapping, repairCodeRequest, SourceFile, ImportInfo, ProcessMappingParametersRequest, Command, MetadataWithAttachments, InlineMappingsSourceResult, ProcessContextTypeCreationRequest, ProjectImports, ImportStatements, TemplateId, GetModuleDirParams, TextEdit, DataMapperSourceRequest, AllDataMapperSourceRequest, DataMapperModelRequest, DeleteMappingRequest, CodeData, keywords } from "@wso2/ballerina-core"; +import { DataMapperModelResponse, DMModel, Mapping, repairCodeRequest, SourceFile, ImportInfo, ProcessMappingParametersRequest, Command, MetadataWithAttachments, InlineMappingsSourceResult, ProcessContextTypeCreationRequest, ProjectImports, ImportStatements, TemplateId, GetModuleDirParams, TextEdit, DataMapperSourceRequest, AllDataMapperSourceRequest, DataMapperModelRequest, DeleteMappingRequest, CodeData } from "@wso2/ballerina-core"; import { getDataMappingPrompt } from "./prompts/mapping-prompt"; import { getBallerinaCodeRepairPrompt } from "./prompts/repair-prompt"; import { CopilotEventHandler, createWebviewEventHandler, updateAndSaveChat } from "../utils/events"; @@ -128,8 +128,7 @@ async function generateAIMappings( JSON.stringify(dataModelStructure), JSON.stringify(existingUserMappings || []), JSON.stringify(userProvidedMappingHints || {}), - JSON.stringify(existingSubMappings || []), - keywords + JSON.stringify(existingSubMappings || []) ); const chatMessages: ModelMessage[] = [ diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/prompts/mapping-prompt.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/prompts/mapping-prompt.ts index d7e93f03ae..cd472c4406 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/prompts/mapping-prompt.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/prompts/mapping-prompt.ts @@ -17,7 +17,7 @@ /** * Generates the main data mapping prompt for AI */ -export function getDataMappingPrompt(DM_MODEL: string, userMappings: string, mappingTips: string, subMappings: string, reservedKeywords: string[]): string { +export function getDataMappingPrompt(DM_MODEL: string, userMappings: string, mappingTips: string, subMappings: string): string { return `You are a specialized code generation assistant for the Ballerina programming language. Your task is to generate syntactically correct Ballerina expressions that transform input data fields into output data fields based on provided specifications. Here is the data model schema that defines the structure and types: @@ -79,11 +79,6 @@ When generating mapping expressions, follow this strict order of priority: - Use the \`re\` template expression to create RegExp values: \`string:RegExp pattern = re \`[0-9]+\`;\` - Common functions: \`regexp:isFullMatch()\`, \`regexp:find()\`, \`regexp:findAll()\`, \`regexp:replace()\`, \`regexp:replaceAll()\`, \`regexp:split()\` -### Reserved Keywords -Ballerina has reserved keywords that **cannot be used as plain identifiers**. When a field name, variable name, or loop element variable coincides with a reserved keyword, you **MUST** prefix it with a single quote (\`'\`). - -Reserved keywords: ${reservedKeywords.map(k => `\`${k}\``).join(", ")} - ### Ballerina Syntax Requirements 1. Write syntactically correct Ballerina code without compilation errors 2. Use \`.toString()\` directly for type conversion to strings diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/schema.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/schema.ts index 0b5619b97b..ec44048a14 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/schema.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/data-mapper/schema.ts @@ -43,14 +43,5 @@ const RepairedMappingsSchema = z.object({ repairedMappings: z.array(RepairedMappingSchema), }); -// Schema for mapping instruction extraction from files (uses array since Anthropic API doesn't support dynamic keys) -const MappingInstructionSchema = z.object({ - mapping_fields: z.array(z.object({ - output_field: z.string(), - MAPPING_TIP: z.string(), - INPUT_FIELDS: z.array(z.string()), - })), -}); - // Export the schema for reuse -export { GeneratedMappingSchema, RepairedMappingsSchema, MappingInstructionSchema }; +export { GeneratedMappingSchema, RepairedMappingsSchema }; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/executors/base/AICommandExecutor.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/executors/base/AICommandExecutor.ts index 5f723d8ef4..da895febcf 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/executors/base/AICommandExecutor.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/executors/base/AICommandExecutor.ts @@ -21,7 +21,6 @@ import { CopilotEventHandler } from '../../utils/events'; import { chatStateStorage, ChatStateStorage } from '../../../../views/ai-panel/chatStateStorage'; import { getTempProject, cleanupTempProject } from '../../utils/project/temp-project'; import { getErrorMessage } from '../../utils/ai-utils'; -import { MigrationDebugLogger } from '../../migration/debug-logger'; /** * Unified configuration for all AI command executors @@ -39,14 +38,6 @@ export interface AICommandConfig { /** Command-specific parameters */ params: TParams; - /** - * Optional LLM model override. - * When provided, the executor uses this model instead of the default - * (Anthropic Sonnet via WSO2/direct key). This enables plugging in - * models from the VS Code Language Model API or other providers. - */ - model?: any; - /** Optional chat storage configuration */ chatStorage?: { projectRootPath: string; @@ -61,47 +52,6 @@ export interface AICommandConfig { /** Cleanup strategy: 'immediate' (DataMapper) or 'review' (Agent) */ cleanupStrategy: 'immediate' | 'review'; }; - - /** - * Optional callback invoked when the full `ModelMessage[]` array becomes - * available — either on successful completion or on abort (partial messages). - * Used by the wizard migration flow to persist conversation history to disk - * so it can be resumed later via AI Chat. - * - * @param messages The conversation messages from the Vercel AI SDK. - * @param status How the run ended: `'completed'`, `'aborted'`, or `'error'`. - */ - onMessagesAvailable?: (messages: any[], status: 'completed' | 'aborted' | 'error') => void; - - /** - * Optional per-execution tool configuration. - * Allows the caller to inject context-specific tool options without changing - * the base executor interface for every new feature. - */ - toolOptions?: { - /** Absolute path to the original migration source project (Mule, Tibco, etc.). */ - migrationSourcePath?: string; - }; - - /** - * Optional overrides for the agent loop limits. - * Defaults (for normal agent usage): maxSteps = 50, maxOutputTokens = 8192. - * Migration enhancement should use higher values because the agent needs to - * read source files, edit many large files, run diagnostics repeatedly, etc. - */ - agentLimits?: { - /** Maximum number of LLM ↔ tool roundtrips before the agent stops. */ - maxSteps?: number; - /** Maximum output tokens per LLM response. */ - maxOutputTokens?: number; - }; - - /** - * Optional debug logger for migration enhancement runs. - * When provided, `AgentExecutor` logs individual tool call durations and - * summaries to `.ballerina-ai-migration/debug.log` via this logger. - */ - debugLogger?: MigrationDebugLogger; } /** @@ -199,9 +149,6 @@ export abstract class AICommandExecutor { * Stage 1: Initialize workspace/thread in chat storage (if enabled) */ protected async initializeWorkspaceThread(): Promise { - if (!this.config.chatStorage) { - return; - } try { const { projectRootPath, threadId } = this.config.chatStorage; @@ -330,12 +277,9 @@ export abstract class AICommandExecutor { }); } - // Attempt cleanup on error, but never delete an existingTempPath — - // that is a real project directory supplied by the caller (e.g. migration - // enhancement), not a throwaway temp created by this executor. + // Attempt cleanup on error const tempProjectPath = this.config.executionContext.tempProjectPath; - const isExistingPath = this.config.lifecycle?.existingTempPath === tempProjectPath; - if (tempProjectPath && !isExistingPath && !process.env.AI_TEST_ENV) { + if (tempProjectPath && !process.env.AI_TEST_ENV) { try { await cleanupTempProject(tempProjectPath); } catch (cleanupError) { @@ -349,9 +293,6 @@ export abstract class AICommandExecutor { * @returns Array of chat messages, or empty array if storage disabled */ protected getChatHistory(): any[] { - if (!this.config.chatStorage) { - return []; - } const { projectRootPath, threadId } = this.config.chatStorage; return chatStateStorage.getChatHistoryForLLM(projectRootPath, threadId); } @@ -362,9 +303,6 @@ export abstract class AICommandExecutor { * @param metadata - Generation metadata (operation type, etc.) */ protected addGeneration(userPrompt: string, metadata: any): any { - if (!this.config.chatStorage) { - return undefined; - } const { projectRootPath, threadId } = this.config.chatStorage; return chatStateStorage.addGeneration( projectRootPath, diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/debug-logger.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/debug-logger.ts deleted file mode 100644 index d56a7463ba..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/debug-logger.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -import * as fs from "fs"; -import * as path from "path"; -import { AI_MIGRATION_DIR } from "./types"; - -const LOG_FILE_NAME = "debug.log"; -const MAX_SUMMARY_LENGTH = 200; - -/** - * Lightweight append-only logger that writes migration enhancement run - * milestones and tool call summaries to `.ballerina-ai-migration/debug.log`. - * - * Multiple runs accumulate in the same file — each run begins with a - * separator header so individual runs can be identified. - * - * All writes are synchronous and immediately flushed for crash-safety. - * Write failures are silently swallowed so the logger never disrupts the - * main enhancement flow. - */ -export class MigrationDebugLogger { - private readonly logFilePath: string; - - constructor(projectRoot: string, modelId: string) { - const logDir = path.join(projectRoot, AI_MIGRATION_DIR); - if (!fs.existsSync(logDir)) { - try { - fs.mkdirSync(logDir, { recursive: true }); - } catch { - // If we can't create the dir, writes below will silently fail. - } - } - this.logFilePath = path.join(logDir, LOG_FILE_NAME); - this._write(`\n=== Enhancement Run ${new Date().toISOString()} (model: ${modelId}) ===`); - } - - /** Record a high-level milestone (stage start/end, run completion, abort). */ - logMilestone(message: string): void { - this._write(`[${this._ts()}] [MILESTONE] ${message}`); - } - - /** - * Record a tool call result. - * @param toolName Name of the tool as registered with the agent. - * @param durationMs Wall-clock time from tool-call to tool-result. - * @param resultSummary Short human-readable summary of the result (max 200 chars). - */ - logToolCall(toolName: string, durationMs: number, resultSummary: string): void { - const safe = resultSummary ?? ""; - const summary = safe.length > MAX_SUMMARY_LENGTH - ? safe.substring(0, MAX_SUMMARY_LENGTH) + "…" - : safe; - this._write(`[${this._ts()}] [TOOL] ${toolName} — ${durationMs}ms — ${summary}`); - } - - /** Record an error with optional stack trace. */ - logError(context: string, error: unknown): void { - const msg = error instanceof Error - ? `${error.message}${error.stack ? `\n${error.stack}` : ""}` - : String(error); - this._write(`[${this._ts()}] [ERROR] ${context}: ${msg}`); - } - - /** Record a timeout event (e.g. LS diagnostics timeout). */ - logTimeout(context: string, timeoutMs: number): void { - this._write(`[${this._ts()}] [TIMEOUT] ${context} — timed out after ${timeoutMs}ms`); - } - - private _ts(): string { - return new Date().toISOString(); - } - - private _write(line: string): void { - try { - fs.appendFileSync(this.logFilePath, line + "\n", "utf8"); - } catch { - // Non-critical — swallow write errors silently. - } - } -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/history.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/history.ts deleted file mode 100644 index 014d930e72..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/history.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -import * as fs from "fs"; -import * as path from "path"; -import { ModelMessage } from "ai"; -import { AI_MIGRATION_DIR } from "./types"; - -/** Filename for the conversation history file inside AI_MIGRATION_DIR. */ -export const AI_MIG_HISTORY_FILENAME = "history.json"; - -/** Returns the absolute path to the history file for the given project root. */ -function historyFilePath(projectRoot: string): string { - return path.join(projectRoot, AI_MIGRATION_DIR, AI_MIG_HISTORY_FILENAME); -} - -/** - * Shape of the persisted history file. - */ -export interface MigrationHistory { - /** Full conversation messages from the Vercel AI SDK `response.messages`. */ - messages: ModelMessage[]; - /** ISO timestamp of when the history was written. */ - savedAt: string; - /** Whether the agent completed successfully or was aborted. */ - completionStatus: "completed" | "aborted" | "error"; -} - -// --------------------------------------------------------------------------- -// Public API -// --------------------------------------------------------------------------- - -/** - * Persists the conversation history to disk. - * - * @param projectRoot Absolute path to the migrated project root. - * @param messages The `ModelMessage[]` array from the AI SDK response. - * @param status How the agent run ended. - */ -export function saveAgentHistory( - projectRoot: string, - messages: ModelMessage[], - status: MigrationHistory["completionStatus"], -): void { - const filePath = historyFilePath(projectRoot); - const dir = path.dirname(filePath); - const data: MigrationHistory = { - messages, - savedAt: new Date().toISOString(), - completionStatus: status, - }; - try { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8"); - console.log(`[MigrationHistory] Saved ${messages.length} messages (${status}) to ${filePath}`); - } catch (err) { - console.error("[MigrationHistory] Failed to save history:", err); - } -} - -/** - * Loads the persisted conversation history from disk. - * - * @param projectRoot Absolute path to the migrated project root. - * @returns The history object, or `null` if no history file exists. - */ -export function loadAgentHistory(projectRoot: string): MigrationHistory | null { - const filePath = historyFilePath(projectRoot); - try { - if (!fs.existsSync(filePath)) { - return null; - } - const raw = fs.readFileSync(filePath, "utf8"); - return JSON.parse(raw) as MigrationHistory; - } catch (err) { - console.error("[MigrationHistory] Failed to load history:", err); - return null; - } -} - -/** - * Removes the persisted history file (e.g. after enhancement is fully complete - * and the user no longer needs to resume). - * - * @param projectRoot Absolute path to the migrated project root. - */ -export function clearAgentHistory(projectRoot: string): void { - const filePath = historyFilePath(projectRoot); - try { - if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath); - console.log(`[MigrationHistory] Cleared history at ${filePath}`); - } - } catch (err) { - console.error("[MigrationHistory] Failed to clear history:", err); - } -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/orchestrator.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/orchestrator.ts deleted file mode 100644 index 553f8d939a..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/orchestrator.ts +++ /dev/null @@ -1,1338 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -import * as fs from "fs"; -import * as path from "path"; -import { AIMachineEventType, Command, ChatNotify } from "@wso2/ballerina-core"; -import { commands, EventEmitter, Uri, window, workspace } from "vscode"; -import { extension } from "../../../BalExtensionContext"; -import { StateMachine } from "../../../stateMachine"; -import { AIStateMachine, openAIPanelWithPrompt } from "../../../views/ai-panel/aiMachine"; -import { AgentExecutor } from "../agent/AgentExecutor"; -import { AICommandConfig } from "../executors/base/AICommandExecutor"; -import { createMigrationEventHandler, createVisualizerMigrationEventHandler, createAIPanelMigrationEventHandler } from "../utils/events"; -import { sendVisualizerMigrationNotification, sendAIPanelNotification } from "../utils/ai-utils"; -import { getEnhancementStages, getPerProjectEnhancementStages, getWorkspaceValidationStage, getResumePreamble, EnhancementStage } from "./prompts"; -import { MigrationDebugLogger } from "./debug-logger"; -import { TranscriptWriter } from "./transcript-writer"; -import { getWorkspaceTomlValues } from "../../../utils"; -import { setMigrationEnhancementActive } from "../../../utils/source-utils"; - -// ── Wizard streaming emitter – exposed via extension.ts exports ────────────── -const _wizardChatEmitter = new EventEmitter(); -/** `vscode.Event` subscribed by the wi-extension to relay streaming events. */ -export const onWizardChatNotify = _wizardChatEmitter.event; -import { - AI_ENHANCE_TOML_FILENAME, - AI_MIGRATION_DIR, - AI_SUMMARY_FILENAME, - ActiveMigrationSessionLocal, - EnhanceTomlData, - MIGRATION_PROJECT_ROOT_KEY, - PackageEnhancementResult, - PENDING_ENHANCEMENT_TTL_MS, - PENDING_MIGRATION_ENHANCEMENT_KEY, - PendingMigrationEnhancement, -} from "./types"; - -// =========================================================================== -// Active Session – in-memory state for the current window session -// =========================================================================== - -/** Module-level session state – reset on each extension host lifecycle. */ -let _activeSession: ActiveMigrationSessionLocal = { isActive: false, aiFeatureUsed: false, fullyEnhanced: true }; - -/** `true` when enhancement was triggered from AI Chat (not the wizard). Routes events to the AI Panel. */ -let _enhancementFromAIChat = false; - -/** `true` while `runWizardMigrationEnhancement` is executing in AI Chat mode. Used to route the abort handler. */ -let _runningFromAIChat = false; - -// =========================================================================== -// Toml helpers – `.ai-migrate-enhance.toml` -// =========================================================================== - -/** - * Reads and parses the `state.toml` from `.ballerina-ai-migration/` inside the given directory. - * Returns `null` if the file does not exist or cannot be parsed. - */ -export function readEnhanceToml(projectRoot: string): EnhanceTomlData | null { - try { - const filePath = path.join(projectRoot, AI_MIGRATION_DIR, AI_ENHANCE_TOML_FILENAME); - if (!fs.existsSync(filePath)) { - return null; - } - const content = fs.readFileSync(filePath, "utf8"); - const aiFeatureUsedMatch = content.match(/aiFeatureUsed\s*=\s*(true|false)/); - const fullyEnhancedMatch = content.match(/fullyEnhanced\s*=\s*(true|false)/); - const sourcePathMatch = content.match(/sourcePath\s*=\s*"([^"]+)"/); - const currentPackageMatch = content.match(/currentPackage\s*=\s*"([^"]+)"/); - const currentStageMatch = content.match(/currentStage\s*=\s*(\d+)/); - const multiProjectMatch = content.match(/multiProject\s*=\s*(true|false)/); - - // Parse completedPackages array - const completedPackagesMatch = content.match(/completedPackages\s*=\s*\[([^\]]*)\]/); - let completedPackages: string[] | undefined; - if (completedPackagesMatch?.[1]) { - completedPackages = completedPackagesMatch[1] - .split(",") - .map(s => s.trim().replace(/^"|"$/g, "")) - .filter(s => s.length > 0); - } - - return { - aiFeatureUsed: aiFeatureUsedMatch?.[1] === "true", - fullyEnhanced: fullyEnhancedMatch?.[1] === "true", - sourcePath: sourcePathMatch?.[1], - completedPackages, - currentPackage: currentPackageMatch?.[1], - currentStage: currentStageMatch ? parseInt(currentStageMatch[1], 10) : undefined, - multiProject: multiProjectMatch ? multiProjectMatch[1] === "true" : undefined, - }; - } catch { - return null; - } -} - -/** - * Writes the `state.toml` to `.ballerina-ai-migration/` inside the given directory. - */ -export function writeEnhanceToml( - projectRoot: string, - aiFeatureUsed: boolean, - fullyEnhanced: boolean, - sourcePath?: string, - completedPackages?: string[], - currentPackage?: string, - currentStage?: number, - multiProject?: boolean, -): void { - const dir = path.join(projectRoot, AI_MIGRATION_DIR); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - const filePath = path.join(dir, AI_ENHANCE_TOML_FILENAME); - let content = `[enhancement]\naiFeatureUsed = ${aiFeatureUsed}\nfullyEnhanced = ${fullyEnhanced}\n`; - if (sourcePath) { - content += `sourcePath = "${sourcePath}"\n`; - } - if (completedPackages && completedPackages.length > 0) { - const quoted = completedPackages.map(p => `"${p}"`).join(", "); - content += `completedPackages = [${quoted}]\n`; - } - if (currentPackage !== undefined) { - content += `currentPackage = "${currentPackage}"\n`; - } - if (currentStage !== undefined) { - content += `currentStage = ${currentStage}\n`; - } - // When multiProject is not explicitly provided, preserve the existing value from disk. - const effectiveMultiProject = multiProject !== undefined - ? multiProject - : readEnhanceToml(projectRoot)?.multiProject; - if (effectiveMultiProject !== undefined) { - content += `multiProject = ${effectiveMultiProject}\n`; - } - fs.writeFileSync(filePath, content); -} - -// =========================================================================== -// Session state access -// =========================================================================== - -/** - * Returns the current migration enhancement session state. - * Prefers the in-memory active session; falls back to reading the toml from the - * current workspace/project folder when no pipeline is actively running. - */ -export function getActiveMigrationSessionState(): ActiveMigrationSessionLocal { - console.log("[MigrationEnhancement] getActiveMigrationSessionState called, _activeSession:", JSON.stringify(_activeSession)); - - // If a pipeline is currently running in this window, return it immediately. - if (_activeSession.isActive) { - return { ..._activeSession }; - } - - // Determine the project root from the open workspace/project. - // _resolveCurrentProjectRoot returns the path that actually contains the toml. - const projectRoot = _resolveCurrentProjectRoot(); - console.log("[MigrationEnhancement] resolved projectRoot:", projectRoot); - if (!projectRoot) { - return { ..._activeSession }; - } - - const data = readEnhanceToml(projectRoot); - console.log("[MigrationEnhancement] readEnhanceToml result:", JSON.stringify(data)); - // If the toml is found, it is the source of truth. - if (data) { - return { - isActive: false, - aiFeatureUsed: data.aiFeatureUsed, - fullyEnhanced: data.fullyEnhanced, - }; - } - - // Toml not found – fall back to whatever _activeSession reports. - // checkAndRunPendingEnhancement may have set it to { fullyEnhanced: false } - // when aiFeatureUsed=false. If it's still the cold default (fullyEnhanced: true) - // the card will remain hidden – that is correct for non-migration projects. - return { ..._activeSession }; -} - -/** - * Marks the enhancement as complete by updating `fullyEnhanced = true` in the - * toml file and clearing the in-memory active session. - */ -export function markEnhancementComplete(): void { - const projectRoot = _resolveCurrentProjectRoot(); - if (projectRoot) { - const data = readEnhanceToml(projectRoot); - if (data) { - writeEnhanceToml(projectRoot, data.aiFeatureUsed, true, data.sourcePath); - } - } - _activeSession = { isActive: false, aiFeatureUsed: _activeSession.aiFeatureUsed, fullyEnhanced: true }; - console.log("[MigrationEnhancement] Enhancement marked as complete."); -} - -/** - * Previously seeded raw conversation history into chatStateStorage. - * Now a no-op — resume context is injected via the prompt preamble from summary.md. - * - * @returns Always `false` (no history seeded). - */ -export function seedMigrationHistoryIntoChatState(): boolean { - return false; -} - -/** - * Allows the user to start (or re-trigger) the enhancement pipeline from AI Chat. - * Updates the toml with `fullyEnhanced = false` and sets up the pipeline for AI Chat. - */ -export async function startMigrationEnhancement(): Promise { - const projectRoot = _resolveCurrentProjectRoot(); - if (projectRoot) { - const existing = readEnhanceToml(projectRoot); - writeEnhanceToml(projectRoot, true, false, existing?.sourcePath); - // Set wizard module variables so runWizardMigrationEnhancement() can be - // triggered by the AI Chat via wizardEnhancementReady(). - _wizardProjectRoot = projectRoot; - _wizardSourcePath = existing?.sourcePath; - } - - // Mark the session as active (panel is already open when called from AI Chat) - _activeSession = { isActive: true, aiFeatureUsed: true, fullyEnhanced: false }; - // Signal runWizardMigrationEnhancement to route events to AI Chat, not the wizard. - _enhancementFromAIChat = true; - console.log("[MigrationEnhancement] Enhancement started – pipeline ready for AI Chat trigger."); -} - -// =========================================================================== -// Schedule – called before vscode.openFolder so data survives the reload -// =========================================================================== - -/** - * Persists the enhancement state to VS Code global state so that - * `checkAndRunPendingEnhancement` can find the toml in the freshly opened window. - * - * Call this right before `commands.executeCommand('vscode.openFolder', …)`. - */ -export function scheduleMigrationEnhancement( - aiFeatureUsed: boolean, - projectRoot: string, - sourcePath?: string, -): void { - const entry: PendingMigrationEnhancement = { - aiFeatureUsed, - projectRoot, - timestamp: Date.now(), - sourcePath, - }; - extension.context.globalState.update(PENDING_MIGRATION_ENHANCEMENT_KEY, entry); - // Also persist the project root without expiry so getActiveMigrationSessionState - // can always resolve the toml even if the webview beats checkAndRunPendingEnhancement. - extension.context.globalState.update(MIGRATION_PROJECT_ROOT_KEY, projectRoot); - console.log(`[MigrationEnhancement] Scheduled enhancement (aiFeatureUsed=${aiFeatureUsed}) for project: ${projectRoot}`); -} - -// =========================================================================== -// Check & Run – called once the extension reaches the extensionReady state -// =========================================================================== - -/** - * Checks whether a migration enhancement was scheduled in a previous window. - * Reads the `state.toml` from the stored project root and decides what to do: - * - `aiFeatureUsed = true` + `fullyEnhanced = false` → show notification to resume via AI Chat - * - `aiFeatureUsed = false` → project opened without AI; session + notification shown - * - `fullyEnhanced = true` → nothing to do - * - * Safe to call on every activation – a no-op when there is no pending entry. - */ -export async function checkAndRunPendingEnhancement(): Promise { - const stored = extension.context.globalState.get( - PENDING_MIGRATION_ENHANCEMENT_KEY - ); - - if (!stored) { - return; - } - - // Consume the entry immediately to avoid re-running on later activations - await extension.context.globalState.update(PENDING_MIGRATION_ENHANCEMENT_KEY, undefined); - - // Discard stale entries (e.g. the user opened an unrelated workspace later) - const age = Date.now() - stored.timestamp; - if (age > PENDING_ENHANCEMENT_TTL_MS) { - console.log(`[MigrationEnhancement] Discarding stale entry (age: ${Math.round(age / 1000)}s)`); - return; - } - - // Read the toml from disk – it is the single source of truth - const data = readEnhanceToml(stored.projectRoot); - if (!data) { - console.warn(`[MigrationEnhancement] No toml found at: ${stored.projectRoot}`); - return; - } - - if (data.fullyEnhanced) { - console.log("[MigrationEnhancement] Enhancement already completed – nothing to do."); - return; - } - - console.log(`[MigrationEnhancement] Found pending enhancement (aiFeatureUsed=${data.aiFeatureUsed}) for: ${stored.projectRoot}`); - - if (data.aiFeatureUsed) { - // Set session state so other parts of the extension know about the migration - _activeSession = { isActive: false, aiFeatureUsed: true, fullyEnhanced: false }; - - const hasSummary = fs.existsSync( - path.join(stored.projectRoot, AI_MIGRATION_DIR, AI_SUMMARY_FILENAME), - ); - const message = hasSummary - ? "Migration AI enhancement was paused. You can resume it from AI Chat." - : "Migration project created. You can start AI enhancement from AI Chat."; - - const action = await window.showInformationMessage( - message, - "Open AI Chat" - ); - - if (action === "Open AI Chat") { - openAIPanelWithPrompt(); - } - } else { - // User skipped AI — expose the session so the card shows in AI Chat with - // a "Start AI Enhancement" button, and notify the user. - _activeSession = { isActive: false, aiFeatureUsed: false, fullyEnhanced: false }; - console.log("[MigrationEnhancement] AI not enabled at wizard – notification shown."); - const action = await window.showInformationMessage( - "Your migrated project is ready. Open AI Chat to run AI enhancement — it can resolve TODOs, fix build errors, and refine tests.", - "Open AI Chat" - ); - if (action === "Open AI Chat") { - openAIPanelWithPrompt(); - } - } -} - -// =========================================================================== -// Pipeline implementations -// =========================================================================== - -// =========================================================================== -// Workspace package detection -// =========================================================================== - -/** - * Reads the root `Ballerina.toml` of a workspace and returns the list of - * relative package paths declared in the `[workspace]` section. - * - * Returns `null` when the project is a single package (no `[workspace]` - * section or the file does not exist). - */ -async function getWorkspacePackagePaths(projectRoot: string): Promise { - const toml = await getWorkspaceTomlValues(projectRoot); - if (!toml?.workspace?.packages || toml.workspace.packages.length === 0) { - return null; - } - return toml.workspace.packages; -} - -/** - * Builds a lightweight manifest of all packages in the workspace. - * For each peer package (i.e. _not_ the currently-being-enhanced package) - * the manifest lists the package name and its public function / type names - * so the agent can emit correct `import` statements without needing the - * full source code in its context window. - */ -function buildCrossPackageManifest( - projectRoot: string, - allPackagePaths: string[], - currentPackagePath: string, -): string { - const entries: string[] = []; - for (const pkgPath of allPackagePaths) { - if (pkgPath === currentPackagePath) { - continue; - } - const fullPath = path.join(projectRoot, pkgPath); - const tomlPath = path.join(fullPath, "Ballerina.toml"); - - let pkgName = pkgPath; - if (fs.existsSync(tomlPath)) { - try { - const tomlContent = fs.readFileSync(tomlPath, "utf8"); - const nameMatch = tomlContent.match(/name\s*=\s*"([^"]+)"/); - if (nameMatch?.[1]) { - pkgName = nameMatch[1]; - } - } catch { /* keep directory name */ } - } - - // Collect public symbols from .bal files (excluding tests/) - const publicSymbols = collectPublicSymbols(fullPath); - entries.push(`- **${pkgName}** (\`${pkgPath}/\`): ${publicSymbols.length > 0 ? publicSymbols.join(", ") : "_no public symbols found_"}`); - } - - if (entries.length === 0) { - return ""; - } - return `\n\n## Other Packages in This Workspace\n\nYou can \`import ${entries.length > 0 ? "" : ""}\` to use symbols from these packages.\n\n${entries.join("\n")}`; -} - -/** - * Scans root .bal files (excluding tests/) for `public function`, `public type`, - * `public class`, `public const`, `public enum` declarations. - * Returns a short list of symbol names (max 30 per package to keep context lean). - */ -function collectPublicSymbols(packageDir: string): string[] { - const symbols: string[] = []; - const MAX_SYMBOLS = 30; - - if (!fs.existsSync(packageDir)) { - return symbols; - } - - try { - const files = fs.readdirSync(packageDir).filter(f => f.endsWith(".bal")); - for (const file of files) { - if (symbols.length >= MAX_SYMBOLS) { break; } - const content = fs.readFileSync(path.join(packageDir, file), "utf8"); - const regex = /^public\s+(function|type|class|const|enum)\s+(\w+)/gm; - let match: RegExpExecArray | null; - while ((match = regex.exec(content)) !== null) { - symbols.push(`\`${match[2]}\``); - if (symbols.length >= MAX_SYMBOLS) { break; } - } - } - } catch { /* best-effort */ } - - return symbols; -} - -/** - * Reads the `package.name` from a package's `Ballerina.toml`. - * Returns `null` if the file does not exist or cannot be parsed. - */ -function readPackageName(packageDir: string): string | null { - const tomlPath = path.join(packageDir, "Ballerina.toml"); - if (!fs.existsSync(tomlPath)) { return null; } - try { - const content = fs.readFileSync(tomlPath, "utf8"); - const match = content.match(/name\s*=\s*"([^"]+)"/); - return match?.[1] ?? null; - } catch { return null; } -} - -/** - * Emits a summary report of per-package enhancement results to the UI. - */ -function emitFinalReport( - eventHandler: (event: ChatNotify) => void, - results: PackageEnhancementResult[], -): void { - const succeeded = results.filter(r => r.success); - const failed = results.filter(r => !r.success); - - let report = `\n\n---\n\n## Enhancement Report\n\n`; - report += `**${succeeded.length}** of **${results.length}** packages enhanced successfully.\n\n`; - - if (failed.length > 0) { - report += `### Failed packages\n\n`; - for (const f of failed) { - report += `- \`${f.packagePath}\`: ${f.error}\n`; - } - report += `\n`; - } - - eventHandler({ type: "content_block", content: report }); -} - -/** - * If a `summary.md` exists for the project, prepend the resume preamble to - * the first stage's prompt so the agent knows this is a continuation. - */ -function injectResumePreamble(projectRoot: string, stages: EnhancementStage[]): void { - if (stages.length === 0) { return; } - const preamble = getResumePreamble(projectRoot); - if (preamble) { - stages[0] = { - ...stages[0], - prompt: preamble + "\n\n" + stages[0].prompt, - }; - console.log("[MigrationEnhancement] Resume preamble injected into first stage prompt."); - } -} - -// =========================================================================== -// Per-package stage runner – shared by wizard and migration-panel flows -// =========================================================================== - -interface StageRunnerOpts { - /** Absolute workspace root (or single-project root). */ - projectRoot: string; - /** Absolute path to the package being enhanced (equals `projectRoot` for single-project). */ - packagePath: string; - /** Absolute path to the original source project. */ - sourcePath?: string; - /** Enhancement stages to execute. */ - stages: EnhancementStage[]; - /** Callback that sends events to UI. */ - eventHandler: (event: ChatNotify) => void; - /** Shared abort controller — checked between stages. */ - abortController: AbortController; - /** Whether this is running from AI Chat (enables chat storage). */ - fromAIChat: boolean; - /** Prefix for generation IDs to avoid collisions. */ - stageIdPrefix: string; - /** Whether to use `existingTempPath` (wizard/review flow) vs `immediate` cleanup. */ - useExistingTempPath: boolean; - /** Optional debug logger — when provided, tool call timings are written to debug.log. */ - debugLogger?: MigrationDebugLogger; - /** Optional transcript writer — when provided, stage content is recorded to disk. */ - transcriptWriter?: TranscriptWriter; - /** Relative package path used for transcript file layout (empty string for single-package). */ - packageRelPath?: string; -} - -/** - * Runs the given enhancement stages sequentially for a single package path. - * Each stage gets a fresh `AgentExecutor` (fresh context window). - * - * When `useExistingTempPath` is true the agent edits files in-place at - * `packagePath` (wizard flow — files already on disk). - * When false (migration-panel flow) temp project cleanup is immediate. - */ -async function runStagesForPackage(opts: StageRunnerOpts): Promise { - const { - projectRoot, packagePath, sourcePath, stages, - eventHandler, abortController, fromAIChat, - stageIdPrefix, useExistingTempPath, debugLogger, - transcriptWriter, packageRelPath, - } = opts; - - for (let i = 0; i < stages.length; i++) { - const stage = stages[i]; - - if (abortController.signal.aborted) { - console.log(`[MigrationEnhancement] Aborted before ${stage.name}`); - break; - } - - // Start transcript recording for this stage - const isWorkspaceValidation = stageIdPrefix.includes("workspace-validation"); - if (transcriptWriter) { - transcriptWriter.startStage(packageRelPath ?? "", i, stage.name, isWorkspaceValidation); - } - - // Wrap the event handler to also capture content/tool events to transcript - const recordingHandler: typeof eventHandler = transcriptWriter - ? (event) => { - if (event.type === "content_block" && "content" in event) { - transcriptWriter!.appendContent(event.content); - } else if (event.type === "tool_call" && "toolName" in event) { - const inputSummary = event.toolInput - ? (typeof event.toolInput === "string" ? event.toolInput : JSON.stringify(event.toolInput)) - : undefined; - transcriptWriter!.appendToolCall(event.toolName, inputSummary); - } else if (event.type === "tool_result" && "toolName" in event) { - transcriptWriter!.appendToolResult(event.toolName, !event.failed); - } - eventHandler(event); - } - : eventHandler; - - recordingHandler({ - type: "content_block", - content: `\n\n---\n\n**Starting ${stage.name}** (${i + 1} of ${stages.length})\n\n`, - }); - - const stageGenId = `${stageIdPrefix}-stage${i + 1}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; - - // Scope the execution context to the single package so that - // `getProjectSource` only loads this package's source files. - const config: AICommandConfig = { - executionContext: { - projectPath: packagePath, - workspacePath: undefined, - }, - eventHandler: recordingHandler, - generationId: stageGenId, - abortController, - params: { - usecase: stage.prompt, - fileAttachmentContents: [], - isPlanMode: false, - }, - chatStorage: fromAIChat - ? { projectRootPath: projectRoot, threadId: "default", enabled: true } - : undefined, - lifecycle: useExistingTempPath - ? { existingTempPath: packagePath, cleanupStrategy: "review" as const } - : { cleanupStrategy: "immediate" as const }, - toolOptions: { - migrationSourcePath: sourcePath, - }, - agentLimits: stage.agentLimits, - debugLogger, - }; - - if (debugLogger) { - debugLogger.logMilestone(`${stage.name} — started (maxSteps: ${stage.agentLimits.maxSteps})`); - } - console.log(`[MigrationEnhancement] Running ${stage.name} (maxSteps: ${stage.agentLimits.maxSteps})`); - await new AgentExecutor(config).run(); - if (debugLogger) { - debugLogger.logMilestone(`${stage.name} — completed`); - } - console.log(`[MigrationEnhancement] ${stage.name} completed.`); - - recordingHandler({ - type: "content_block", - content: `\n\n**${stage.name} — Complete** ✅\n\n`, - }); - - if (transcriptWriter) { - transcriptWriter.finalizeStage(); - } - } -} - -// =========================================================================== -// Agent execution – called when the migration panel signals readiness -// =========================================================================== - -/** Module-level abort controller for the currently running migration agent. */ -let _migrationAbortController: AbortController | undefined; - -/** Module-level selected model ID (set by the UI's model selector). */ -let _selectedModelId: string = "wso2"; // default to WSO2 BI Copilot - -/** - * Update the selected model ID from the webview. - */ -export function setMigrationModelId(modelId: string): void { - _selectedModelId = modelId; - console.log(`[MigrationEnhancement] Model set to: ${modelId}`); -} - -/** - * Fired by `migrationPanelReady` RPC – creates an `AICommandConfig` with the - * migration event handler and runs `AgentExecutor` to stream results to the - * standalone Migration Panel. - * - * For multi-package workspaces the enhancement iterates over each package - * individually so that only one package's source is in the context window - * at a time. Single-package projects run the 4-stage pipeline directly. - */ -export async function runMigrationAgent(): Promise { - // Determine the project root (workspace folder) - const projectRoot = _resolveCurrentProjectRoot() - ?? workspace.workspaceFolders?.[0]?.uri.fsPath; - - if (!projectRoot) { - window.showErrorMessage("Migration enhancement: unable to determine project root."); - return; - } - - // Read source path from toml — the agent will access source files on demand - // via migration_source_list / migration_source_read tools. - const tomlData = readEnhanceToml(projectRoot); - const sourcePath = tomlData?.sourcePath; - const eventHandler = createMigrationEventHandler(Command.Agent); - _migrationAbortController = new AbortController(); - setMigrationEnhancementActive(true); - const debugLogger = new MigrationDebugLogger(projectRoot, _selectedModelId); - const transcriptWriter = new TranscriptWriter(projectRoot); - - try { - const packagePaths = await getWorkspacePackagePaths(projectRoot); - - if (packagePaths && packagePaths.length > 1) { - // ── Multi-package workspace ────────────────────────────────── - const completedPackages = new Set(tomlData?.completedPackages ?? []); - const results: PackageEnhancementResult[] = []; - let resumeInjected = false; - - console.log(`[MigrationEnhancement] Starting migration agent – ${packagePaths.length} packages, model: ${_selectedModelId}`); - debugLogger.logMilestone(`Run start — ${packagePaths.length} packages, model: ${_selectedModelId}, projectRoot: ${projectRoot}`); - eventHandler({ - type: "content_block", - content: `\n\n**Workspace contains ${packagePaths.length} packages.** Enhancing each package individually.\n\n`, - }); - - for (let pkgIdx = 0; pkgIdx < packagePaths.length; pkgIdx++) { - const pkgRelPath = packagePaths[pkgIdx]; - if (completedPackages.has(pkgRelPath)) { - eventHandler({ type: "content_block", content: `\n\n⏭️ Skipping already-completed package: \`${pkgRelPath}\`\n\n` }); - results.push({ packagePath: pkgRelPath, success: true }); - continue; - } - - if (_migrationAbortController.signal.aborted) { break; } - - const fullPkgPath = path.join(projectRoot, pkgRelPath); - const pkgName = readPackageName(fullPkgPath) ?? pkgRelPath; - const manifest = buildCrossPackageManifest(projectRoot, packagePaths, pkgRelPath); - const stages = getPerProjectEnhancementStages(pkgName, pkgRelPath, pkgIdx, packagePaths.length, manifest); - if (!resumeInjected) { - injectResumePreamble(projectRoot, stages); - resumeInjected = true; - } - - eventHandler({ type: "content_block", content: `\n\n## 📦 Package ${pkgIdx + 1}/${packagePaths.length}: \`${pkgName}\`\n\n` }); - debugLogger.logMilestone(`Package ${pkgIdx + 1}/${packagePaths.length}: ${pkgRelPath} — starting`); - - // Persist progress - writeEnhanceToml(projectRoot, tomlData?.aiFeatureUsed ?? true, false, sourcePath, [...completedPackages], pkgRelPath, 0); - - try { - await runStagesForPackage({ - projectRoot, packagePath: fullPkgPath, sourcePath, stages, - eventHandler, abortController: _migrationAbortController, - fromAIChat: false, stageIdPrefix: `migration-${pkgRelPath}`, - useExistingTempPath: false, debugLogger, - transcriptWriter, packageRelPath: pkgRelPath, - }); - completedPackages.add(pkgRelPath); - results.push({ packagePath: pkgRelPath, success: true }); - debugLogger.logMilestone(`Package ${pkgIdx + 1}/${packagePaths.length}: ${pkgRelPath} — completed`); - writeEnhanceToml(projectRoot, tomlData?.aiFeatureUsed ?? true, false, sourcePath, [...completedPackages]); - } catch (pkgError) { - if (_migrationAbortController.signal.aborted) { throw pkgError; } - const errMsg = pkgError instanceof Error ? pkgError.message : String(pkgError); - console.error(`[MigrationEnhancement] Package ${pkgRelPath} failed:`, pkgError); - debugLogger.logError(`Package ${pkgRelPath}`, pkgError); - eventHandler({ type: "content_block", content: `\n\n⚠️ Package \`${pkgRelPath}\` failed: ${errMsg}. Continuing to next package.\n\n` }); - results.push({ packagePath: pkgRelPath, success: false, error: errMsg }); - } - } - - if (!_migrationAbortController.signal.aborted) { - // ── Workspace-level validation after all per-package stages ── - if (results.some(r => r.success)) { - eventHandler({ type: "content_block", content: `\n\n## 🔍 Cross-Package Workspace Validation\n\n` }); - debugLogger.logMilestone("Workspace validation — starting"); - try { - await runStagesForPackage({ - projectRoot, packagePath: projectRoot, sourcePath, - stages: [getWorkspaceValidationStage(packagePaths.length)], - eventHandler, abortController: _migrationAbortController, - fromAIChat: false, stageIdPrefix: "migration-workspace-validation", - useExistingTempPath: false, debugLogger, - transcriptWriter, packageRelPath: "", - }); - debugLogger.logMilestone("Workspace validation — completed"); - } catch (wsError) { - if (!_migrationAbortController.signal.aborted) { - const errMsg = wsError instanceof Error ? wsError.message : String(wsError); - debugLogger.logError("workspace validation", wsError); - eventHandler({ type: "content_block", content: `\n\n⚠️ Workspace validation failed: ${errMsg}\n\n` }); - } - } - } - emitFinalReport(eventHandler, results); - debugLogger.logMilestone(`Run complete — ${results.filter(r => r.success).length}/${results.length} packages succeeded`); - // Write summary before marking complete - const updatedToml = readEnhanceToml(projectRoot); - if (updatedToml) { - const summary = transcriptWriter.generateSummary(updatedToml, results); - transcriptWriter.writeSummary(summary); - } - markEnhancementComplete(); - } else { - debugLogger.logMilestone("Run aborted by user"); - } - } else { - // ── Single-package project ─────────────────────────────────── - const stages = getEnhancementStages(); - injectResumePreamble(projectRoot, stages); - console.log(`[MigrationEnhancement] Starting migration agent (${stages.length} stages) – model: ${_selectedModelId}, sourcePath: ${sourcePath ?? 'none'}`); - debugLogger.logMilestone(`Run start — single package, model: ${_selectedModelId}, projectRoot: ${projectRoot}`); - - await runStagesForPackage({ - projectRoot, packagePath: projectRoot, sourcePath, stages, - eventHandler, abortController: _migrationAbortController, - fromAIChat: false, stageIdPrefix: "migration", - useExistingTempPath: false, debugLogger, - transcriptWriter, packageRelPath: "", - }); - - if (!_migrationAbortController.signal.aborted) { - debugLogger.logMilestone("Run complete — single package succeeded"); - // Write summary before marking complete - const updatedToml = readEnhanceToml(projectRoot); - if (updatedToml) { - const summary = transcriptWriter.generateSinglePackageSummary(updatedToml, false); - transcriptWriter.writeSummary(summary); - } - markEnhancementComplete(); - console.log("[MigrationEnhancement] Migration agent completed all stages successfully."); - } else { - debugLogger.logMilestone("Run aborted by user"); - } - } - } catch (error) { - if (_migrationAbortController.signal.aborted) { - console.log("[MigrationEnhancement] Migration agent was aborted by user."); - debugLogger.logMilestone("Run aborted by user (outer catch)"); - // Write summary on abort for resume context - const abortToml = readEnhanceToml(projectRoot); - if (abortToml) { - const summary = abortToml.multiProject - ? transcriptWriter.generateSummary(abortToml, []) - : transcriptWriter.generateSinglePackageSummary(abortToml, true); - transcriptWriter.writeSummary(summary); - } - } else { - console.error("[MigrationEnhancement] Migration agent error:", error); - debugLogger.logError("run", error); - } - } finally { - _migrationAbortController = undefined; - setMigrationEnhancementActive(false); - } -} - -/** - * Abort the currently running migration agent (if any). - */ -export function abortMigrationAgent(): void { - if (_migrationAbortController) { - _migrationAbortController.abort(); - console.log("[MigrationEnhancement] Abort signal sent to migration agent."); - } -} - -/** - * Previously returned raw history messages for display. - * Now returns empty — transcript files and summary.md replace history.json. - */ -export function getMigrationHistoryMessages(): Array<{ role: string; content: string }> { - return []; -} - -// =========================================================================== -// Wizard-level AI Enhancement -// =========================================================================== - -/** The project root created by the wizard that hasn't been opened yet. */ -let _wizardProjectRoot: string | undefined; -/** The original source path for the wizard enhancement. */ -let _wizardSourcePath: string | undefined; - -/** - * Called by the RPC manager after `createBIProjectFromMigration` returns the - * project root (when `aiFeatureUsed === true`). Stores the project - * root so the wizard enhancement can be kicked off from the webview. - */ -export function setWizardProjectRoot(projectRoot: string, sourcePath?: string): void { - console.log('[orchestrator] setWizardProjectRoot called. projectRoot:', projectRoot, 'sourcePath:', sourcePath); - _wizardProjectRoot = projectRoot; - _wizardSourcePath = sourcePath; -} - -/** - * Runs the AI enhancement agent against the wizard-created project. - * Streams events to the Visualizer webview instead of the Migration Panel. - * Called when the wizard enhancement step is ready. - */ -/** - * Ensures the user is authenticated before running the AI agent. - * If not authenticated, triggers the login flow and waits for completion. - * Returns true if authenticated, false if auth was cancelled or timed out. - */ -async function ensureAuthenticated(): Promise { - const AUTH_TIMEOUT_MS = 120_000; // 2 minutes - - const state = AIStateMachine.state(); - console.log('[orchestrator] ensureAuthenticated called. AIStateMachine state:', state); - if (state === "Authenticated") { - return true; - } - - // Tell the wizard UI we're signing in - const signingInMsg = { type: "content_block" as const, content: "Signing in to BI Copilot...\n\n" }; - sendVisualizerMigrationNotification(signingInMsg); - _wizardChatEmitter.fire(signingInMsg); - - // If state is Initialize, wait for it to resolve first - if (state === "Initialize") { - const resolved = await new Promise((resolve) => { - const timeout = setTimeout(() => { - sub.unsubscribe(); - resolve(false); - }, AUTH_TIMEOUT_MS); - - const sub = AIStateMachine.service().subscribe((snapshot) => { - const s = snapshot.value; - if (s === "Authenticated") { - clearTimeout(timeout); - sub.unsubscribe(); - resolve(true); - } else if (s === "Unauthenticated" || s === "Disabled") { - clearTimeout(timeout); - sub.unsubscribe(); - resolve(false); - } - }); - }); - - if (resolved) { - return true; - } - // Fell through to Unauthenticated – continue to trigger login below - if (AIStateMachine.state() === "Disabled") { - return false; - } - } - - // Trigger the login flow (same as AI Chat's LOGIN event) - AIStateMachine.sendEvent(AIMachineEventType.LOGIN); - - // Wait for Authenticated, or timeout / cancellation - return new Promise((resolve) => { - const timeout = setTimeout(() => { - sub.unsubscribe(); - resolve(false); - }, AUTH_TIMEOUT_MS); - - const sub = AIStateMachine.service().subscribe((snapshot) => { - const s = snapshot.value; - if (s === "Authenticated") { - clearTimeout(timeout); - sub.unsubscribe(); - resolve(true); - } else if (s === "Unauthenticated" || s === "Disabled") { - // Login was cancelled or failed - clearTimeout(timeout); - sub.unsubscribe(); - resolve(false); - } - }); - }); -} - -/** - * Returns true when the AI state machine is currently in the Authenticated state. - * Safe to call synchronously from any context. - */ -export function isAIAuthenticated(): boolean { - return AIStateMachine.state() === "Authenticated"; -} - -/** - * Triggers the BI Copilot browser sign-in flow and waits until the user is - * authenticated, cancels, or the 2-minute timeout elapses. - * - * Unlike `ensureAuthenticated`, this function does NOT emit any messages to a - * streaming UI — it is intended for use by the wizard before kicking off the - * enhancement pipeline. - * - * @returns `true` on successful authentication, `false` otherwise. - */ -export async function signInForAI(): Promise<{ success: boolean; error?: string }> { - const AUTH_TIMEOUT_MS = 120_000; - - const state = AIStateMachine.state(); - console.log('[orchestrator] signInForAI called. AIStateMachine state:', JSON.stringify(state)); - if (state === "Authenticated") { - return { success: true }; - } - if (state === "Disabled") { - console.log('[orchestrator] signInForAI: AI is disabled'); - return { success: false, error: "AI features are disabled." }; - } - - // Wait for the initialisation check to settle before acting. - if (state === "Initialize") { - console.log('[orchestrator] signInForAI: waiting for Initialize to settle...'); - const resolved = await new Promise((resolve) => { - const timeout = setTimeout(() => { sub.unsubscribe(); resolve(false); }, AUTH_TIMEOUT_MS); - const sub = AIStateMachine.service().subscribe((snapshot) => { - const s = snapshot.value; - if (s === "Authenticated") { clearTimeout(timeout); sub.unsubscribe(); resolve(true); } - else if (s === "Unauthenticated" || s === "Disabled") { clearTimeout(timeout); sub.unsubscribe(); resolve(false); } - }); - }); - console.log('[orchestrator] signInForAI: Initialize settled. resolved:', resolved, 'currentState:', JSON.stringify(AIStateMachine.state())); - if (resolved) { return { success: true }; } - if (AIStateMachine.state() === "Disabled") { return { success: false, error: "AI features are disabled." }; } - } - - // Trigger the browser-based login flow. - const preLoginState = AIStateMachine.state(); - console.log('[orchestrator] signInForAI: sending LOGIN event. preLoginState:', JSON.stringify(preLoginState)); - AIStateMachine.sendEvent(AIMachineEventType.LOGIN); - const postLoginState = AIStateMachine.state(); - console.log('[orchestrator] signInForAI: LOGIN sent. postLoginState:', JSON.stringify(postLoginState)); - - return new Promise<{ success: boolean; error?: string }>((resolve) => { - let settled = false; - const finish = (result: { success: boolean; error?: string }, reason: string) => { - if (settled) { return; } - settled = true; - clearTimeout(timeout); - sub.unsubscribe(); - console.log('[orchestrator] signInForAI resolved:', result.success, 'reason:', reason, 'finalState:', JSON.stringify(AIStateMachine.state())); - resolve(result); - }; - - const timeout = setTimeout(() => finish({ success: false, error: "Sign-in timed out. Please try again." }, 'timeout'), AUTH_TIMEOUT_MS); - - const sub = AIStateMachine.service().subscribe((snapshot) => { - const s = snapshot.value; - console.log('[orchestrator] signInForAI subscriber: state =', JSON.stringify(s)); - if (s === "Authenticated") { - finish({ success: true }, 'authenticated'); - } else if (s === "Unauthenticated" || s === "Disabled") { - const ctx = AIStateMachine.context(); - const errorMsg = ctx?.errorMessage || "Sign-in was cancelled or failed."; - finish({ success: false, error: errorMsg }, `state-${s}`); - } - }); - }); -} - -export async function runWizardMigrationEnhancement(): Promise { - console.log('[orchestrator] runWizardMigrationEnhancement called. _wizardProjectRoot:', _wizardProjectRoot); - const projectRoot = _wizardProjectRoot; - if (!projectRoot) { - console.error('[orchestrator] runWizardMigrationEnhancement: NO project root set!'); - window.showErrorMessage("Migration enhancement: no project root set for wizard enhancement."); - return; - } - - // Ensure the user is authenticated before running the AI agent - // Route events to AI Chat when triggered from there, otherwise to the wizard Visualizer. - const fromAIChat = _enhancementFromAIChat; - _enhancementFromAIChat = false; // consume the flag - _runningFromAIChat = fromAIChat; - const baseHandler = fromAIChat - ? createAIPanelMigrationEventHandler(Command.Agent) - : createVisualizerMigrationEventHandler(Command.Agent); - const eventHandler = (event: ChatNotify) => { - baseHandler(event); - _wizardChatEmitter.fire(event); - }; - eventHandler({ type: "start" }); - - const isAuthenticated = await ensureAuthenticated(); - if (!isAuthenticated) { - eventHandler({ - type: "error", - content: "Please sign in to WSO2 Integrator Copilot to use AI enhancement. Please retry the AI Enhancement step.", - }); - return; - } - - // Read the source path fresh from the toml so that any edits the user made - // to state.toml after the enhancement was scheduled are honoured. - // Fall back to the in-memory value captured at wizard/startMigrationEnhancement time. - const tomlData = readEnhanceToml(projectRoot); - const sourcePath = tomlData?.sourcePath ?? _wizardSourcePath; - - _migrationAbortController = new AbortController(); - setMigrationEnhancementActive(true); - const debugLogger = new MigrationDebugLogger(projectRoot, _selectedModelId); - const transcriptWriter = new TranscriptWriter(projectRoot); - - try { - const packagePaths = await getWorkspacePackagePaths(projectRoot); - - if (packagePaths && packagePaths.length > 1) { - // ── Multi-package workspace ────────────────────────────────── - const completedPackages = new Set(tomlData?.completedPackages ?? []); - const results: PackageEnhancementResult[] = []; - let resumeInjected = false; - - // Suppress per-stage "stop" events so the wizard doesn't prematurely - // show "completed" after the first package. The real final "stop" is - // emitted explicitly once all packages finish (or "abort" if cancelled). - const stageEventHandler = (event: ChatNotify) => { - if (event.type === "stop") { return; } - eventHandler(event); - }; - - console.log(`[MigrationEnhancement] Starting wizard migration agent – ${packagePaths.length} packages, projectRoot: ${projectRoot}`); - debugLogger.logMilestone(`Run start — ${packagePaths.length} packages (wizard), model: ${_selectedModelId}, projectRoot: ${projectRoot}`); - eventHandler({ - type: "content_block", - content: `\n\n**Workspace contains ${packagePaths.length} packages.** Enhancing each package individually.\n\n`, - }); - - for (let pkgIdx = 0; pkgIdx < packagePaths.length; pkgIdx++) { - const pkgRelPath = packagePaths[pkgIdx]; - if (completedPackages.has(pkgRelPath)) { - eventHandler({ type: "content_block", content: `\n\n⏭️ Skipping already-completed package: \`${pkgRelPath}\`\n\n` }); - results.push({ packagePath: pkgRelPath, success: true }); - continue; - } - - if (_migrationAbortController.signal.aborted) { break; } - - const fullPkgPath = path.join(projectRoot, pkgRelPath); - const pkgName = readPackageName(fullPkgPath) ?? pkgRelPath; - const manifest = buildCrossPackageManifest(projectRoot, packagePaths, pkgRelPath); - const stages = getPerProjectEnhancementStages(pkgName, pkgRelPath, pkgIdx, packagePaths.length, manifest); - if (!resumeInjected) { - injectResumePreamble(projectRoot, stages); - resumeInjected = true; - } - - eventHandler({ type: "content_block", content: `\n\n## 📦 Package ${pkgIdx + 1}/${packagePaths.length}: \`${pkgName}\`\n\n` }); - debugLogger.logMilestone(`Package ${pkgIdx + 1}/${packagePaths.length}: ${pkgRelPath} — starting`); - - // Persist progress so we can resume from this point - writeEnhanceToml( - projectRoot, tomlData?.aiFeatureUsed ?? true, false, sourcePath, - [...completedPackages], pkgRelPath, 0, true, - ); - - try { - await runStagesForPackage({ - projectRoot, packagePath: fullPkgPath, sourcePath, stages, - eventHandler: stageEventHandler, abortController: _migrationAbortController, - fromAIChat, stageIdPrefix: `wizard-${pkgRelPath}`, - useExistingTempPath: true, debugLogger, - transcriptWriter, packageRelPath: pkgRelPath, - }); - completedPackages.add(pkgRelPath); - results.push({ packagePath: pkgRelPath, success: true }); - debugLogger.logMilestone(`Package ${pkgIdx + 1}/${packagePaths.length}: ${pkgRelPath} — completed`); - // Persist after each successful package - writeEnhanceToml( - projectRoot, tomlData?.aiFeatureUsed ?? true, false, sourcePath, - [...completedPackages], - ); - } catch (pkgError) { - if (_migrationAbortController.signal.aborted) { throw pkgError; } - const errMsg = pkgError instanceof Error ? pkgError.message : String(pkgError); - console.error(`[MigrationEnhancement] Package ${pkgRelPath} failed:`, pkgError); - debugLogger.logError(`Package ${pkgRelPath}`, pkgError); - eventHandler({ type: "content_block", content: `\n\n⚠️ Package \`${pkgRelPath}\` failed: ${errMsg}. Continuing to next package.\n\n` }); - results.push({ packagePath: pkgRelPath, success: false, error: errMsg }); - } - } - - if (!_migrationAbortController.signal.aborted) { - // ── Workspace-level validation after all per-package stages ── - if (results.some(r => r.success)) { - eventHandler({ type: "content_block", content: `\n\n## 🔍 Cross-Package Workspace Validation\n\n` }); - debugLogger.logMilestone("Workspace validation — starting (wizard)"); - try { - await runStagesForPackage({ - projectRoot, packagePath: projectRoot, sourcePath, - stages: [getWorkspaceValidationStage(packagePaths.length)], - eventHandler: stageEventHandler, abortController: _migrationAbortController, - fromAIChat, stageIdPrefix: "wizard-workspace-validation", - useExistingTempPath: true, debugLogger, - transcriptWriter, packageRelPath: "", - }); - debugLogger.logMilestone("Workspace validation — completed (wizard)"); - } catch (wsError) { - if (!_migrationAbortController.signal.aborted) { - const errMsg = wsError instanceof Error ? wsError.message : String(wsError); - debugLogger.logError("workspace validation (wizard)", wsError); - eventHandler({ type: "content_block", content: `\n\n⚠️ Workspace validation failed: ${errMsg}\n\n` }); - } - } - } - emitFinalReport(eventHandler, results); - const data = readEnhanceToml(projectRoot); - if (data) { - const summary = transcriptWriter.generateSummary(data, results); - transcriptWriter.writeSummary(summary); - writeEnhanceToml(projectRoot, data.aiFeatureUsed, true, data.sourcePath); - } - debugLogger.logMilestone(`Run complete — ${results.filter(r => r.success).length}/${results.length} packages succeeded (wizard)`); - console.log("[MigrationEnhancement] Wizard migration agent completed all packages successfully."); - eventHandler({ type: "stop", command: Command.Agent }); - } else { - debugLogger.logMilestone("Run aborted by user (wizard)"); - eventHandler({ type: "abort", command: Command.Agent }); - } - } else { - // ── Single-package project (existing behavior) ─────────────── - const stages = getEnhancementStages(); - injectResumePreamble(projectRoot, stages); - console.log(`[MigrationEnhancement] Starting wizard migration agent (${stages.length} stages) – projectRoot: ${projectRoot}, sourcePath: ${sourcePath ?? 'none'}`); - debugLogger.logMilestone(`Run start — single package (wizard), model: ${_selectedModelId}, projectRoot: ${projectRoot}`); - - await runStagesForPackage({ - projectRoot, packagePath: projectRoot, sourcePath, stages, - eventHandler, abortController: _migrationAbortController, - fromAIChat, stageIdPrefix: "wizard-migration", - useExistingTempPath: true, debugLogger, - transcriptWriter, packageRelPath: "", - }); - - if (!_migrationAbortController.signal.aborted) { - const data = readEnhanceToml(projectRoot); - if (data) { - const summary = transcriptWriter.generateSinglePackageSummary(data, false); - transcriptWriter.writeSummary(summary); - writeEnhanceToml(projectRoot, data.aiFeatureUsed, true, data.sourcePath); - } - debugLogger.logMilestone("Run complete — single package succeeded (wizard)"); - console.log("[MigrationEnhancement] Wizard migration agent completed all stages successfully."); - } else { - debugLogger.logMilestone("Run aborted by user (wizard, single-package)"); - } - } - } catch (error) { - if (_migrationAbortController.signal.aborted) { - console.log("[MigrationEnhancement] Wizard migration agent was aborted by user."); - debugLogger.logMilestone("Run aborted by user (wizard, outer catch)"); - if (_runningFromAIChat && projectRoot) { - // Persist partial state so the Resume button can appear - const data = readEnhanceToml(projectRoot); - writeEnhanceToml(projectRoot, data?.aiFeatureUsed ?? true, false, data?.sourcePath, data?.completedPackages); - // Write summary on abort for resume context - if (data) { - const summary = data.multiProject - ? transcriptWriter.generateSummary(data, []) - : transcriptWriter.generateSinglePackageSummary(data, true); - transcriptWriter.writeSummary(summary); - } - // Notify AI Chat panel to show the interrupted message - sendAIPanelNotification({ type: "abort", command: Command.Agent }); - // Show a VS Code notification so the user can jump back to AI Chat - window.showInformationMessage( - "AI Enhancement paused. Your progress has been saved.", - "Open AI Chat" - ).then((selection) => { - if (selection === "Open AI Chat") { - openAIPanelWithPrompt(); - } - }); - } - } else { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("[MigrationEnhancement] Wizard migration agent error:", error); - debugLogger.logError("wizard run", error); - eventHandler({ type: "error", content: `An error occurred during AI enhancement: ${errorMessage}` }); - } - } finally { - _runningFromAIChat = false; - _migrationAbortController = undefined; - setMigrationEnhancementActive(false); - } -} - -/** - * Opens the migrated project in VS Code. - * Called by the wizard after AI enhancement completes or is skipped. - */ -export function openMigratedProject(): void { - const projectRoot = _wizardProjectRoot; - if (!projectRoot) { - window.showErrorMessage("Migration enhancement: no project root to open."); - return; - } - - // Read the current toml to determine the state - const data = readEnhanceToml(projectRoot); - const aiFeatureUsed = data?.aiFeatureUsed ?? true; - const sourcePath = data?.sourcePath ?? _wizardSourcePath; - - // If the AI agent was running (paused by user), the toml already reflects fullyEnhanced=false - const wasRunning = _migrationAbortController !== undefined; - if (wasRunning && data && !data.fullyEnhanced) { - writeEnhanceToml(projectRoot, aiFeatureUsed, false, sourcePath); - } - - scheduleMigrationEnhancement(aiFeatureUsed, projectRoot, sourcePath); - - // Clear the wizard state - _wizardProjectRoot = undefined; - _wizardSourcePath = undefined; - - commands.executeCommand('vscode.openFolder', Uri.file(projectRoot)); -} - -// =========================================================================== -// Internal helpers -// =========================================================================== - -function _resolveCurrentProjectRoot(): string | undefined { - // Build a list of candidate paths to check for the toml file. - const candidates: string[] = []; - - const folders = workspace.workspaceFolders; - if (folders && folders.length > 0) { - candidates.push(folders[0].uri.fsPath); - } - - // Also check the project root stored persistently at migration time. - const stored = extension.context.globalState.get(MIGRATION_PROJECT_ROOT_KEY); - console.log("[MigrationEnhancement] stored MIGRATION_PROJECT_ROOT_KEY:", stored); - if (stored && !candidates.includes(stored)) { - candidates.push(stored); - } - - // Also check the active Ballerina project path from the state machine — - // this is the most reliable source when the panel is opened manually - // without going through the migration wizard first. - try { - const smCtx = StateMachine.context(); - const smProjectPath = smCtx?.projectPath; - const smWorkspacePath = smCtx?.workspacePath; - if (smProjectPath && !candidates.includes(smProjectPath)) { - candidates.push(smProjectPath); - } - if (smWorkspacePath && !candidates.includes(smWorkspacePath)) { - candidates.push(smWorkspacePath); - } - console.log("[MigrationEnhancement] StateMachine candidates:", smProjectPath, smWorkspacePath); - } catch { - // StateMachine may not be initialized yet — ignore - } - - // Return the first candidate that actually contains the toml file. - for (const candidate of candidates) { - const tomlPath = path.join(candidate, AI_MIGRATION_DIR, AI_ENHANCE_TOML_FILENAME); - const exists = fs.existsSync(tomlPath); - console.log("[MigrationEnhancement] checking toml at:", tomlPath, "exists:", exists); - if (exists) { - return candidate; - } - } - - // No toml found in any candidate – still return the workspace folder so - // the caller can decide (it will get null from readEnhanceToml and fall back). - return candidates[0]; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/prompts.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/prompts.ts deleted file mode 100644 index b0c69d6a10..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/prompts.ts +++ /dev/null @@ -1,661 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -// --------------------------------------------------------------------------- -// Enhancement stage definitions -// --------------------------------------------------------------------------- - -import * as fs from "fs"; -import * as path from "path"; -import { AI_MIGRATION_DIR, AI_SUMMARY_FILENAME } from "./types"; - -/** Describes a single stage of the multi-stage migration enhancement. */ -export interface EnhancementStage { - /** Human-readable name shown in progress messages. */ - name: string; - /** The prompt text the agent receives for this stage. */ - prompt: string; - /** Per-stage agent limits (overrides the default). */ - agentLimits: { maxSteps: number; maxOutputTokens: number }; -} - -/** - * Returns the ordered list of enhancement stages. - * The orchestrator runs each stage sequentially, giving each stage a **fresh - * context window** so the agent never runs out of context mid-work. - */ -export function getEnhancementStages(): EnhancementStage[] { - const shared = getSharedEnhancementContext(); - return [ - { - name: "Stage 1 — Fidelity Check & TODO Resolution", - prompt: shared + "\n\n" + getStage1Prompt(), - agentLimits: { maxSteps: 200, maxOutputTokens: 16384 }, - }, - { - name: "Stage 2 — Zero Compilation Diagnostics", - prompt: shared + "\n\n" + getStage2Prompt(), - agentLimits: { maxSteps: 100, maxOutputTokens: 16384 }, - }, - { - name: "Stage 3 — Test Refinement", - prompt: shared + "\n\n" + getStage3Prompt(), - agentLimits: { maxSteps: 100, maxOutputTokens: 16384 }, - }, - { - name: "Stage 4 — Final Validation & Documentation", - prompt: shared + "\n\n" + getStage4Prompt(), - agentLimits: { maxSteps: 50, maxOutputTokens: 16384 }, - }, - ]; -} - -// --------------------------------------------------------------------------- -// Per-project enhancement stages (for multi-package workspaces) -// --------------------------------------------------------------------------- - -/** - * Injected at the end of per-package Stages 2 and 4 to prevent the agent - * from spending turns on errors that are an artefact of isolated compilation. - */ -const CROSS_PACKAGE_ISOLATION_NOTE = `--- - -## Cross-Package Compilation Note - -This package is compiled **in isolation** from the rest of the workspace during this stage. -Errors about missing modules from sibling packages (e.g. \`BCE2003: module 'org/otherPkg' not found\`) -are **expected** and will be resolved during the final workspace validation stage that runs after all -packages have been individually enhanced. - -**Do not attempt to fix these inter-package import errors.** They are not broken code — they are a -temporary artefact of the isolated per-package compilation. Focus only on errors within **this -package's own code**. Any package listed in the cross-package manifest above is a workspace sibling; -their exports are correct as declared.`; - -/** - * Returns enhancement stages scoped to a single package inside a - * multi-package workspace. The shared context is augmented with a - * per-package preamble so the agent knows: - * - * 1. Which package it is currently enhancing. - * 2. Where the package sits within the overall workspace. - * 3. A light-weight cross-package manifest (names + public symbols) - * so it can write correct `import` statements. - * - * @param packageName Display name of the package (from Ballerina.toml). - * @param packagePath Relative path of the package from the workspace root. - * @param packageIndex Zero-based position in the ordered package list. - * @param totalPackages Total number of packages in the workspace. - * @param crossPackageManifest Markdown snippet listing peer packages and symbols. - */ -export function getPerProjectEnhancementStages( - packageName: string, - packagePath: string, - packageIndex: number, - totalPackages: number, - crossPackageManifest: string, -): EnhancementStage[] { - const preamble = getPerProjectPreamble( - packageName, packagePath, packageIndex, totalPackages, crossPackageManifest, - ); - const shared = preamble + "\n\n" + getSharedEnhancementContext(); - return [ - { - name: `[${packageName}] Stage 1 — Fidelity Check & TODO Resolution`, - prompt: shared + "\n\n" + getStage1Prompt(), - agentLimits: { maxSteps: 200, maxOutputTokens: 16384 }, - }, - { - name: `[${packageName}] Stage 2 — Zero Compilation Diagnostics`, - prompt: shared + "\n\n" + getStage2Prompt() + "\n\n" + CROSS_PACKAGE_ISOLATION_NOTE, - agentLimits: { maxSteps: 100, maxOutputTokens: 16384 }, - }, - { - name: `[${packageName}] Stage 3 — Test Refinement`, - prompt: shared + "\n\n" + getStage3Prompt(), - agentLimits: { maxSteps: 100, maxOutputTokens: 16384 }, - }, - { - name: `[${packageName}] Stage 4 — Final Validation & Documentation`, - prompt: shared + "\n\n" + getStage4Prompt() + "\n\n" + CROSS_PACKAGE_ISOLATION_NOTE, - agentLimits: { maxSteps: 50, maxOutputTokens: 16384 }, - }, - ]; -} - -/** - * Builds a preamble injected before the shared context for per-project runs. - */ -function getPerProjectPreamble( - packageName: string, - packagePath: string, - packageIndex: number, - totalPackages: number, - crossPackageManifest: string, -): string { - return `## Per-Package Enhancement Context - -You are enhancing **package ${packageIndex + 1} of ${totalPackages}**: \`${packageName}\` (path: \`${packagePath}/\`). - -**Scope rules:** -- Only modify files inside this package (\`${packagePath}/\`). -- Do NOT modify files in other packages.${crossPackageManifest} - ----`; -} - -// --------------------------------------------------------------------------- -// Shared context — included at the top of every stage prompt -// --------------------------------------------------------------------------- - -function getSharedEnhancementContext(): string { - return `You are enhancing a Ballerina project that was automatically migrated from an external integration -platform (e.g. MuleSoft Mule 3/4, TIBCO BusinessWorks, or similar) by a static code migration tool. The tool -produced a structurally valid Ballerina package (or workspace with multiple packages) but left \`// TODO\` and -\`// FIXME\` comments where it could not fully translate a construct, and may have introduced compilation errors. - -**Apply your combined knowledge of the source integration platform and Ballerina throughout this task.** - ---- - -## Critical Rules — Read Before Anything Else - -These rules are **non-negotiable**. Violating any of them means the enhancement has failed. - -1. **IMPLEMENT, do not document.** Write working Ballerina code. Never produce summaries, roadmaps, - remaining-work lists, or explanations of what _should_ be done instead of doing it. - **Do NOT create** \`NEXT_STEPS.md\`, \`README_MIGRATION.md\`, \`ENHANCEMENT_SUMMARY.md\` (unless Stage 4), - or any guide/roadmap/documentation file. -2. **Never stop early.** "This project is complex", "time constraints", "token limits", "escaping issues" - are NOT valid reasons to stop. You have ample budget. Keep making \`file_edit\` calls. -3. **Edit files in place — always.** Use \`file_edit\` / \`file_multi_edit\` on existing files. - **NEVER** create \`*_new.bal\`, \`*_backup.bal\`, \`*_v2.bal\`, or any copy of an existing file. - If a file is large, break your edits into multiple \`file_edit\` calls targeting different regions. -4. **No stubs or placeholders.** Every TODO must become real, runnable Ballerina logic — not an empty - function, not a \`return {}\`, not a \`return ""\`, not a \`// placeholder\`. If a DataWeave transform - is 200 lines, translate all 200 lines. -5. **No empty files.** Never create a file that contains only comments or is empty. -6. **Code over commentary.** Your text output between tool calls should be 1–2 sentences of progress. - If you are writing more than 3 sentences without a tool call, stop and make an edit instead. -7. **\`file_write\` is ONLY for new files.** Files like \`functions.bal\`, \`data_mappings.bal\`, \`main.bal\`, - \`configs.bal\`, \`types.bal\` etc. that appear in the initial project source ALREADY EXIST. You must use - \`file_edit\` / \`file_multi_edit\` to modify them. Use \`file_write\` only when creating a file that has - no content yet (e.g. an entirely new \`.bal\` file the migration tool did not produce). -8. **Never write a "Summary" or "Remaining Work" section.** Do not output a final summary of completed - and remaining work. Just keep editing files until every TODO is resolved. - ---- - -## Mandatory Workflow: Process One File at a Time - -**This is the most important workflow rule.** Do NOT read all original source files upfront. Instead: - -1. Pick one \`.bal\` file that has TODOs/FIXMEs (start with \`main.bal\`, then \`functions.bal\`, then others). -2. For each TODO in that file, read **only** the specific original source file related to that TODO - (using \`migration_source_read\`). Do not read unrelated source files. -3. Implement the fix using \`file_edit\` / \`file_multi_edit\`. -4. Move to the next TODO in the same file, or the next file. -5. Output a one-line progress note: "Resolved 3/7 TODOs in functions.bal". - -**Why:** Reading all source files at once fills your context window before you make any edits, leaving -no room for the actual implementation work. Read just-in-time, edit immediately. - ---- - -## Ballerina Coding Guidelines - -- Use \`check\` for error propagation. Use \`match\` / \`if\`/\`else\` over flag variables. -- Prefer typed \`record\` types over \`map\`. Use \`isolated\` functions where possible. -- Prefer expression-bodied functions (\`=> expr\`) for pure data transformations. -- Use \`configurable\` for externalised configuration. Max line length: 120 characters. - ---- - -## File Editing Strategy - -| Tool | When to use | -|---|---| -| \`file_read\` | Re-read a file after editing it, or read a file not in the initial message. | -| \`file_edit\` | Replace one text region in an **existing** file (find-and-replace). | -| \`file_multi_edit\` | Multiple find-and-replace edits in the **same existing** file. | -| \`file_write\` | Create a file that does **not yet exist** (zero content). | - ---- - -## Project Structure Awareness - -### Default BI file structure -| File | Contents | -|---|---| -| \`main.bal\` | HTTP/scheduler listeners, services, class definitions | -| \`functions.bal\` | Block-body functions from source flows/sub-flows | -| \`data_mappings.bal\` | Expression-bodied functions from DataWeave/XSLT transforms | -| \`automations.bal\` | The \`main\` function (scheduled/batch flows) | -| \`types.bal\` | All \`type\` definitions | -| \`configs.bal\` | \`configurable\` variables | -| \`connections.bal\` | Connector client/connection initialisations | -| \`internal_types.bal\` | Shared internal types | -| \`todo.bal\` | Constructs the tool could not map | - -### Config.toml -Every \`configurable\` variable must have a corresponding entry in \`Config.toml\`. - ---- - -## Original Source Context - -You have two tools for reading the original source project: -- **\`migration_source_list\`**: List files/directories. -- **\`migration_source_read\`**: Read a specific file. - -**Read source files on-demand** — only when you need them for a specific TODO you are about to resolve. -Do NOT read all source files as a first step. - -### Handling missing original source -If the source cannot be found: -1. Implement what can be inferred from surrounding code and platform knowledge. -2. Must be syntactically and type-correct. -3. Leave a scoped comment noting the approximation. -4. **Never leave an empty stub.** - ---- - -## Inter-Package References - -1. Verify \`import\` statements and \`public\` visibility. -2. Create minimal compilable stubs for missing cross-package functions. -3. Ensure workspace \`Ballerina.toml\` lists all packages. - ---- - -## Multi-Project Workspace - -Process one package at a time. Read other packages for context but only modify them for compilation stubs.`; -} - -// --------------------------------------------------------------------------- -// Stage 1 — Fidelity Check + Resolve TODO/FIXME Comments -// --------------------------------------------------------------------------- - -function getStage1Prompt(): string { - return `## Your Task — Stage 1: Fidelity Check + Resolve TODO/FIXME Comments - -Your sole focus: resolve every TODO/FIXME in source files (excluding \`tests/\`). A later stage handles -diagnostics, tests, and documentation. - -> **Do NOT** create any documentation files. **Do NOT** fix compilation errors (Stage 2 does that). -> **Do NOT** create \`functions_new.bal\`, \`data_mappings_new.bal\`, or ANY copy of existing files. - -### Workflow — Follow This Exact Order - -**Phase A: Quick fidelity scan (≤ 3 tool calls)** -1. Call \`migration_source_list\` on the root directory (\`.\`) to see the source project structure. -2. Compare the listed source files against the Ballerina project files in the initial message. -3. Note any source constructs that have **no** Ballerina counterpart — you will implement them during Phase B. - **Do NOT read every source file now.** Just note which files exist. - -**Phase B: Resolve TODOs file by file** - -Process files in this order: \`todo.bal\` → \`main.bal\` → \`functions.bal\` → \`data_mappings.bal\` → other \`.bal\` files. - -For each file: -1. Scan the file content (already in the initial message) for \`// TODO\` and \`// FIXME\` comments. -2. For each TODO: - a. If it references a specific source construct/file, call \`migration_source_read\` to read **only that file**. - b. Implement the fix immediately using \`file_edit\` or \`file_multi_edit\`. - c. For \`// TODO: UNSUPPORTED ... BLOCK ENCOUNTERED\` comments: the commented-out source between the - \`// ---\` lines is the specification. Translate it to Ballerina and remove the entire commented block. -3. After finishing all TODOs in one file, output one line: "✅ \`\`: resolved N TODOs". -4. Move to the next file. - -**Phase C: Fidelity-gap implementation** - -Using the notes from Phase A, implement any source constructs that were silently dropped by the migration -tool. Read the specific source file, then add the corresponding Ballerina code to the appropriate \`.bal\` file. - -**Phase D: Clean up todo.bal** - -If all constructs from \`todo.bal\` have been moved to their correct files, delete \`todo.bal\`. -If some remain, continue implementing them. - -### Connector / Construct Mapping Quick Reference - -| Source construct | Ballerina equivalent | -|---|---| -| Custom loggers (json-logger, etc.) | \`log:printInfo\` / \`log:printError\` with structured fields | -| Object stores / caches | \`ballerina/cache\` or \`map\` variable | -| Message queues (Anypoint MQ, JMS) | \`ballerinax/rabbitmq\`, \`kafka\`, or \`java.jms\` | -| Batch processors | \`foreach\` with error collection, or \`fork\`/\`worker\` | -| Schedulers | \`ballerina/task\` or \`@schedule\` annotation | -| DataWeave transforms | Expression-bodied Ballerina functions | -| SOAP/XML services | \`ballerina/http\` + \`ballerina/xmldata\` | -| Choice routers | \`if\`/\`else\` or \`match\` | -| Error handling (on-error-propagate) | \`on fail\` / \`check\` | -| Error handling (on-error-continue) | \`do { } on fail { }\` (no re-throw) | -| Scatter-gather | \`fork\`/\`worker\` or \`future\` types | -| Flow references | Ballerina function calls | - -### Anti-Patterns — DO NOT DO THESE - -The following actions are **failures**. If you catch yourself doing any of them, stop immediately: - -❌ Reading 4+ original source files before making your first \`file_edit\` call. -❌ Creating \`functions_new.bal\`, \`data_mappings_new.bal\`, or any "-new" / "-v2" / "_backup" file. -❌ Using \`file_write\` on a file that already has content (\`functions.bal\`, \`main.bal\`, etc.). -❌ Writing a "Summary", "Remaining Work", "Next Steps", or "Recommendation" section. -❌ Saying "Due to complexity..." or "Given the size..." or "time constraints" and stopping. -❌ Creating a function that returns \`{}\`, \`""\`, \`()\`, or \`0\` as a "placeholder". -❌ Writing more than 3 sentences of text between two consecutive tool calls. - -If a DataWeave file is 400 lines, you translate all 400 lines. Break the work into multiple -\`file_edit\` calls if needed, each handling a section of the file. - -### Completion Criteria - -When every TODO/FIXME in source files (excluding \`tests/\`) is resolved, output one line: -"Stage 1 complete: resolved N TODOs across M files. todo.bal: [deleted | N constructs remain]." -Then stop. Do not write anything else.`; -} - -// --------------------------------------------------------------------------- -// Stage 2 — Zero Compilation Diagnostics -// --------------------------------------------------------------------------- - -function getStage2Prompt(): string { - return `## Your Task — Stage 2: Achieve Zero Compilation Diagnostics - -You are running **Stage 2** of the enhancement pipeline. The previous stage resolved all TODO/FIXME comments. -Your sole focus is achieving **zero error-level compilation diagnostics** across all source files -(excluding \`tests/\`). - -> **Do NOT** create \`ENHANCEMENT_SUMMARY.md\` or any documentation files during this stage. -> **Do NOT** work on test files — that is Stage 3. -> Focus entirely on fixing compilation errors. - -### Workflow - -1. Run the diagnostics tool to get all current compilation errors. -2. Address errors systematically: - - **Missing imports**: Add the correct \`import\` statement. Prefer \`ballerina/\` stdlib and \`ballerinax/\` connectors. - - **Type mismatches**: Align types with declarations in \`internal_types.bal\` and connector return types. - Use \`check\` for error unions. - - **Undefined symbols**: Check whether the symbol should come from another file in the same package, or - whether a function/type was not migrated and needs to be created. - - **Incompatible function signatures**: Match parameter types and return types. Pay attention to - \`returns error?\` vs \`returns SomeType|error\`. - - **Config.toml mismatches**: Ensure every \`configurable\` variable has a matching key in \`Config.toml\`. - - **Cross-package references**: Verify imports, ensure called functions are \`public\`, create stubs if needed. -3. After each batch of fixes, **re-run diagnostics** to verify progress and catch new errors. -4. Repeat until zero error-level diagnostics remain. - -### Important Notes - -- If a diagnostic cannot be fixed by consulting the original source (because the source itself was broken - or absent), apply the minimum change that makes the code compile: widen a type to \`anydata\`, extract a - helper function with a compatible signature, etc. Always add a scoped comment explaining the approximation. -- You may use \`migration_source_read\` if you need to check original source for context while fixing errors. -- If the previous stage left any unresolved TODOs that cause compilation errors, fix them now. - -### Completion Criteria for Stage 2 - -When the diagnostics tool reports zero error-level diagnostics for all source files (excluding \`tests/\`): -- Output the final diagnostics count (should be 0 errors). -- List any best-effort approximations you made to fix diagnostics. - -Then stop. Stage 3 will handle test files.`; -} - -// --------------------------------------------------------------------------- -// Stage 3 — Test Refinement -// --------------------------------------------------------------------------- - -function getStage3Prompt(): string { - return `## Your Task — Stage 3: Refine and Validate Test Files - -You are running **Stage 3** of the enhancement pipeline. Stages 1 and 2 have already resolved all TODOs -and achieved zero compilation diagnostics in source files. Your sole focus is making the test files in -\`tests/\` complete, correctly typed, and compilation-error free. - -> **Tests are not executed during this stage.** The goal is to produce structurally complete, correctly-typed -> test files. Test execution and pass/fail validation is handled in a separate subsequent step. -> **Do NOT** create \`ENHANCEMENT_SUMMARY.md\` during this stage — that is Stage 4. - -### Preparation: Build a Behaviour Map - -Before touching any test file: -1. Read the enhanced Ballerina source files (\`functions.bal\`, \`data_mappings.bal\`, \`main.bal\`, etc.) to - understand actual function signatures, return types, error paths, and data shapes. -2. Use \`migration_source_list\` and \`migration_source_read\` to read original test files (e.g. MUnit XML - under \`src/test/munit/\`) to understand the original test scenarios. - -### For each test file in \`tests/\`: - -1. **Align with enhanced implementation**: Verify test calls match current function signatures. Update - call sites if signatures changed during Stages 1/2. -2. **Align with original test intent**: Cross-reference each test against original test files. If the - original mocked a connector, mock the equivalent \`ballerinax/\` client using \`@test:Mock\`. -3. **Resolve all \`// TODO\` and \`// FIXME\` comments** with correct test logic. -4. **Ensure meaningful assertions**: Every \`@test:Config\` function must have at least one substantive - assertion (\`test:assertEquals\`, \`test:assertTrue\`, \`test:assertFail\`, etc.). No trivially-true checks. -5. **Check for dropped test coverage**: If a test exists in the original source but has no counterpart - in \`tests/\`, create the missing test function. -6. **Mock connectors and external services**: Use \`@test:Mock\` for \`http:Client\`, database clients, etc. -7. **Wire setup/teardown correctly**: \`@test:BeforeSuite\`, \`@test:AfterSuite\`, etc. -8. **Ensure test files compile**: Run the diagnostics tool **including** the \`tests/\` directory and fix errors. -9. **Do not delete or comment out existing test cases.** - -### Completion Criteria for Stage 3 - -When all test files are complete and compilation-error free: -- Output the diagnostics count for test files (should be 0 errors). -- List test functions added or significantly modified. - -Then stop. Stage 4 will handle final validation and documentation.`; -} - -// --------------------------------------------------------------------------- -// Stage 4 — Final Validation & Documentation -// --------------------------------------------------------------------------- - -function getStage4Prompt(): string { - return `## Your Task — Stage 4: Final Validation & Documentation - -You are running the **final stage** of the enhancement pipeline. Stages 1–3 have resolved TODOs, fixed -diagnostics, and refined test files. Your focus is verifying completeness and writing \`ENHANCEMENT_SUMMARY.md\`. - -### Validation Checklist - -Verify each of the following. If any item fails, **fix it** before writing documentation: - -- [ ] **Source files**: Zero unresolved \`// TODO\` and \`// FIXME\` comments remain (or every remaining - comment is a scoped best-effort note — never an unimplemented stub). -- [ ] **\`todo.bal\`**: Empty or deleted. -- [ ] **UNSUPPORTED BLOCK comments**: Zero \`// TODO: UNSUPPORTED ... BLOCK ENCOUNTERED\` comments remain. -- [ ] **Diagnostics**: Run the diagnostics tool. Zero error-level diagnostics in source files. -- [ ] **Config.toml**: Has entries for all \`configurable\` variables. -- [ ] **Test files**: Run diagnostics including \`tests/\`. All test files compile. Every original test - scenario has a Ballerina test function. No empty stubs. -- [ ] **Cross-package stubs**: Any stubs written in other packages are noted. - -### Write ENHANCEMENT_SUMMARY.md - -After the checklist passes, create \`ENHANCEMENT_SUMMARY.md\` in the package root with these sections: - -\`\`\`markdown -# Enhancement Summary — - -## Overview - - -## Changes Made - -### Fidelity Fixes -List constructs silently dropped by the static tool that were completed during fidelity check. - -### TODO / FIXME Resolutions -List every TODO/FIXME resolved: file, original comment, what was implemented. - -### Best-Effort Approximations -List places where original source was missing and a best-effort implementation was produced. - -### Compilation Fixes -List diagnostics fixed in Stage 2. - -### Test Changes -Summarise Stage 3 work: tests updated, added, mocks added, scenarios not covered. - -## Remaining Scoped TODOs -List all remaining \`// TODO\` comments (best-effort notes only). - -## Compilation Status -**✅ Zero diagnostics** or list acceptable warnings. - -## Test Readiness -**✅ Test files compile and are structurally complete** or note gaps. -\`\`\` - -### Workspace-Level Summary (multi-package only) - -If this is a multi-package workspace and all packages are complete, also create \`ENHANCEMENT_SUMMARY.md\` -at the workspace root: - -\`\`\`markdown -# Enhancement Summary — Workspace - -## Packages Enhanced -| Package | Diagnostics | Test Files Ready | Remaining TODOs | Notes | -|---|---|---|---|---| - -## Cross-Package Stubs Created -List stubs written in one package to unblock another. - -## Overall Remaining Work -Summarise anything requiring human review. -\`\`\` - -### Completion Criteria for Stage 4 - -Output the final status: checklist results and confirmation that \`ENHANCEMENT_SUMMARY.md\` was written.`; -} - -// --------------------------------------------------------------------------- -// Cross-package workspace validation stage (runs after all per-package stages) -// --------------------------------------------------------------------------- - -/** - * Returns a single workspace-level validation stage to run after all packages - * have been individually enhanced. The executor should use the workspace root - * as `packagePath` so the compiler sees all packages simultaneously. - * - * @param packageCount Total number of packages in the workspace. - */ -export function getWorkspaceValidationStage(packageCount: number): EnhancementStage { - return { - name: "Cross-Package Workspace Validation", - prompt: getWorkspaceValidationPrompt(packageCount), - agentLimits: { maxSteps: 10 + packageCount * 3, maxOutputTokens: 16384 }, - }; -} - -function getWorkspaceValidationPrompt(packageCount: number): string { - return `## Workspace-Level Cross-Package Validation - -This is the **final stage** of the multi-package enhancement pipeline. All ${packageCount} packages have been -individually enhanced and should now compile in isolation. Your task is to compile the full workspace and -fix any remaining cross-package issues so that all packages build together successfully. - -### What to do - -1. Run the diagnostics tool **without** specifying a \`packagePath\` (or specify the workspace root \`.\`) - to compile the entire workspace at once. -2. Review all errors. Focus especially on: - - **Inter-package import errors** (\`BCE2003\`, \`BCE2007\`): a module from one package cannot be found - by another. Check that: - - The exporting package declares the symbol with \`public\`. - - The importing package's \`Ballerina.toml\` lists the exporting package under \`[[dependency]]\`. - - **Type-compatibility errors** across package boundaries. - - **Missing public symbols**: a function or type expected by a peer package does not exist yet. -3. Fix all cross-package errors using \`file_edit\` on the affected files. -4. Re-run diagnostics and repeat until zero error-level diagnostics remain across the full workspace. -5. If a cross-package fix requires touching a package that was already completed, apply the minimal - targeted fix needed to make the workspace compile. - -### Important Notes - -- Do NOT re-run Stage 1–4 work here — only fix errors that span package boundaries. -- Only make changes that are strictly necessary to achieve zero workspace-level errors. -- Do NOT create \`ENHANCEMENT_SUMMARY.md\` or any documentation in this stage. - -### Completion Criteria - -Zero error-level diagnostics across the complete workspace. -Output: "Workspace validation complete — 0 errors across ${packageCount} packages."`; -} - -/** - * Returns a lightweight enhancement prompt for demo or quick validation scenarios. - * This prompt is NOT used in the main wizard flow, but can be used for lightweight demos. - */ -export function getLightweightEnhancementPrompt(): string { - return `You are enhancing a Ballerina project that was automatically migrated from a legacy integration platform. - -Perform the following quick improvements: - -1. **Add WSO2 license headers** – For every \`.bal\` file that does NOT already have a license header at the top, prepend the standard Apache 2.0 / WSO2 license comment block. -2. **Run diagnostics** – Use the diagnostics tool to check for compilation errors. If there are any obvious one-line fixes (e.g. missing imports, unused variables), fix them. Do NOT attempt large refactors. - -Stop once license headers have been added and trivial diagnostics are resolved.`; -} - -// --------------------------------------------------------------------------- -// Resume preamble – injected when resuming a paused enhancement -// --------------------------------------------------------------------------- - -/** - * Reads `summary.md` from `.ballerina-ai-migration/` and wraps it in an - * instruction preamble that tells the agent this is a continuation. - * - * Returns `null` when no summary exists (fresh run — no resume context needed). - */ -export function getResumePreamble(projectRoot: string): string | null { - const summaryPath = path.join(projectRoot, AI_MIGRATION_DIR, AI_SUMMARY_FILENAME); - try { - if (!fs.existsSync(summaryPath)) { - return null; - } - const summary = fs.readFileSync(summaryPath, "utf8"); - if (!summary.trim()) { - return null; - } - return [ - "## ⚠️ CONTINUATION — This is a RESUMED enhancement session", - "", - "A previous enhancement run was paused. The summary below describes what was already done.", - "Do NOT repeat work that was already completed — pick up from where it left off.", - "If you need more detail about a specific stage, read the transcript files in", - "`.ballerina-ai-migration/transcripts/`.", - "", - "---", - "", - summary, - "", - "---", - "", - ].join("\n"); - } catch { - return null; - } -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/transcript-writer.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/transcript-writer.ts deleted file mode 100644 index 9b760ac4e5..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/transcript-writer.ts +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -import * as fs from "fs"; -import * as path from "path"; -import { AI_MIGRATION_DIR, AI_TRANSCRIPT_DIR, AI_SUMMARY_FILENAME, EnhanceTomlData, PackageEnhancementResult } from "./types"; - -/** - * Records per-stage transcript markdown files and generates a concise - * `summary.md` that the agent can use as context when resuming. - * - * File layout inside `.ballerina-ai-migration/`: - * ``` - * transcripts/ - * / (multi-package) - * stage1.md - * stage2.md - * stage1.md (single-package) - * workspace-validation.md (multi-package only) - * summary.md - * ``` - */ -export class TranscriptWriter { - private readonly transcriptDir: string; - private currentFilePath: string | undefined; - - constructor(private readonly projectRoot: string) { - this.transcriptDir = path.join(projectRoot, AI_MIGRATION_DIR, AI_TRANSCRIPT_DIR); - } - - /** - * Begin a new stage transcript. Writes the markdown header. - * - * @param packageRelPath Relative package path (empty string for single-package or workspace validation). - * @param stageIndex Zero-based stage index. - * @param stageName Human-readable stage name. - * @param isWorkspaceValidation If `true`, writes to `workspace-validation.md`. - */ - startStage( - packageRelPath: string, - stageIndex: number, - stageName: string, - isWorkspaceValidation = false, - ): void { - const dir = packageRelPath - ? path.join(this.transcriptDir, packageRelPath) - : this.transcriptDir; - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - const filename = isWorkspaceValidation - ? "workspace-validation.md" - : `stage${stageIndex + 1}.md`; - - this.currentFilePath = path.join(dir, filename); - - const header = `# ${stageName}\n\n_Started: ${new Date().toISOString()}_\n\n---\n\n`; - fs.writeFileSync(this.currentFilePath, header, "utf8"); - } - - /** - * Append streaming content text to the current stage transcript. - * No-op when no stage is active. - */ - appendContent(text: string): void { - if (!this.currentFilePath || !text) { return; } - try { - fs.appendFileSync(this.currentFilePath, text, "utf8"); - } catch (err) { - console.error("[TranscriptWriter] Failed to append content:", err); - } - } - - /** - * Append a tool-call marker to the transcript. - */ - appendToolCall(toolName: string, inputSummary?: string): void { - if (!this.currentFilePath) { return; } - const line = inputSummary - ? `\n> 🔧 **${toolName}** — ${truncate(inputSummary, 200)}\n` - : `\n> 🔧 **${toolName}**\n`; - try { - fs.appendFileSync(this.currentFilePath, line, "utf8"); - } catch (err) { - console.error("[TranscriptWriter] Failed to append tool call:", err); - } - } - - /** - * Append a tool-result marker to the transcript. - */ - appendToolResult(toolName: string, succeeded: boolean): void { - if (!this.currentFilePath) { return; } - const icon = succeeded ? "✅" : "❌"; - const line = `> ${icon} **${toolName}** completed\n\n`; - try { - fs.appendFileSync(this.currentFilePath, line, "utf8"); - } catch (err) { - console.error("[TranscriptWriter] Failed to append tool result:", err); - } - } - - /** - * Write a completion marker at the end of the current stage transcript. - */ - finalizeStage(): void { - if (!this.currentFilePath) { return; } - try { - fs.appendFileSync( - this.currentFilePath, - `\n\n---\n\n_Completed: ${new Date().toISOString()}_\n`, - "utf8", - ); - } catch (err) { - console.error("[TranscriptWriter] Failed to finalize stage:", err); - } - this.currentFilePath = undefined; - } - - /** - * Generate a structured summary of the enhancement run so far. - * This is written to `summary.md` and used as context when resuming. - */ - generateSummary( - tomlData: EnhanceTomlData, - results: PackageEnhancementResult[], - ): string { - const lines: string[] = []; - lines.push("# AI Enhancement Summary\n"); - lines.push(`_Generated: ${new Date().toISOString()}_\n`); - - // Overall status - const succeeded = results.filter(r => r.success); - const failed = results.filter(r => !r.success); - lines.push(`## Status\n`); - lines.push(`- **Packages completed**: ${succeeded.length} of ${results.length}`); - if (tomlData.completedPackages?.length) { - lines.push(`- **Completed**: ${tomlData.completedPackages.join(", ")}`); - } - if (tomlData.currentPackage) { - lines.push(`- **Last active package**: ${tomlData.currentPackage}`); - } - if (tomlData.currentStage !== undefined) { - lines.push(`- **Last active stage**: ${tomlData.currentStage + 1}`); - } - if (failed.length > 0) { - lines.push(`- **Failed**: ${failed.map(f => `${f.packagePath} (${f.error ?? "unknown"})`).join(", ")}`); - } - lines.push(""); - - // Per-package transcript summaries - lines.push(`## Per-Package Details\n`); - for (const result of results) { - const icon = result.success ? "✅" : "❌"; - lines.push(`### ${icon} ${result.packagePath}\n`); - - // Read stage files for this package to extract completion lines - const pkgTranscriptDir = result.packagePath - ? path.join(this.transcriptDir, result.packagePath) - : this.transcriptDir; - const stageFiles = this.listStageFiles(pkgTranscriptDir); - for (const sf of stageFiles) { - const firstLine = this.readFirstLine(sf); - const completed = this.fileContainsCompletion(sf); - const status = completed ? "completed" : "in-progress/aborted"; - lines.push(`- **${path.basename(sf, ".md")}**: ${firstLine ?? "unknown"} — ${status}`); - } - if (stageFiles.length === 0) { - lines.push("- _No transcript files found_"); - } - lines.push(""); - } - - return lines.join("\n"); - } - - /** - * Generate a summary for a single-package project. - */ - generateSinglePackageSummary( - tomlData: EnhanceTomlData, - aborted: boolean, - ): string { - const lines: string[] = []; - lines.push("# AI Enhancement Summary\n"); - lines.push(`_Generated: ${new Date().toISOString()}_\n`); - - lines.push(`## Status\n`); - lines.push(`- **Type**: Single package`); - lines.push(`- **Outcome**: ${aborted ? "Paused/Aborted" : "Completed"}`); - if (tomlData.currentStage !== undefined) { - lines.push(`- **Last active stage**: ${tomlData.currentStage + 1}`); - } - lines.push(""); - - lines.push(`## Stage Details\n`); - const stageFiles = this.listStageFiles(this.transcriptDir); - for (const sf of stageFiles) { - const firstLine = this.readFirstLine(sf); - const completed = this.fileContainsCompletion(sf); - const status = completed ? "completed" : "in-progress/aborted"; - lines.push(`- **${path.basename(sf, ".md")}**: ${firstLine ?? "unknown"} — ${status}`); - } - if (stageFiles.length === 0) { - lines.push("- _No transcript files found_"); - } - lines.push(""); - - return lines.join("\n"); - } - - /** - * Write the summary markdown to disk. - */ - writeSummary(summary: string): void { - const summaryPath = path.join( - this.projectRoot, AI_MIGRATION_DIR, AI_SUMMARY_FILENAME, - ); - const dir = path.dirname(summaryPath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - try { - fs.writeFileSync(summaryPath, summary, "utf8"); - console.log(`[TranscriptWriter] Summary written to ${summaryPath}`); - } catch (err) { - console.error("[TranscriptWriter] Failed to write summary:", err); - } - } - - // ── Helpers ────────────────────────────────────────────────────────────── - - private listStageFiles(dir: string): string[] { - try { - if (!fs.existsSync(dir)) { return []; } - return fs.readdirSync(dir) - .filter(f => f.endsWith(".md")) - .sort() - .map(f => path.join(dir, f)); - } catch { return []; } - } - - private readFirstLine(filePath: string): string | null { - try { - const content = fs.readFileSync(filePath, "utf8"); - const match = content.match(/^#\s+(.+)/m); - return match?.[1] ?? null; - } catch { return null; } - } - - private fileContainsCompletion(filePath: string): boolean { - try { - const content = fs.readFileSync(filePath, "utf8"); - return content.includes("_Completed:"); - } catch { return false; } - } -} - -/** Truncate a string, appending `…` if it exceeds `maxLen`. */ -function truncate(s: string, maxLen: number): string { - return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + "…"; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/types.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/migration/types.ts deleted file mode 100644 index 500c63ea7a..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/migration/types.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://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. - -/** globalState key – only one pending enhancement is allowed at a time */ -export const PENDING_MIGRATION_ENHANCEMENT_KEY = "ballerina.pendingMigrationEnhancement"; - -/** - * Persistent globalState key that stores just the project root of the most - * recently migrated project. Unlike `PENDING_MIGRATION_ENHANCEMENT_KEY` this - * entry is never TTL-expired so `getActiveMigrationSessionState` can always - * locate the state file even if the webview mounts before - * `checkAndRunPendingEnhancement` runs. - */ -export const MIGRATION_PROJECT_ROOT_KEY = "ballerina.migrationProjectRoot"; - -/** Hidden directory inside the project root that stores AI migration metadata. */ -export const AI_MIGRATION_DIR = ".ballerina-ai-migration"; - -/** State TOML filename inside AI_MIGRATION_DIR. */ -export const AI_ENHANCE_TOML_FILENAME = "state.toml"; - -/** Subdirectory inside AI_MIGRATION_DIR that holds per-stage transcript files. */ -export const AI_TRANSCRIPT_DIR = "transcripts"; - -/** Filename for the structured summary written at pause / completion. */ -export const AI_SUMMARY_FILENAME = "summary.md"; - -/** - * Shape of the value stored in VS Code globalState before the - * `vscode.openFolder` reload so that the enhancement pipeline can - * be resumed in the freshly opened window. - */ -export interface PendingMigrationEnhancement { - /** `true` when the AI enhancement feature was used (wizard or post-wizard). */ - aiFeatureUsed: boolean; - projectRoot: string; - /** epoch ms – used to discard stale entries (>10 min) */ - timestamp: number; - /** Absolute path to the original source project (e.g. Mule XML directory). */ - sourcePath?: string; -} - -/** Milliseconds before a stale pending-enhancement entry is discarded */ -export const PENDING_ENHANCEMENT_TTL_MS = 10 * 60 * 1000; // 10 minutes - -/** - * Shape of the `state.toml` file written inside `.ballerina-ai-migration/`. - */ -export interface EnhanceTomlData { - /** `true` when the AI enhancement feature was used (wizard or post-wizard). */ - aiFeatureUsed: boolean; - /** `true` once the AI enhancement pipeline has completed successfully for this project. */ - fullyEnhanced: boolean; - /** Absolute path to the original source project (e.g. Mule XML directory). */ - sourcePath?: string; - /** Relative paths of packages that have completed all enhancement stages. */ - completedPackages?: string[]; - /** Relative path of the package currently being enhanced. */ - currentPackage?: string; - /** Zero-based index of the stage currently being run within `currentPackage`. */ - currentStage?: number; - /** `true` when this project is a multi-package workspace (cross-dependent packages). */ - multiProject?: boolean; -} - -/** - * Outcome of enhancing a single package in a multi-package workspace. - */ -export interface PackageEnhancementResult { - /** Relative path of the package from the workspace root. */ - packagePath: string; - /** Whether all stages completed successfully. */ - success: boolean; - /** If `success` is false, a short description of what went wrong. */ - error?: string; -} - -/** - * Describes the current state of a migration AI enhancement session. - * Mirrors `ActiveMigrationSession` from `@wso2/ballerina-core`. - * Defined locally here to avoid cross-package build-order issues. - */ -export interface ActiveMigrationSessionLocal { - isActive: boolean; - /** `true` when the AI enhancement feature was used (wizard or post-wizard). */ - aiFeatureUsed: boolean; - /** `true` once the enhancement pipeline has completed successfully (read from the toml). */ - fullyEnhanced: boolean; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/prompt-enhancement/prompts.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/prompt-enhancement/prompts.ts index b6ab3fe5e7..1d63d3d6a8 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/prompt-enhancement/prompts.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/prompt-enhancement/prompts.ts @@ -162,9 +162,8 @@ When the user provides a task rather than a system prompt, convert it into a sys Keep the persona natural and proportional to the task. ### OUTPUT FORMAT -- Output ONLY the improved prompt text — nothing else. -- Do NOT include explanations, change summaries, "Changes made:", "Here is your prompt:", or any meta-commentary about what you changed or why. -- The user sees your output directly as their new prompt. Anything that isn't the prompt itself will be shown to them as part of the prompt. +- Output ONLY the optimized system prompt text. +- Do not include explanations, "Here is your prompt:", or code block wrappers. ### CRITICAL: PRESERVE VARIABLES The prompt may contain interpolation variables wrapped in \${...}. These are template placeholders that get replaced at runtime. You MUST: diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/state/ApprovalManager.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/state/ApprovalManager.ts index 3aa1e8eef0..0c0160c1a0 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/state/ApprovalManager.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/state/ApprovalManager.ts @@ -16,7 +16,7 @@ * under the License. */ -import { Task, MACHINE_VIEW, ClarifyQuestion } from "@wso2/ballerina-core/lib/state-machine-types"; +import { Task, MACHINE_VIEW } from "@wso2/ballerina-core/lib/state-machine-types"; import { CopilotEventHandler } from "../utils/events"; import { ConfigVariable } from "../../../utils/toml-utils"; import { StateMachine } from "../../../stateMachine"; @@ -48,14 +48,6 @@ export interface ConnectorSpecResponse { comment?: string; } -/** - * Clarify tool response - */ -export interface ClarifyResponse { - answered: boolean; - answers?: Array<{ question: string; answers: string[] }>; -} - /** * Configuration response containing actual values (converted to metadata before exposing to agent) */ @@ -98,7 +90,6 @@ export class ApprovalManager { private connectorSpecs = new Map>(); private configurationRequests = new Map>(); private webToolApprovals = new Map>(); - private clarifyRequests = new Map>(); private approvalQueue: ApprovalQueueItem[] = []; private approvalQueueActive = false; private notificationCounters = new Map(); @@ -505,55 +496,6 @@ export class ApprovalManager { this._advanceApprovalQueue(requestId); } - // ============================================ - // Clarify Tool - // ============================================ - - /** - * Request clarification from user - * Emits clarify_event and waits for user to select answer(s) - */ - requestClarify( - requestId: string, - params: { questions: ClarifyQuestion[] }, - eventHandler: CopilotEventHandler, - ): Promise { - console.log(`[ApprovalManager] Requesting clarification: ${requestId}`); - - eventHandler({ - type: "clarify_event", - requestId, - stage: "asking", - questions: params.questions, - }); - - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - this.clarifyRequests.delete(requestId); - reject(new Error(`Clarify request timeout for request ${requestId}`)); - }, this.DEFAULT_TIMEOUT_MS); - - this.clarifyRequests.set(requestId, { resolve, reject, timeoutId }); - }); - } - - /** - * Resolve a pending clarify request (called by RPC handler when user responds) - */ - resolveClarify(requestId: string, answered: boolean, answers?: Array<{ question: string; answers: string[] }>): void { - const resolver = this.clarifyRequests.get(requestId); - if (!resolver) { - console.warn(`[ApprovalManager] No pending clarify request for: ${requestId}`); - return; - } - - console.log(`[ApprovalManager] Resolving clarify request: ${requestId}, answered: ${answered}`); - - if (resolver.timeoutId) { clearTimeout(resolver.timeoutId); } - resolver.resolve({ answered, answers }); - this.clarifyRequests.delete(requestId); - } - // ============================================ // Notification Counter // ============================================ @@ -645,15 +587,6 @@ export class ApprovalManager { this.approvalQueue = []; this.approvalQueueActive = false; - // Resolve clarify requests as skipped - for (const [, resolver] of this.clarifyRequests.entries()) { - if (resolver.timeoutId) { - clearTimeout(resolver.timeoutId); - } - resolver.resolve({ answered: false }); - } - this.clarifyRequests.clear(); - // Reset all notification counters and fire handlers (e.g. turn off globe) for (const [type, count] of this.notificationCounters.entries()) { if (count > 0) { @@ -666,14 +599,13 @@ export class ApprovalManager { /** * Get count of pending approvals (useful for debugging) */ - getPendingCount(): { plans: number; tasks: number; connectorSpecs: number; configurations: number; webTools: number; clarify: number } { + getPendingCount(): { plans: number; tasks: number; connectorSpecs: number; configurations: number; webTools: number } { return { plans: this.planApprovals.size, tasks: this.taskApprovals.size, connectorSpecs: this.connectorSpecs.size, configurations: this.configurationRequests.size, webTools: this.webToolApprovals.size, - clarify: this.clarifyRequests.size, }; } } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts index dd342260e0..afe51620c9 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts @@ -25,7 +25,7 @@ import { LLM_API_BASE_PATH } from "../constants"; import { AIMachineEventType, AnthropicKeySecrets, LoginMethod, BIIntelSecrets } from "@wso2/ballerina-core"; export const ANTHROPIC_HAIKU = "claude-haiku-4-5-20251001"; -export const ANTHROPIC_SONNET_4 = "claude-sonnet-4-6"; +export const ANTHROPIC_SONNET_4 = "claude-sonnet-4-5-20250929"; type AnthropicModel = | typeof ANTHROPIC_HAIKU @@ -190,7 +190,7 @@ export const getAnthropicClient = async (model: AnthropicModel): Promise => // Map Anthropic model names to AWS Bedrock model IDs (base models without region prefix) const baseModelMap: Record = { [ANTHROPIC_HAIKU]: "anthropic.claude-haiku-4-5-20251001-v1:0", - [ANTHROPIC_SONNET_4]: "anthropic.claude-sonnet-4-6", + [ANTHROPIC_SONNET_4]: "anthropic.claude-sonnet-4-5-20250929-v1:0", }; const baseModelId = baseModelMap[model]; @@ -222,7 +222,7 @@ export const getAnthropicClient = async (model: AnthropicModel): Promise => const vertexModelMap: Record = { [ANTHROPIC_HAIKU]: "claude-3-5-haiku@20241022", - [ANTHROPIC_SONNET_4]: "claude-sonnet-4-6", + [ANTHROPIC_SONNET_4]: "claude-sonnet-4-5@20250929", }; const vertexModelId = vertexModelMap[model]; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts index b24cf8db15..b8559a432d 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts @@ -40,8 +40,6 @@ import { ModelMessage } from "ai"; import { MessageRole } from "./ai-types"; import { RPCLayer } from "../../../RPCLayer"; import { AiPanelWebview } from "../../../views/ai-panel/webview"; -import { MigrationPanelWebview } from "../../../views/migration-panel/webview"; -import { VisualizerWebview } from "../../../views/visualizer/webview"; import { GenerationType } from "./libs/libraries"; // import { REQUIREMENTS_DOCUMENT_KEY } from "./code/np_prompts"; @@ -255,7 +253,7 @@ export function sendToolResultNotification(toolName: string, toolOutput?: any, t sendAIPanelNotification(msg); } -export function sendTaskApprovalRequestNotification(approvalType: "plan" | "completion", tasks: any[], taskDescription?: string, message?: string, requestId?: string, autoApproved?: boolean): void { +export function sendTaskApprovalRequestNotification(approvalType: "plan" | "completion", tasks: any[], taskDescription?: string, message?: string, requestId?: string): void { const msg: ChatNotify = { type: "task_approval_request", requestId: requestId || `approval-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, @@ -263,7 +261,6 @@ export function sendTaskApprovalRequestNotification(approvalType: "plan" | "comp tasks: tasks, taskDescription: taskDescription, message: message, - autoApproved: autoApproved, }; sendAIPanelNotification(msg); } @@ -311,10 +308,6 @@ export function sendConfigurationCollectionNotification(event: ChatNotify & { ty sendAIPanelNotification(event); } -export function sendClarifyNotification(event: ChatNotify & { type: "clarify_event" }): void { - sendAIPanelNotification(event); -} - export function sendWebToolToggleNotification(active: boolean): void { RPCLayer._messenger.sendNotification( webToolToggle, @@ -323,30 +316,13 @@ export function sendWebToolToggleNotification(active: boolean): void { ); } -export function sendAIPanelNotification(msg: ChatNotify): void { +function sendAIPanelNotification(msg: ChatNotify): void { RPCLayer._messenger.sendNotification(onChatNotify, { type: "webview", webviewType: AiPanelWebview.viewType }, msg); } -/** - * Sends a chat notification to the standalone Migration Enhancement Panel. - * Mirrors `sendAIPanelNotification` but targets `MigrationPanelWebview.viewType`. - */ -export function sendMigrationPanelNotification(msg: ChatNotify): void { - RPCLayer._messenger.sendNotification(onChatNotify, { type: "webview", webviewType: MigrationPanelWebview.viewType }, msg); -} - -/** - * Sends a chat notification to the Visualizer webview. - * Used by the wizard-level migration AI enhancement to stream progress - * back to the ImportIntegration wizard before the project is opened. - */ -export function sendVisualizerMigrationNotification(msg: ChatNotify): void { - RPCLayer._messenger.sendNotification(onChatNotify, { type: "webview", webviewType: VisualizerWebview.viewType }, msg); -} - export function sendUsageMetricsNotification( usage: { inputTokens: number; cacheCreationInputTokens: number; cacheReadInputTokens: number; outputTokens: number }, - breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; files: number; messages: number; toolResults: number }, + breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; messages: number; toolResults: number }, ): void { sendAIPanelNotification({ type: "usage_metrics", usage, breakdown }); } @@ -369,31 +345,13 @@ export function getErrorMessage(error: unknown): string { return "Usage limit exceeded."; } if (error.name === "AI_RetryError") { - return "An error occurred connecting with the AI service. Please try again later."; + return "An error occured connecting with the AI service. Please try again later."; } if (error.name === "AbortError") { return "Generation stopped by the user."; } - // Friendly message for connection / stream interruption errors - const msg = error.message; - if ( - msg.includes("Remote host closed the connection") || - msg.includes("reading stream") || - msg.includes("inbound response body") || - msg.includes("ECONNRESET") || - msg.includes("socket hang up") - ) { - return "The AI service connection was interrupted. Please try again."; - } - if (msg.includes("JSON parsing failed")) { - return "The AI service returned an invalid response. Please try again."; - } - if (msg.includes("Unsupported login method")) { - return "Please sign in to BI Copilot to use AI features."; - } - - return msg; + return error.message; } // If it's an object with a .message field, use that if ( diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/eol-utils.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/eol-utils.ts deleted file mode 100644 index 72228678eb..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/eol-utils.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -/** - * Utilities for handling line endings (EOL) across platforms. - * - * Problem: On Windows, files use CRLF (\r\n) line endings. LLMs produce - * LF (\n) only. This causes string matching to fail when the agent tries - * to edit files — the file has \r\n but the LLM sends \n. - * - * Solution: Normalize to LF at the read boundary, work with LF internally, - * and restore the original EOL style at the write boundary. - */ - -export type EolSequence = '\n' | '\r\n'; - -/** - * Detects the dominant line ending in a string. - * Returns '\r\n' if any CRLF sequence is found, otherwise '\n'. - */ -export function detectEol(content: string): EolSequence { - return content.includes('\r\n') ? '\r\n' : '\n'; -} - -/** - * Normalizes all line endings to LF (\n). - * Handles CRLF (\r\n) and bare CR (\r). - */ -export function normalizeToLf(content: string): string { - return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); -} - -/** - * Restores line endings to the target EOL sequence. - * Only performs conversion when target is CRLF — LF content is unchanged. - */ -export function restoreEol(content: string, eol: EolSequence): string { - if (eol === '\n') { - return content; - } - return content.replace(/\n/g, '\r\n'); -} - -/** - * Reads file content and returns it normalized to LF, along with the - * detected original EOL. Use with {@link restoreEol} when writing back. - * - * @returns [normalizedContent, originalEol] - */ -export function readAndNormalize(rawContent: string): [string, EolSequence] { - const eol = detectEol(rawContent); - return [normalizeToLf(rawContent), eol]; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/events.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/events.ts index c1d89040a1..7bf81a480e 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/events.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/events.ts @@ -15,7 +15,6 @@ // under the License. import { ChatNotify, Command, onChatNotify } from "@wso2/ballerina-core"; -import { ModelUsage } from "./libs/function-registry"; import { RPCLayer } from "../../../RPCLayer"; import { AiPanelWebview } from "../../../views/ai-panel/webview"; import { @@ -35,82 +34,12 @@ import { sendSaveChatNotification, sendConnectorGenerationNotification, sendConfigurationCollectionNotification, - sendMigrationPanelNotification, - sendVisualizerMigrationNotification, - sendAIPanelNotification, - sendClarifyNotification, sendChatComponentNotification, sendUsageMetricsNotification, } from "./ai-utils"; export type CopilotEventHandler = (event: ChatNotify) => void; -export type ToolModelUsage = Record; - -// Per-million-token pricing by model -const MODEL_PRICING: Record = { - 'claude-sonnet-4-6': { input: 3, cacheWrite: 3.75, cacheRead: 0.30, output: 15 }, - 'claude-haiku-4-5-20251001': { input: 1, cacheWrite: 1.25, cacheRead: 0.10, output: 5 }, -}; - -interface CostInput { - model: string; - inputTokens: number; - outputTokens: number; - cacheReadTokens?: number; - cacheWriteTokens?: number; -} - -export function calculateCost(usage: CostInput): number { - const pricing = MODEL_PRICING[usage.model]; - if (!pricing) { return 0; } - - const cacheRead = usage.cacheReadTokens || 0; - const cacheWrite = usage.cacheWriteTokens || 0; - const baseInput = usage.inputTokens - cacheRead - cacheWrite; - - return ( - baseInput * pricing.input + - cacheWrite * pricing.cacheWrite + - cacheRead * pricing.cacheRead + - usage.outputTokens * pricing.output - ) / 1_000_000; -} - -export function calculateTotalCost( - mainModel: string, - mainUsage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number }, - toolModelUsage: ToolModelUsage -): number { - const mainCost = calculateCost({ model: mainModel, ...mainUsage }); - const toolCost = Object.entries(toolModelUsage).reduce( - (sum, [model, u]) => sum + calculateCost({ model, inputTokens: u.inputTokens, outputTokens: u.outputTokens }), - 0 - ); - return mainCost + toolCost; -} - -export function emitModelUsage(eventHandler: CopilotEventHandler, usages: ModelUsage[], accumulator: ToolModelUsage): void { - for (const u of usages) { - if (!accumulator[u.model]) { - accumulator[u.model] = { inputTokens: 0, outputTokens: 0 }; - } - accumulator[u.model].inputTokens += u.inputTokens; - accumulator[u.model].outputTokens += u.outputTokens; - - eventHandler({ - type: "usage_metrics", - model: u.model, - usage: { - inputTokens: u.inputTokens, - cacheCreationInputTokens: 0, - cacheReadInputTokens: 0, - outputTokens: u.outputTokens, - }, - }); - } -} - /** * Updates chat message with model messages and triggers save * This is a shared utility used by agent, datamapper, and other AI features @@ -163,8 +92,7 @@ export function createWebviewEventHandler(command: Command): CopilotEventHandler event.tasks, event.taskDescription, event.message, - event.requestId, - event.autoApproved + event.requestId ); break; case "evals_tool_result": @@ -182,9 +110,6 @@ export function createWebviewEventHandler(command: Command): CopilotEventHandler case "configuration_collection_event": sendConfigurationCollectionNotification(event); break; - case "clarify_event": - sendClarifyNotification(event); - break; case "chat_component": sendChatComponentNotification(event.componentType, event.data, event.id); break; @@ -199,8 +124,8 @@ export function createWebviewEventHandler(command: Command): CopilotEventHandler console.log('[Compaction] Context compaction completed'); RPCLayer._messenger.sendNotification(onChatNotify, { type: "webview", webviewType: AiPanelWebview.viewType }, event); break; - case "compaction_disabled": - console.warn('[Compaction] Compaction disabled — codebase floor exceeds trigger threshold'); + case "compaction_failed": + console.warn(`[Compaction] Context compaction failed: ${event.reason}`); RPCLayer._messenger.sendNotification(onChatNotify, { type: "webview", webviewType: AiPanelWebview.viewType }, event); break; default: @@ -209,39 +134,3 @@ export function createWebviewEventHandler(command: Command): CopilotEventHandler } }; } - -/** - * Event handler factory that routes agent/executor events to the standalone - * Migration Enhancement Panel (instead of the AI Chat panel). - * - * Uses `sendMigrationPanelNotification` under the hood so the notifications - * target `MigrationPanelWebview.viewType`. - */ -export function createMigrationEventHandler(command: Command): CopilotEventHandler { - return (event: ChatNotify) => { - // Route all events through the migration-panel notification channel - sendMigrationPanelNotification(event); - }; -} - -/** - * Event handler factory that routes agent/executor events to the AI Chat panel. - * Used when the user starts migration enhancement directly from AI Chat (static project). - */ -export function createAIPanelMigrationEventHandler(command: Command): CopilotEventHandler { - return (event: ChatNotify) => { - sendAIPanelNotification(event); - }; -} - -/** - * Event handler factory that routes agent/executor events to the Visualizer - * webview. Used for the wizard-level migration AI enhancement so the - * ImportIntegration wizard can show live streaming progress before the project - * is opened in VS Code. - */ -export function createVisualizerMigrationEventHandler(command: Command): CopilotEventHandler { - return (event: ChatNotify) => { - sendVisualizerMigrationNotification(event); - }; -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/function-registry.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/function-registry.ts index 34af817ef4..3b47661e47 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/function-registry.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/function-registry.ts @@ -33,46 +33,25 @@ import { GenerationType } from "./libraries"; // import { getRequiredTypesFromLibJson } from "../healthcare/healthcare"; import { langClient } from "../../activator"; -export interface ModelUsage { - model: string; - inputTokens: number; - outputTokens: number; -} - -export function mergeUsage(...usages: ModelUsage[]): ModelUsage[] { - const map = new Map(); - for (const u of usages) { - const existing = map.get(u.model); - if (existing) { - existing.inputTokens += u.inputTokens; - existing.outputTokens += u.outputTokens; - } else { - map.set(u.model, { ...u }); - } - } - return Array.from(map.values()); -} - // Constants for type definitions const TYPE_RECORD = 'Record'; const TYPE_UNION = 'Union'; const TYPE_CONSTRUCTOR = 'Constructor'; -export async function selectRequiredFunctions(prompt: string, selectedLibNames: string[], generationType: GenerationType): Promise<{ libraries: Library[], usage: ModelUsage[] }> { +export async function selectRequiredFunctions(prompt: string, selectedLibNames: string[], generationType: GenerationType): Promise { const selectedLibs: Library[] = await getMaximizedSelectedLibs(selectedLibNames); - const { functionsResponse, usage: functionsUsage } = await getRequiredFunctions(selectedLibNames, prompt, selectedLibs, generationType); + const functionsResponse: GetFunctionResponse[] = await getRequiredFunctions(selectedLibNames, prompt, selectedLibs, generationType); let typeLibraries: Library[] = []; - const allUsages: ModelUsage[] = [...functionsUsage]; if (generationType === GenerationType.HEALTHCARE_GENERATION) { - const { types: resp, usage } = await getRequiredTypesFromLibJson(selectedLibNames, prompt, selectedLibs); + const resp: GetTypeResponse[] = await getRequiredTypesFromLibJson(selectedLibNames, prompt, selectedLibs); typeLibraries = toTypesToLibraries(resp, selectedLibs); - allUsages.push(usage); } const maximizedLibraries: Library[] = await toMaximizedLibrariesFromLibJson(functionsResponse, selectedLibs); + + // Merge typeLibraries and maximizedLibraries without duplicates const mergedLibraries = mergeLibrariesWithoutDuplicates(maximizedLibraries, typeLibraries); - const result = { libraries: mergedLibraries, usage: mergeUsage(...allUsages) }; - return result; + return mergedLibraries; } function getClientFunctionCount(clients: MinifiedClient[]): number { @@ -141,9 +120,9 @@ async function getRequiredFunctions( prompt: string, librariesJson: Library[], generationType: GenerationType -): Promise<{ functionsResponse: GetFunctionResponse[], usage: ModelUsage[] }> { +): Promise { if (librariesJson.length === 0) { - return { functionsResponse: [], usage: [] }; + return []; } const startTime = Date.now(); @@ -166,13 +145,13 @@ async function getRequiredFunctions( ); // Create promises for large libraries (each processed individually) - const largeLiberiesPromises = largeLibs.map((funcItem) => + const largeLiberiesPromises: Promise[] = largeLibs.map((funcItem) => getSuggestedFunctions(prompt, [funcItem]) ); // Create promise for small libraries (processed in bulk) const smallLibrariesPromise = - smallLibs.length !== 0 ? getSuggestedFunctions(prompt, smallLibs) : Promise.resolve({ libraries: [] as GetFunctionResponse[], usage: { model: ANTHROPIC_HAIKU, inputTokens: 0, outputTokens: 0 } }); + smallLibs.length !== 0 ? getSuggestedFunctions(prompt, smallLibs) : Promise.resolve([]); console.log( `[Parallel Execution Start] Starting ${largeLiberiesPromises.length} large library requests + 1 small libraries bulk request` @@ -180,8 +159,7 @@ async function getRequiredFunctions( const parallelStartTime = Date.now(); // Wait for all promises to complete - const allResults = await Promise.all([smallLibrariesPromise, ...largeLiberiesPromises]); - const [smallLibResult, ...largeLibResults] = allResults; + const [smallLibResults, ...largeLibResults] = await Promise.all([smallLibrariesPromise, ...largeLiberiesPromises]); const parallelEndTime = Date.now(); const parallelDuration = (parallelEndTime - parallelStartTime) / 1000; @@ -189,12 +167,10 @@ async function getRequiredFunctions( console.log(`[Parallel Execution Complete] Total parallel execution time: ${parallelDuration}s`); // Flatten the results - const collectiveResp: GetFunctionResponse[] = [...smallLibResult.libraries, ...largeLibResults.flatMap(r => r.libraries)]; + const collectiveResp: GetFunctionResponse[] = [...smallLibResults, ...largeLibResults.flat()]; const endTime = Date.now(); const totalDuration = (endTime - startTime) / 1000; - const aggregatedUsage = mergeUsage(...allResults.map(r => r.usage)); - console.log( `[getRequiredFunctions Complete] Total function count: ${collectiveResp.reduce( (total, lib) => @@ -204,17 +180,17 @@ async function getRequiredFunctions( 0 )}, Total duration: ${totalDuration}s, Preparation time: ${ (parallelStartTime - startTime) / 1000 - }s, Parallel time: ${parallelDuration}s, Usage:`, aggregatedUsage + }s, Parallel time: ${parallelDuration}s` ); - return { functionsResponse: collectiveResp, usage: aggregatedUsage }; + return collectiveResp; } async function getSuggestedFunctions( prompt: string, libraryList: GetFunctionsRequest[] -): Promise<{ libraries: GetFunctionResponse[], usage: ModelUsage }> { +): Promise { const startTime = Date.now(); const libraryNames = libraryList.map((lib) => lib.name).join(", "); const functionCount = libraryList.reduce( @@ -266,7 +242,7 @@ Now, based on the provided libraries, clients, and functions, and the user query { role: "user", content: getLibUserPrompt }, ]; try { - const { object, usage } = await generateObject({ + const { object } = await generateObject({ model: await getAnthropicClient(ANTHROPIC_HAIKU), maxOutputTokens: 8192, temperature: 0, @@ -284,7 +260,6 @@ Now, based on the provided libraries, clients, and functions, and the user query libraryList.some((inputLib) => inputLib.name === lib.name) ); - const callUsage: ModelUsage = { model: ANTHROPIC_HAIKU, inputTokens: usage.inputTokens || 0, outputTokens: usage.outputTokens || 0 }; console.log( `[AI Request Complete] Libraries: [${libraryNames}], Duration: ${duration}s, Selected Functions: ${libList.libraries.reduce( (total, lib) => @@ -292,11 +267,11 @@ Now, based on the provided libraries, clients, and functions, and the user query (lib.clients?.reduce((clientTotal, client) => clientTotal + client.functions.length, 0) || 0) + (lib.functions?.length || 0), 0 - )}, Usage:`, callUsage + )}` ); printSelectedFunctions(filteredLibList); - return { libraries: filteredLibList, usage: callUsage }; + return filteredLibList; } catch (error) { const endTime = Date.now(); const duration = (endTime - startTime) / 1000; @@ -377,7 +352,6 @@ export async function getMaximizedSelectedLibs(libNames: string[]): Promise { - const emptyUsage: ModelUsage = { model: ANTHROPIC_HAIKU, inputTokens: 0, outputTokens: 0 }; +): Promise { if (librariesJson.length === 0) { - return { types: [], usage: emptyUsage }; + return []; } const typeDefs: GetTypesRequest[] = librariesJson @@ -866,7 +838,7 @@ export async function getRequiredTypesFromLibJson( })); if (typeDefs.length === 0) { - return { types: [], usage: emptyUsage }; + return []; } const getLibSystemPrompt = `You are an assistant tasked with selecting the Ballerina types needed to solve a given question based on a set of Ballerina libraries given in the context as a JSON. @@ -911,7 +883,7 @@ Think step-by-step to choose the required types in order to solve the given ques { role: "user", content: getLibUserPrompt }, ]; try { - const { object, usage } = await generateObject({ + const { object } = await generateObject({ model: await getAnthropicClient(ANTHROPIC_HAIKU), maxOutputTokens: 8192, temperature: 0, @@ -920,11 +892,8 @@ Think step-by-step to choose the required types in order to solve the given ques abortSignal: new AbortController().signal, }); - const callUsage: ModelUsage = { model: ANTHROPIC_HAIKU, inputTokens: usage.inputTokens || 0, outputTokens: usage.outputTokens || 0 }; - console.log(`[getRequiredTypesFromLibJson] Usage:`, callUsage); - const libList = object as GetTypesResponse; - return { types: libList.libraries, usage: callUsage }; + return libList.libraries; } catch (error) { throw new Error(`Failed to parse bulk functions response: ${error}`); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/library-types.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/library-types.ts index a0a8d6f62c..ad89edf857 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/library-types.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/library-types.ts @@ -163,7 +163,6 @@ export interface Library { functions?: RemoteFunction[]; services?: Service[]; instructions?: string; - readme?: string; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/to-syntax-string.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/to-syntax-string.ts deleted file mode 100644 index 6d7d28030a..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/libs/to-syntax-string.ts +++ /dev/null @@ -1,503 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -import { - Library, - TypeDefinition, - RecordTypeDefinition, - EnumTypeDefinition, - UnionTypeDefinition, - ConstantTypeDefinition, - ClassTypeDefinition, - Client, - RemoteFunction, - ResourceFunction, - Field, - Type, - Link, - Parameter, - GenericService, - FixedService, - Service, - ServiceRemoteFunction, - ParameterDef, - PathParameter, -} from "./library-types"; - -/** - * Derives a module prefix from a library name. - * Rule: split on `/` and `.`, take the last segment. - * e.g., "ballerina/http" -> "http", "ballerinax/docusign.dsesign" -> "dsesign" - */ -export function deriveModulePrefix(libraryName: string): string { - const parts = libraryName.split(/[/.]/); - return parts[parts.length - 1]; -} - -interface ExternalLinkInfo { - recordName: string; - libraryName: string; - modulePrefix: string; -} - -/** - * Collects external link info from a Type's links array. - */ -function collectExternalLinks(type: Type): ExternalLinkInfo[] { - if (!type.links) { - return []; - } - return type.links - .filter((link): link is Link & { libraryName: string } => - link.category === "external" && !!link.libraryName - ) - .map((link) => ({ - recordName: link.recordName, - libraryName: link.libraryName, - modulePrefix: deriveModulePrefix(link.libraryName), - })); -} - -/** - * Applies module prefix to type name for each external link using word-boundary-aware replacement. - */ -function applyPrefixToTypeName(typeName: string, externalLinks: ExternalLinkInfo[]): string { - let result = typeName; - for (const link of externalLinks) { - const regex = new RegExp(`\\b${escapeRegExp(link.recordName)}\\b`, "g"); - result = result.replace(regex, `${link.modulePrefix}:${link.recordName}`); - } - return result; -} - -function escapeRegExp(str: string): string { - return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - -/** - * Builds the "// Special Agent Note: ..." comment for external links. - * Groups record names by library name. - */ -function buildSpecialAgentNote(externalLinks: ExternalLinkInfo[]): string { - if (externalLinks.length === 0) { - return ""; - } - - const grouped = new Map(); - for (const link of externalLinks) { - if (!grouped.has(link.libraryName)) { - grouped.set(link.libraryName, []); - } - grouped.get(link.libraryName)!.push(link.recordName); - } - - const parts: string[] = []; - for (const [libName, recordNames] of grouped) { - parts.push(`${recordNames.join(", ")} FROM ${libName} package`); - } - - return ` // Special Agent Note: ${parts.join(", ")}`; -} - -/** - * Renders a description as `#` comment lines. - */ -function renderDescription(description: string | undefined): string { - if (!description || description.trim() === "") { - return ""; - } - return description - .split("\n") - .map((line) => `# ${line}`) - .join("\n") + "\n"; -} - -/** - * Renders a record type definition to Ballerina syntax. - */ -function renderRecord(typeDef: RecordTypeDefinition): string { - const lines: string[] = []; - lines.push(renderDescription(typeDef.description)); - lines.push(`type ${typeDef.name} record {`); - - for (const field of typeDef.fields) { - const externalLinks = collectExternalLinks(field.type); - const typeName = applyPrefixToTypeName(field.type.name, externalLinks); - const optional = (field as any).optional ? "?" : ""; - const defaultVal = field.default !== undefined ? ` = ${field.default}` : ""; - const fieldDesc = field.description ? ` # ${field.description}\n` : ""; - const agentNote = buildSpecialAgentNote(externalLinks); - lines.push(`${fieldDesc} ${typeName} ${field.name}${optional}${defaultVal};${agentNote}`); - } - - lines.push("};"); - return lines.join("\n"); -} - -/** - * Renders an enum type definition to Ballerina syntax. - */ -function renderEnum(typeDef: EnumTypeDefinition): string { - const lines: string[] = []; - lines.push(renderDescription(typeDef.description)); - const members = typeDef.members.map((m) => m.name).join(",\n "); - lines.push(`enum ${typeDef.name} {\n ${members}\n}`); - return lines.join(""); -} - -/** - * Renders a union type definition to Ballerina syntax. - */ -function renderUnion(typeDef: UnionTypeDefinition): string { - const desc = renderDescription(typeDef.description); - if (!typeDef.members || typeDef.members.length === 0) { - return `${desc}type ${typeDef.name};`; - } - const members = typeDef.members.map((m) => m.name).join("|"); - return `${desc}type ${typeDef.name} ${members};`; -} - -/** - * Renders a constant type definition to Ballerina syntax. - */ -function renderConstant(typeDef: ConstantTypeDefinition): string { - const desc = renderDescription(typeDef.description); - const value = typeDef.varType.name === "string" ? `"${typeDef.value}"` : typeDef.value; - return `${desc}const ${typeDef.varType.name} ${typeDef.name} = ${value};`; -} - -/** - * Renders a class type definition to Ballerina syntax. - */ -function renderClass(typeDef: ClassTypeDefinition): string { - const desc = renderDescription(typeDef.description); - return `${desc}class ${typeDef.name} {\n}`; -} - -/** - * Renders a type definition to Ballerina syntax. - */ -function renderTypeDef(typeDef: TypeDefinition): string { - switch (typeDef.type) { - case "Record": - return renderRecord(typeDef as RecordTypeDefinition); - case "Enum": - return renderEnum(typeDef as EnumTypeDefinition); - case "Union": - return renderUnion(typeDef as UnionTypeDefinition); - case "Constant": - return renderConstant(typeDef as ConstantTypeDefinition); - case "Class": - return renderClass(typeDef as ClassTypeDefinition); - default: - return `// Unknown type: ${typeDef.name}`; - } -} - -/** - * Collects all external links from parameters and return type. - */ -function collectFunctionExternalLinks(params: Parameter[], returnType?: Type): ExternalLinkInfo[] { - const links: ExternalLinkInfo[] = []; - for (const param of params) { - links.push(...collectExternalLinks(param.type)); - } - if (returnType) { - links.push(...collectExternalLinks(returnType)); - } - // Deduplicate by recordName + libraryName - const seen = new Set(); - return links.filter((l) => { - const key = `${l.recordName}::${l.libraryName}`; - if (seen.has(key)) { - return false; - } - seen.add(key); - return true; - }); -} - -/** - * Renders a parameter (for functions). - */ -function renderParam(param: Parameter): string { - const externalLinks = collectExternalLinks(param.type); - const typeName = applyPrefixToTypeName(param.type.name, externalLinks); - const optional = (param as any).optional; - const defaultVal = (param as any).default !== undefined ? ` = ${(param as any).default}` : ""; - return `${typeName} ${param.name}${defaultVal}`; -} - -/** - * Renders a constructor function. - */ -function renderConstructor(func: RemoteFunction): string { - const allExternalLinks = collectFunctionExternalLinks(func.parameters, func.return?.type); - const params = func.parameters.map(renderParam).join(", "); - const returnStr = func.return?.type ? ` returns ${applyPrefixToTypeName(func.return.type.name, allExternalLinks)}` : ""; - const agentNote = buildSpecialAgentNote(allExternalLinks); - return ` function init(${params})${returnStr};${agentNote}`; -} - -/** - * Renders a remote function. - */ -function renderRemoteFunction(func: RemoteFunction, indent: string = " "): string { - const allExternalLinks = collectFunctionExternalLinks(func.parameters, func.return?.type); - const desc = func.description ? `${indent}# ${func.description.split("\n").join(`\n${indent}# `)}\n` : ""; - const params = func.parameters.map(renderParam).join(", "); - const returnStr = func.return?.type ? ` returns ${applyPrefixToTypeName(func.return.type.name, allExternalLinks)}` : ""; - const agentNote = buildSpecialAgentNote(allExternalLinks); - return `${desc}${indent}remote function ${func.name}(${params})${returnStr};${agentNote}`; -} - -/** - * Renders a resource function. - */ -function renderResourceFunction(func: ResourceFunction, indent: string = " "): string { - const allExternalLinks = collectFunctionExternalLinks(func.parameters, func.return?.type); - const desc = func.description ? `${indent}# ${func.description.split("\n").join(`\n${indent}# `)}\n` : ""; - - // Build path string - const pathSegments = func.paths.map((p) => { - if (typeof p === "string") { - return p; - } - return `[${p.type} ${p.name}]`; - }); - const pathStr = pathSegments.join("/"); - - // Exclude parameters that appear in paths - const pathParamNames = new Set( - func.paths - .filter((p): p is PathParameter => typeof p !== "string") - .map((p) => p.name) - ); - const nonPathParams = func.parameters.filter((p) => !pathParamNames.has(p.name)); - const params = nonPathParams.map(renderParam).join(", "); - - const returnStr = func.return?.type ? ` returns ${applyPrefixToTypeName(func.return.type.name, allExternalLinks)}` : ""; - const agentNote = buildSpecialAgentNote(allExternalLinks); - return `${desc}${indent}resource function ${func.accessor} ${pathStr}(${params})${returnStr};${agentNote}`; -} - -/** - * Renders a client to Ballerina syntax. - */ -function renderClient(client: Client): string { - const lines: string[] = []; - const desc = client.description ? renderDescription(client.description) : ""; - lines.push(`${desc}client class ${client.name} {`); - - for (const func of client.functions) { - if ("type" in func && func.type === "Constructor") { - lines.push(renderConstructor(func as RemoteFunction)); - } else if ("accessor" in func) { - lines.push(""); - lines.push(renderResourceFunction(func as ResourceFunction)); - } else { - lines.push(""); - lines.push(renderRemoteFunction(func as RemoteFunction)); - } - } - - lines.push("}"); - return lines.join("\n"); -} - -/** - * Renders a standalone (normal) function to Ballerina syntax. - * Includes `# + param` and `# + return` documentation. - */ -function renderStandaloneFunction(func: RemoteFunction): string { - const allExternalLinks = collectFunctionExternalLinks(func.parameters, func.return?.type); - const lines: string[] = []; - - // Description - if (func.description) { - const descLines = func.description.split("\n").map((l) => `# ${l}`); - lines.push(...descLines); - } - - // Parameter docs - for (const param of func.parameters) { - if (param.description) { - lines.push(`# + ${param.name} - ${param.description}`); - } - } - - // Return doc - if (func.return?.description) { - lines.push(`# + return - ${func.return.description}`); - } - - const params = func.parameters.map(renderParam).join(", "); - const returnStr = func.return?.type ? ` returns ${applyPrefixToTypeName(func.return.type.name, allExternalLinks)}` : ""; - const agentNote = buildSpecialAgentNote(allExternalLinks); - lines.push(`function ${func.name}(${params})${returnStr};${agentNote}`); - - return lines.join("\n"); -} - -/** - * Renders a ParameterDef (used in fixed service methods). - */ -function renderParamDef(param: ParameterDef & { name?: string }): string { - return `${param.type.name}${param.name ? " " + param.name : ""}`; -} - -/** - * Renders a generic service. - */ -function renderGenericService(service: GenericService): string { - const lines: string[] = []; - const listenerParams = service.listener.parameters.map( - (p) => `${p.type.name} ${p.name}` - ).join(", "); - lines.push(`// --- Service (generic) ---`); - lines.push(`// Listener: ${service.listener.name}(${listenerParams})`); - lines.push(`// Instructions:`); - if (service.instructions) { - lines.push(service.instructions); - } - return lines.join("\n"); -} - -/** - * Renders a fixed service. - */ -function renderFixedService(service: FixedService): string { - const lines: string[] = []; - const listenerParams = service.listener.parameters.map( - (p) => `${p.type.name} ${p.name}${(p as any).default !== undefined ? ` = ${(p as any).default}` : ""}` - ).join(", "); - lines.push(`service on new ${service.listener.name}(${listenerParams}) {`); - - for (const method of service.methods) { - const methodWithName = method as ServiceRemoteFunction & { name?: string }; - const desc = method.description ? ` # ${method.description}\n` : ""; - const params = method.parameters.map((p) => renderParamDef(p as ParameterDef & { name?: string })).join(", "); - const returnStr = method.return?.type ? ` returns ${method.return.type.name}` : ""; - const optionalComment = method.optional ? " // optional" : ""; - - // Try to extract method name from description - let methodName = methodWithName.name || ""; - if (!methodName && method.description) { - const match = method.description.match(/`(\w+)`/); - if (match) { - methodName = match[1]; - } - } - - lines.push(`${desc} remote function ${methodName}(${params})${returnStr};${optionalComment}`); - lines.push(""); - } - - // Remove trailing empty line - if (lines[lines.length - 1] === "") { - lines.pop(); - } - - lines.push("}"); - return lines.join("\n"); -} - -/** - * Renders a service to Ballerina syntax. - */ -function renderService(service: Service): string { - if (service.type === "generic") { - return renderGenericService(service as GenericService); - } else { - return renderFixedService(service as FixedService); - } -} - -/** - * Converts an array of Library objects to LLM-friendly Ballerina syntax string. - */ -export function toSyntaxString(libraries: Library[]): string { - const output: string[] = []; - - for (const lib of libraries) { - // Library header - output.push(`// ============================================================`); - output.push(`// Library: ${lib.name}`); - if (lib.description) { - output.push(`// ${lib.description.split("\n")[0]}`); - } - output.push(`// ============================================================`); - output.push(`import ${lib.name};`); - - // Instructions (prepended if present) - if (lib.instructions) { - output.push(""); - output.push(lib.instructions); - } - - // README (prepended if present) - if (lib.readme) { - output.push(""); - output.push("// --- README ---"); - output.push(lib.readme); - output.push("// --- END README ---"); - } - - // Types section - if (lib.typeDefs && lib.typeDefs.length > 0) { - output.push(""); - output.push("// --- Types ---"); - for (const typeDef of lib.typeDefs) { - output.push(""); - output.push(renderTypeDef(typeDef)); - } - } - - // Client section - if (lib.clients && lib.clients.length > 0) { - output.push(""); - output.push("// --- Client ---"); - for (const client of lib.clients) { - output.push(""); - output.push(renderClient(client)); - } - } - - // Functions section - if (lib.functions && lib.functions.length > 0) { - output.push(""); - output.push("// --- Functions ---"); - for (const func of lib.functions) { - output.push(""); - output.push(renderStandaloneFunction(func)); - } - } - - // Service section - if (lib.services && lib.services.length > 0) { - output.push(""); - output.push("// --- Service ---"); - for (const service of lib.services) { - output.push(""); - output.push(renderService(service)); - } - } - - output.push(""); - } - - return output.join("\n"); -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/vscode-lm-adapter.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/utils/vscode-lm-adapter.ts deleted file mode 100644 index 43cde699b5..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/utils/vscode-lm-adapter.ts +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -/** - * Adapter that wraps a VS Code Language Model (vscode.lm) chat model into - * a Vercel AI SDK-compatible LanguageModel so it can be used with `streamText()`. - * - * The VS Code LM API (`vscode.lm.selectChatModels()`) exposes models contributed - * by extensions like GitHub Copilot. This adapter bridges the gap between the - * VS Code LM interface and the Vercel AI SDK interface expected by AgentExecutor. - * - * Usage: - * import { getVSCodeLMModel } from './vscode-lm-adapter'; - * const model = await getVSCodeLMModel(); - * // Pass to AgentExecutor config.model or streamText({ model }) - * - * Note: This is a simplified adapter. The VS Code LM API returns streamed - * `LanguageModelTextPart` / `LanguageModelToolCallPart` chunks whereas - * the Vercel AI SDK expects a specific `LanguageModelV1` interface. - * A full adapter would implement the complete LanguageModelV1 protocol. - * For now, this provides a thin compatibility layer for basic text streaming. - */ - -import * as vscode from "vscode"; -import { ReadableStream } from "stream/web"; - -/** - * Selects a VS Code Language Model chat model by family/vendor filter. - * Returns the raw vscode.LanguageModelChat which can be used directly or - * wrapped for Vercel AI SDK compatibility. - * - * @param options Selection criteria for the model - */ -export async function selectVSCodeChatModel(options?: { - vendor?: string; - family?: string; - id?: string; -}): Promise { - const models = await vscode.lm.selectChatModels({ - vendor: options?.vendor, - family: options?.family, - id: options?.id, - }); - - if (models.length === 0) { - console.warn("[VSCodeLMAdapter] No matching language models found for:", options); - return undefined; - } - - // Return the first matching model - console.log(`[VSCodeLMAdapter] Selected model: ${models[0].name} (${models[0].id})`); - return models[0]; -} - -/** - * Lists all available VS Code Language Models for display in model selectors. - */ -export async function listAvailableModels(): Promise< - Array<{ id: string; name: string; vendor: string; family: string }> -> { - const models = await vscode.lm.selectChatModels(); - return models.map((m) => ({ - id: m.id, - name: m.name, - vendor: m.vendor, - family: m.family, - })); -} - -/** - * Creates a Vercel AI SDK-compatible wrapper around a VS Code Language Model. - * - * This is a minimal adapter that implements enough of the LanguageModelV1 - * interface to work with `streamText()`. It translates between: - * - Vercel AI SDK messages → VS Code LM messages - * - VS Code LM response stream → Vercel AI SDK stream protocol - * - * LIMITATIONS: - * - Tool calling support is partial (depends on the VS Code LM model capabilities) - * - Provider-specific options are not forwarded - * - Token counting is estimated, not exact - */ -export function createVSCodeLMAdapter(chatModel: vscode.LanguageModelChat): any { - return { - specificationVersion: "v1" as const, - provider: `vscode-lm:${chatModel.vendor}`, - modelId: chatModel.id, - defaultObjectGenerationMode: undefined, - - async doGenerate(options: any) { - const messages = convertToVSCodeMessages(options.prompt); - const response = await chatModel.sendRequest( - messages, - {}, - new vscode.CancellationTokenSource().token - ); - - let text = ""; - for await (const part of response.stream) { - if (part instanceof vscode.LanguageModelTextPart) { - text += part.value; - } - } - - return { - text, - toolCalls: [], - finishReason: "stop" as const, - usage: { promptTokens: 0, completionTokens: 0 }, - rawCall: { rawPrompt: null, rawSettings: {} }, - }; - }, - - async doStream(options: any) { - const messages = convertToVSCodeMessages(options.prompt); - - const cancellationSource = new vscode.CancellationTokenSource(); - - // Wire up abort signal - if (options.abortSignal) { - options.abortSignal.addEventListener("abort", () => { - cancellationSource.cancel(); - }); - } - - const response = await chatModel.sendRequest( - messages, - {}, - cancellationSource.token - ); - - // Convert VS Code LM stream to Vercel AI SDK stream format - const stream = new ReadableStream({ - async start(controller) { - try { - for await (const part of response.stream) { - if (part instanceof vscode.LanguageModelTextPart) { - controller.enqueue({ - type: "text-delta", - textDelta: part.value, - }); - } else if (part instanceof vscode.LanguageModelToolCallPart) { - controller.enqueue({ - type: "tool-call", - toolCallType: "function", - toolCallId: part.callId, - toolName: part.name, - args: JSON.stringify(part.input), - }); - } - } - - controller.enqueue({ - type: "finish", - finishReason: "stop", - usage: { promptTokens: 0, completionTokens: 0 }, - }); - controller.close(); - } catch (error: any) { - if (error?.code === "Canceled" || error?.name === "CancellationError") { - controller.enqueue({ - type: "finish", - finishReason: "abort", - usage: { promptTokens: 0, completionTokens: 0 }, - }); - controller.close(); - } else { - controller.error(error); - } - } - }, - }); - - return { - stream, - rawCall: { rawPrompt: null, rawSettings: {} }, - }; - }, - }; -} - -/** - * Converts Vercel AI SDK message format to VS Code Language Model messages. - */ -function convertToVSCodeMessages( - prompt: Array<{ role: string; content: any }> -): vscode.LanguageModelChatMessage[] { - const messages: vscode.LanguageModelChatMessage[] = []; - - for (const msg of prompt) { - const textContent = typeof msg.content === "string" - ? msg.content - : Array.isArray(msg.content) - ? msg.content - .filter((p: any) => p.type === "text") - .map((p: any) => p.text) - .join("\n") - : String(msg.content); - - switch (msg.role) { - case "system": - messages.push(vscode.LanguageModelChatMessage.Assistant(textContent)); - break; - case "user": - messages.push(vscode.LanguageModelChatMessage.User(textContent)); - break; - case "assistant": - messages.push(vscode.LanguageModelChatMessage.Assistant(textContent)); - break; - default: - messages.push(vscode.LanguageModelChatMessage.User(textContent)); - break; - } - } - - return messages; -} - -/** - * Convenience: selects a VS Code LM model and wraps it for Vercel AI SDK. - * Returns `undefined` if no model is available. - */ -export async function getVSCodeLMModel(options?: { - vendor?: string; - family?: string; - id?: string; -}): Promise { - const model = await selectVSCodeChatModel(options); - if (!model) { - return undefined; - } - return createVSCodeLMAdapter(model); -} diff --git a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts index 2ceeaf04f4..4747d99216 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts @@ -42,7 +42,6 @@ import { createBIWorkspaceWithProject, createEmptyBIWorkspace } from "../../utils/bi"; -import { checkAndRunPendingEnhancement } from "../ai/migration/orchestrator"; import { createVersionNumber, findBallerinaPackageRoot, isSupportedSLVersion } from ".././../utils"; import { extension } from "../../BalExtensionContext"; import { VisualizerWebview } from "../../views/visualizer/webview"; @@ -226,18 +225,6 @@ export function activate(context: BallerinaExtension) { // Open the ballerina toml file as the first file for LS to trigger the project loading openBallerinaTomlFile(context); - - // After the language server and project are fully ready, check whether a - // migration AI enhancement was scheduled before the last folder reload. - const service = StateMachine.service(); - const subscription = service.subscribe((state) => { - if (state.value === "extensionReady" && state.changed) { - subscription.unsubscribe(); - checkAndRunPendingEnhancement().catch((err) => - console.error("[MigrationEnhancement] Unexpected error:", err) - ); - } - }); } diff --git a/workspaces/ballerina/ballerina-extension/src/features/icp/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/icp/activator.ts index 51b5aa6d53..70582f6094 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/icp/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/icp/activator.ts @@ -20,8 +20,7 @@ import * as vscode from 'vscode'; import * as cp from 'child_process'; import { BallerinaExtension } from '../../core'; import { resolveICPPath } from './detect'; -import { provisionICPSecret } from './setup'; -import { getICPUrl } from './index'; +import { provisionICPSecret, getStoredICPSecret, writeSecretToConfigToml } from './setup'; const ICP_START_COMMAND = 'ballerina.icp.start'; const ICP_STOP_COMMAND = 'ballerina.icp.stop'; @@ -29,6 +28,10 @@ const ICP_FOCUS_COMMAND = 'ballerina.icp.focus'; const ICP_TASK_NAME = 'ICP Server'; const ICP_TASK_SOURCE = 'ballerina-icp'; +function getICPUrl(): string { + return vscode.workspace.getConfiguration('ballerina').get('icpUrl') || 'https://localhost:9445'; +} + function getICPCredentials(): { username: string; password: string } { const config = vscode.workspace.getConfiguration('ballerina'); return { @@ -71,12 +74,17 @@ export function isICPServerRunning(): boolean { * Returns true if the run should proceed, false if cancelled. */ export async function ensureICPServerRunning(projectPath?: string): Promise { - if (isRunning && !projectPath) { + const storedSecret = projectPath ? await getStoredICPSecret(projectPath) : undefined; + if (projectPath && storedSecret) { + writeSecretToConfigToml(projectPath, storedSecret); + } + + if (isRunning && (!projectPath || storedSecret)) { return true; } - // Server is running — validate and provision secret if needed - if (isRunning && projectPath) { + // Server is running but this project has no secret — provision it now + if (isRunning && projectPath && !storedSecret) { const secret = await provisionICPSecret(projectPath); if (!secret) { vscode.window.showErrorMessage('Failed to provision ICP secret. The project may not connect to ICP at runtime.'); @@ -85,8 +93,13 @@ export async function ensureICPServerRunning(projectPath?: string): Promise { - if (selection === 'Open in Browser') { - vscode.env.openExternal(vscode.Uri.parse(icpUrl)); + // Provision the secret if we have a project path and no stored secret + if (projectPath && !hasSecret) { + const secret = await provisionICPSecret(projectPath); + if (!secret) { + vscode.window.showErrorMessage('Failed to provision ICP secret. The project may not connect to ICP at runtime.'); + return false; } - }); + vscode.window.showInformationMessage( + `ICP server started and configured. Access it at ${icpUrl}${credentialsHint}`, + 'Open in Browser' + ).then((selection) => { + if (selection === 'Open in Browser') { + vscode.env.openExternal(vscode.Uri.parse(icpUrl)); + } + }); + } else { + vscode.window.showInformationMessage( + `ICP server started. Access it at ${icpUrl}${credentialsHint}`, + 'Open in Browser' + ).then((selection) => { + if (selection === 'Open in Browser') { + vscode.env.openExternal(vscode.Uri.parse(icpUrl)); + } + }); + } return isRunning; } @@ -196,17 +218,11 @@ function createICPTask(icpPath: string): vscode.Task { onDidWrite: writeEmitter.event, onDidClose: closeEmitter.event, open: () => { - icpProcess = process.platform === 'win32' - ? cp.spawn(icpPath, [], { - shell: true, - detached: true, - windowsHide: true, - env: { ...process.env }, - }) - : cp.spawn('sh', [icpPath], { - detached: true, - env: { ...process.env }, - }); + icpProcess = cp.spawn(icpPath, [], { + shell: true, + detached: true, + env: { ...process.env }, + }); icpProcess.on('error', (err) => { writeEmitter.fire(`Failed to start ICP: ${err.message}\r\n`); diff --git a/workspaces/ballerina/ballerina-extension/src/features/icp/index.ts b/workspaces/ballerina/ballerina-extension/src/features/icp/index.ts index 1465d0969e..1720a1a85e 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/icp/index.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/icp/index.ts @@ -1,11 +1,2 @@ -import { workspace } from 'vscode'; - -export const ICP_DEFAULT_PORT = 9446; -export const ICP_DEFAULT_URL = `https://localhost:${ICP_DEFAULT_PORT}`; - -export function getICPUrl(): string { - return workspace.getConfiguration('ballerina').get('icpUrl') || ICP_DEFAULT_URL; -} - export { activateICP, isICPServerRunning, ensureICPServerRunning } from './activator'; export { provisionICPSecret, getStoredICPSecret } from './setup'; diff --git a/workspaces/ballerina/ballerina-extension/src/features/icp/setup.ts b/workspaces/ballerina/ballerina-extension/src/features/icp/setup.ts index 11acc855cd..dbcd03e6a8 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/icp/setup.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/icp/setup.ts @@ -16,94 +16,21 @@ * under the License. */ +import axios from 'axios'; import * as https from 'https'; -import * as net from 'net'; -import * as os from 'os'; import * as fs from 'fs'; import * as path from 'path'; -import * as yaml from 'js-yaml'; import { workspace, window } from 'vscode'; import { extension } from '../../BalExtensionContext'; import { parse, stringify } from '@iarna/toml'; -import { getICPUrl, ICP_DEFAULT_PORT } from './index'; const ICP_SECRET_KEY_PREFIX = 'ICP_ORG_SECRET_'; -const CREATE_ORG_SECRET_MUTATION = JSON.stringify({ - query: 'mutation CreateOrgSecret { createOrgSecret(environmentId: "750e8400-e29b-41d4-a716-446655440001") }' -}); -const ORG_SECRETS_QUERY = JSON.stringify({ - query: 'query OrgSecrets { orgSecrets { keyId environmentId environmentName bound createdAt createdBy } }' -}); +const CREATE_ORG_SECRET_MUTATION = '{"query":"mutation CreateOrgSecret { createOrgSecret(environmentId: \\"750e8400-e29b-41d4-a716-446655440001\\") }"}'; -function isLoopback(hostname: string): boolean { - return hostname === '127.0.0.1' || hostname === 'localhost' || hostname === '::1'; -} +const httpsAgent = new https.Agent({ rejectUnauthorized: false }); -function waitForPort(hostname: string, port: number, timeoutMs: number = 10000): Promise { - const start = Date.now(); - return new Promise((resolve) => { - function tryConnect() { - if (Date.now() - start > timeoutMs) { - resolve(false); - return; - } - const socket = new net.Socket(); - socket.setTimeout(2000); - socket.once('connect', () => { - socket.destroy(); - resolve(true); - }); - socket.once('timeout', () => { - socket.destroy(); - setTimeout(tryConnect, 500); - }); - socket.once('error', () => { - socket.destroy(); - setTimeout(tryConnect, 500); - }); - socket.connect(port, hostname); - } - tryConnect(); - }); -} - -const REQUEST_TIMEOUT_MS = 10000; - -function httpsPost(url: string, body: string, headers: Record): Promise<{ status: number; data: any }> { - return new Promise((resolve, reject) => { - const parsed = new URL(url); - const options: https.RequestOptions = { - hostname: parsed.hostname, - port: parsed.port || 443, - path: parsed.pathname + parsed.search, - method: 'POST', - headers: { ...headers, 'Content-Length': Buffer.byteLength(body) }, - rejectUnauthorized: !isLoopback(parsed.hostname), - timeout: REQUEST_TIMEOUT_MS, - }; - - const req = https.request(options, (res) => { - const chunks: Buffer[] = []; - res.on('data', (chunk) => chunks.push(chunk)); - res.on('end', () => { - const raw = Buffer.concat(chunks).toString(); - let data: any; - try { - data = JSON.parse(raw); - } catch { - data = raw; - } - resolve({ status: res.statusCode || 0, data }); - }); - }); - - req.on('timeout', () => { - req.destroy(new Error(`Request to ${url} timed out after ${REQUEST_TIMEOUT_MS}ms`)); - }); - req.on('error', reject); - req.write(body); - req.end(); - }); +function getICPUrl(): string { + return workspace.getConfiguration('ballerina').get('icpUrl') || 'https://localhost:9445'; } function getICPCredentials(): { username: string; password: string } { @@ -118,70 +45,45 @@ function getGraphQLUrl(): string { const icpUrl = getICPUrl(); try { const url = new URL(icpUrl); + const port = parseInt(url.port || '9445', 10); + url.port = String(port + 1); return `${url.origin}/graphql`; } catch { - return `https://127.0.0.1:${ICP_DEFAULT_PORT}/graphql`; + return 'https://localhost:9446/graphql'; } } async function getICPToken(): Promise { const icpUrl = getICPUrl(); const { username, password } = getICPCredentials(); - const loginUrl = `${icpUrl}/auth/login`; - const response = await httpsPost( - loginUrl, - JSON.stringify({ username, password }), - { 'Content-Type': 'application/json' } + const response = await axios.post( + `${icpUrl}/auth/login`, + { username, password }, + { httpsAgent, headers: { 'Content-Type': 'application/json' } } ); - if (response.status < 200 || response.status >= 300) { - throw new Error(`ICP login failed with status ${response.status}: ${response.data?.message || JSON.stringify(response.data)}`); - } - - const token = response.data?.token; - if (!token || typeof token !== 'string') { - throw new Error(`ICP login response missing token: ${JSON.stringify(response.data)}`); - } - - return token; + return response.data.token; } async function createOrgSecret(token: string): Promise { const graphqlUrl = getGraphQLUrl(); - const response = await httpsPost( + const response = await axios.post( graphqlUrl, CREATE_ORG_SECRET_MUTATION, { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, + httpsAgent, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, } ); return response.data.data.createOrgSecret; } -async function isSecretValid(token: string, secret: string): Promise { - const keyId = secret.split('.')[0]; - if (!keyId) { - return false; - } - - const graphqlUrl = getGraphQLUrl(); - const response = await httpsPost( - graphqlUrl, - ORG_SECRETS_QUERY, - { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - } - ); - - const orgSecrets: { keyId: string }[] = response.data?.data?.orgSecrets || []; - return orgSecrets.some(s => s.keyId === keyId); -} - export async function storeICPSecret(projectPath: string, secret: string): Promise { await extension.context.secrets.store(`${ICP_SECRET_KEY_PREFIX}${projectPath}`, secret); } @@ -190,36 +92,6 @@ export async function getStoredICPSecret(projectPath: string): Promise>; - if (Array.isArray(data) && data[0]?.project) { - return data[0].project; - } - } catch { - // file not found or parse error - } - return 'default-project'; -} - -function getIntegrationName(projectPath: string): string { - const ballerinaToml = path.join(projectPath, 'Ballerina.toml'); - try { - const content = fs.readFileSync(ballerinaToml, 'utf-8'); - const data = parse(content) as Record; - if (data?.package?.name) { - return data.package.name; - } - } catch { - // file not found or parse error - } - return 'default-integration'; -} - export function writeSecretToConfigToml(projectPath: string, secret: string): void { const configPath = path.join(projectPath, 'Config.toml'); let config: Record = {}; @@ -233,18 +105,10 @@ export function writeSecretToConfigToml(projectPath: string, secret: string): vo } } - if (!config.wso2) { config.wso2 = {}; } - if (!config.wso2.icp) { config.wso2.icp = {}; } - if (!config.wso2.icp.runtime) { config.wso2.icp.runtime = {}; } - if (!config.wso2.icp.runtime.bridge) { config.wso2.icp.runtime.bridge = {}; } - - const bridge = config.wso2.icp.runtime.bridge; - bridge.secret = secret; - if (!bridge.environment) { bridge.environment = 'dev'; } - if (!bridge.project) { bridge.project = getProjectHandle(projectPath); } - if (!bridge.integration) { bridge.integration = getIntegrationName(projectPath); } - if (!bridge.runtime) { bridge.runtime = os.hostname(); } - fs.writeFileSync(configPath, stringify(config as any), 'utf-8'); + if (config.wso2?.icp?.runtime?.bridge) { + config.wso2.icp.runtime.bridge.secret = secret; + fs.writeFileSync(configPath, stringify(config), 'utf-8'); + } } /** @@ -254,40 +118,24 @@ export function writeSecretToConfigToml(projectPath: string, secret: string): vo * @returns The provisioned secret, or undefined if provisioning failed. */ export async function provisionICPSecret(projectPath: string): Promise { - try { - const icpUrl = getICPUrl(); - const parsed = new URL(icpUrl); - const port = parseInt(parsed.port || String(ICP_DEFAULT_PORT), 10); - const hostname = parsed.hostname; - - const ready = await waitForPort(hostname, port); - if (!ready) { - throw new Error(`ICP server not reachable at ${hostname}:${port}`); - } + // Check if secret already exists in keychain + const stored = await getStoredICPSecret(projectPath); + if (stored) { + writeSecretToConfigToml(projectPath, stored); + return stored; + } + try { const token = await getICPToken(); - - // Check if stored secret is still valid - const stored = await getStoredICPSecret(projectPath); - if (stored) { - const valid = await isSecretValid(token, stored); - console.log(`[ICP] Stored secret validation: keyId=${stored.split('.')[0]}, valid=${valid}`); - if (valid) { - writeSecretToConfigToml(projectPath, stored); - return stored; - } - console.log('[ICP] Stored secret is invalid, creating a new one'); - } else { - console.log('[ICP] No stored secret found, creating a new one'); - } const secret = await createOrgSecret(token); await storeICPSecret(projectPath, secret); writeSecretToConfigToml(projectPath, secret); return secret; - } catch (error: any) { + } catch (error) { const message = error instanceof Error ? error.message : String(error); + console.error('[ICP] Failed to provision secret:', message); window.showWarningMessage( `Failed to provision ICP secret: ${message}. You can set it manually in Config.toml.` ); diff --git a/workspaces/ballerina/ballerina-extension/src/features/tracing/activate.ts b/workspaces/ballerina/ballerina-extension/src/features/tracing/activate.ts index 5339f55ca9..d376437fb3 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tracing/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tracing/activate.ts @@ -112,7 +112,8 @@ export function activateTracing(ballerinaExtInstance: BallerinaExtension) { } TracerMachine.enable(targetPath); - vscode.window.showInformationMessage('Tracing enabled.'); + // Reveal/focus the ballerina-traceView (shows trace panel in panel) + vscode.commands.executeCommand('workbench.view.extension.ballerina-traceView'); }); const disableTracingCommand = vscode.commands.registerCommand(DISABLE_TRACING_COMMAND, async () => { @@ -121,7 +122,6 @@ export function activateTracing(ballerinaExtInstance: BallerinaExtension) { return; } TracerMachine.disable(targetPath); - vscode.window.showInformationMessage('Tracing disabled.'); }); const clearTracesCommand = vscode.commands.registerCommand(CLEAR_TRACES_COMMAND, () => { diff --git a/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-details-webview.ts b/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-details-webview.ts index d8cca9d326..e8f33e92c8 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-details-webview.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-details-webview.ts @@ -104,13 +104,6 @@ export class TraceDetailsWebview { sessionId: this._sessionId, showSidebar: this._showSidebar, }); - } else if (this._sessionId) { - this._panel!.webview.postMessage({ - command: 'traceData', - data: null, - isAgentChat: this._isAgentChat, - sessionId: this._sessionId, - }); } break; case 'exportTrace': @@ -236,8 +229,6 @@ export class TraceDetailsWebview { instance._panel!.reveal(ViewColumn.One); instance.updateWebview(); - - await instance.handleSessionTracesRequest(sessionId); } private updateWebview(): void { diff --git a/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-server-task.ts b/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-server-task.ts index 9e3a27b9e7..1e1bcf33e4 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-server-task.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tracing/trace-server-task.ts @@ -46,7 +46,7 @@ export function createTraceServerTask(): vscode.Task { // Configure task presentation task.presentationOptions = { - reveal: vscode.TaskRevealKind.Never, + reveal: vscode.TaskRevealKind.Always, panel: vscode.TaskPanelKind.New, showReuseMessage: false, clear: false, diff --git a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts index 13a1aa90ec..dfa8a67948 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts @@ -23,7 +23,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { BallerinaExtension } from "src/core"; import Handlebars from "handlebars"; -import { clientManager, findRunningBallerinaProcesses, handleError, waitForBallerinaService } from "./utils"; +import { clientManager, findRunningBallerinaProcesses, handleError, HTTPYAC_CONFIG_TEMPLATE, TRYIT_TEMPLATE, waitForBallerinaService } from "./utils"; import { buildHurlCellsFromOASSpec } from "./hurl-builder"; import { BIDesignModelResponse, EVENT_TYPE, MACHINE_VIEW, OpenAPISpec, ProjectInfo } from "@wso2/ballerina-core"; import { getProjectWorkingDirectory } from "../../utils/file-utils"; @@ -38,8 +38,9 @@ import { TracerMachine } from "../tracing"; // File constants const FILE_NAMES = { - TRYIT: 'TryIt.hurl', - ERROR_LOG: 'tryit_errors.log' + TRYIT: 'tryit.http', + HTTPYAC_CONFIG: 'httpyac.config.js', + ERROR_LOG: 'httpyac_errors.log' }; let errorLogWatcher: FileSystemWatcher | undefined; @@ -57,15 +58,60 @@ export function activateTryItCommand(ballerinaExtInstance: BallerinaExtension) { clientManager.setClient(ballerinaExtInstance.langClient); // Register try it command handler - const disposable = commands.registerCommand(PALETTE_COMMANDS.TRY_IT, async (withNotice: boolean = false, resourceMetadata?: ResourceMetadata, serviceMetadata?: ServiceMetadata, filePath?: string, autoRun?: boolean) => { + const disposable = commands.registerCommand(PALETTE_COMMANDS.TRY_IT, async (withNotice: boolean = false, resourceMetadata?: ResourceMetadata, serviceMetadata?: ServiceMetadata, filePath?: string) => { try { - await openTryItView(withNotice, resourceMetadata, serviceMetadata, filePath, autoRun); + await openTryItView(withNotice, resourceMetadata, serviceMetadata, filePath); } catch (error) { handleError(error, "Opening Try It view failed"); } }); - return Disposable.from(disposable, { + // Command: start Ballerina service (no old Try It UI), then open WSO2 HTTP Client notebook + const startServiceDisposable = commands.registerCommand('ballerina.startService', + async ( + content?: string | object[] | { oasSpec: any; baseUrl: string; serviceName: string; resourceMetadata?: { methodValue: string; pathValue: string } }, + options?: { savable?: boolean }, + serviceMetadata?: ServiceMetadata, + filePath?: string + ) => { + try { + const projectAndServices = await getProjectPathAndServices(serviceMetadata, filePath); + if (!projectAndServices) { return; } + + const { projectPath, services } = projectAndServices; + const processesRunning = await checkBallerinaProcessRunning(projectPath); + if (!processesRunning) { return; } + + if (content) { + const savePath = path.join(projectPath, 'target', 'TryIt.hurl'); + if (isOasDescriptor(content)) { + // Resolve the actual running service port (same logic as Code Lens Try It) + let descriptor = content; + try { + const matchingService = services.find(s => + s.basePath === serviceMetadata?.basePath && + (!serviceMetadata?.listener || compareListeners(s.listener, serviceMetadata.listener)) + ) ?? (services.length === 1 ? services[0] : undefined); + if (matchingService) { + const actualPort = await getServicePort(projectPath, matchingService, content.oasSpec); + const bp = matchingService.basePath === '/' ? '' : sanitizePath(matchingService.basePath); + descriptor = { ...content, baseUrl: `http://localhost:${actualPort}${bp}` }; + } + } catch { + // Fall back to the static baseUrl already in the descriptor + } + await openHurlNotebook(descriptor, savePath, options); + } else { + await openTryItNotebook(content, { ...options, savePath }); + } + } + } catch (error) { + handleError(error, "Starting Ballerina service"); + } + } + ); + + return Disposable.from(disposable, startServiceDisposable, { dispose: disposeErrorWatcher }); } catch (error) { @@ -73,7 +119,7 @@ export function activateTryItCommand(ballerinaExtInstance: BallerinaExtension) { } } -async function openTryItView(withNotice: boolean = false, resourceMetadata?: ResourceMetadata, serviceMetadata?: ServiceMetadata, filePath?: string, autoRun?: boolean): Promise { +async function openTryItView(withNotice: boolean = false, resourceMetadata?: ResourceMetadata, serviceMetadata?: ServiceMetadata, filePath?: string): Promise { try { if (!clientManager.hasClient()) { throw new Error('Ballerina Language Server is not connected'); @@ -104,15 +150,13 @@ async function openTryItView(withNotice: boolean = false, resourceMetadata?: Res wasServiceAlreadyRunning = false; } else { - const processesRunning = autoRun - ? await autoRunIntegration(projectPath) - : await checkBallerinaProcessRunning(projectPath); + const processesRunning = await checkBallerinaProcessRunning(projectPath); if (!processesRunning) { return; } } - let selectedService: ServiceInfo | undefined; + let selectedService: ServiceInfo; // If in resource try it mode, find the service containing the resource path if (resourceMetadata) { const matchingService = await findServiceForResource(services, resourceMetadata, serviceMetadata); @@ -124,18 +168,14 @@ async function openTryItView(withNotice: boolean = false, resourceMetadata?: Res selectedService = matchingService; } else if (services.length > 1) { if (serviceMetadata) { - const normalize = (p: string) => p.replace(/\\/g, ''); const matchingService = services.find(service => - normalize(service.basePath) === normalize(serviceMetadata.basePath) && compareListeners(service.listener, serviceMetadata.listener) + service.basePath === serviceMetadata.basePath && compareListeners(service.listener, serviceMetadata.listener) ); if (matchingService) { selectedService = matchingService; } - } - - // If no service was matched via metadata, fall back to QuickPick - if (!selectedService) { + } else { const quickPickItems = services.map(service => ({ label: `'${service.basePath}' on ${service.listener.name}`, description: `${service.type} Service`, @@ -176,8 +216,7 @@ async function openTryItView(withNotice: boolean = false, resourceMetadata?: Res const baseUrl = `http://localhost:${selectedPort}${basePath}`; const serviceName = selectedService.name || selectedService.basePath; const savePath = path.join(projectPath, 'target', 'TryIt.hurl'); - // Service Try It is savable (Cmd+S saves in-place); Resource Try It is read-only. - await openHurlNotebook({ oasSpec: openapiSpec, baseUrl, serviceName, resourceMetadata }, savePath, { savable: !resourceMetadata }); + await openHurlNotebook({ oasSpec: openapiSpec, baseUrl, serviceName, resourceMetadata }, savePath, { savable: true }); } else if (selectedService.type === ServiceType.GRAPHQL) { const selectedPort: number = await getServicePort(projectPath, selectedService); const port = selectedPort; @@ -219,7 +258,7 @@ async function openTryItView(withNotice: boolean = false, resourceMetadata?: Res } // --------------------------------------------------------------------------- -// Hurl notebook helpers +// Hurl notebook helpers — shared by ballerina.startService and openTryItView // --------------------------------------------------------------------------- interface OasDescriptor { @@ -229,27 +268,15 @@ interface OasDescriptor { resourceMetadata?: { methodValue: string; pathValue: string }; } +function isOasDescriptor(v: any): v is OasDescriptor { + return v !== null && typeof v === 'object' && !Array.isArray(v) && 'oasSpec' in v && 'baseUrl' in v && 'serviceName' in v; +} + async function openHurlNotebook( descriptor: OasDescriptor, savePath: string, options?: { savable?: boolean } ): Promise { - // If TryIt.hurl is already open as a notebook, VS Code returns the cached in-memory - // document and ignores the freshly-written file (e.g. switching Service → Resource Try It). - // Close all tabs for that file first so the new content is read from disk. - const saveUri = vscode.Uri.file(savePath); - const isAlreadyOpen = vscode.workspace.notebookDocuments.some(d => d.uri.fsPath === saveUri.fsPath); - if (isAlreadyOpen) { - await Promise.all( - vscode.window.tabGroups.all - .flatMap(g => g.tabs) - .filter(t => { - const input = t.input as any; - return input?.uri?.fsPath === saveUri.fsPath || input?.notebook?.uri?.fsPath === saveUri.fsPath; - }) - .map(tab => vscode.window.tabGroups.close(tab, true)) - ); - } const cells = buildHurlCellsFromOASSpec(descriptor.oasSpec, descriptor.baseUrl, descriptor.serviceName, descriptor.resourceMetadata); await openTryItNotebook(cells, { ...options, savePath }); } @@ -476,6 +503,82 @@ async function getAvailableServices(projectDir: string): Promise { + try { + // Register Handlebars helpers + registerHandlebarsHelpers(openapiSpec); + + let isResourceMode = false; + let resourcePath = ''; + // Filter paths based on resourceMetadata if provided + if (resourceMetadata) { + const originalPaths = openapiSpec.paths; + const filteredPaths: Record> = {}; + + let matchingPath = ''; + for (const path in originalPaths) { + const pathMatches = comparePathPatterns(path, resourceMetadata.pathValue); + if (pathMatches) { + matchingPath = path; + break; + } + } + + if (matchingPath && originalPaths[matchingPath]) { + isResourceMode = true; + resourcePath = matchingPath; + + const method = resourceMetadata.methodValue.toLowerCase(); + if (originalPaths[matchingPath][method]) { + // Create entry with only the specified method + filteredPaths[matchingPath] = { + [method]: { + ...originalPaths[matchingPath][method] + } + }; + } else { + // Method not found in matching path + vscode.window.showWarningMessage(`Method ${resourceMetadata.methodValue} not found for path ${matchingPath}. Showing all methods for this path.`); + filteredPaths[matchingPath] = originalPaths[matchingPath]; + } + + openapiSpec.paths = filteredPaths; + } else { + // Path not found in OpenAPI spec + vscode.window.showWarningMessage( + `Path ${resourceMetadata.pathValue} not found in service ${service.name || service.basePath}. Showing all resources.` + ); + } + } + + const tryitCompiledTemplate = Handlebars.compile(TRYIT_TEMPLATE); + const tryitContent = tryitCompiledTemplate({ + ...openapiSpec, + port: service.port.toString(), + basePath: service.basePath === '/' ? '' : sanitizePath(service.basePath), // to avoid double slashes in the URL + serviceName: service.name || '/', + isResourceMode: isResourceMode, + resourceMethod: isResourceMode ? resourceMetadata?.methodValue.toUpperCase() : '', + resourcePath: resourcePath, + }); + + const httpyacCompiledTemplate = Handlebars.compile(HTTPYAC_CONFIG_TEMPLATE); + const httpyacContent = httpyacCompiledTemplate({ + errorLogFile: FILE_NAMES.ERROR_LOG, + }); + + const tryitFilePath = path.join(targetDir, FILE_NAMES.TRYIT); + const configFilePath = path.join(targetDir, FILE_NAMES.HTTPYAC_CONFIG); + fs.writeFileSync(tryitFilePath, tryitContent); + fs.writeFileSync(configFilePath, httpyacContent); + + return vscode.Uri.file(tryitFilePath); + } catch (error) { + handleError(error, "Try It client initialization failed"); + return undefined; + } +} + // Helper function to compare path patterns, considering path parameters function comparePathPatterns(specPath: string, targetPath: string): boolean { const specSegments = specPath.split('/').filter(Boolean); @@ -601,43 +704,6 @@ async function getServicePort(projectDir: string, service: ServiceInfo, openapiS } } -/** - * Automatically starts the integration without prompting the user. - * Used when the intent to run is already clear (e.g., user clicked "Chat" on an agent). - */ -async function autoRunIntegration(projectDir: string): Promise { - try { - const balProcesses = await findRunningBallerinaProcesses(projectDir) - .catch(error => { - throw new Error(`Failed to find running Ballerina processes: ${error.message}`); - }); - - if (balProcesses?.length) { - return true; - } - - const { workspacePath, view: webviewType } = StateMachine.context(); - const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; - const needsPackageSelection = requiresPackageSelection(workspacePath, webviewType, projectDir, isWebviewOpen, false); - - if (isWebviewOpen && needsPackageSelection) { - openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.PackageOverview, projectPath: projectDir }); - } - - clearTerminal(); - await startDebugging(Uri.file(projectDir), false, false, true); - - const newProcesses = await waitForBallerinaService(projectDir).then(() => { - return findRunningBallerinaProcesses(projectDir); - }); - - return newProcesses?.length > 0; - } catch (error) { - handleError(error, "Auto-running integration", false); - return false; - } -} - /** * Helper function to detect running Ballerina processes and, prompt the user to run the program if not found */ @@ -975,10 +1041,10 @@ function compareListeners(serviceInfoListener: { name: string, port?: string }, return true; } - // anonymous listeners - handle any listener type (e.g., http:Listener, ai:Listener, etc.) - if (serviceMetadataListener.startsWith('new ') && serviceInfoListener.port) { - // Extract port from patterns like 'new http:Listener(9090)', 'new ai:Listener(8080)', etc. - const portMatch = serviceMetadataListener.match(/new \w+:\w+\((\d+)/); + // anonymous listeners + if (serviceMetadataListener.startsWith('new http:Listener') && serviceInfoListener.port) { + // Extract port from 'http:Listener(9090)' + const portMatch = serviceMetadataListener.match(/new http:Listener\((\d+)\)/); if (portMatch && portMatch[1]) { const port = parseInt(portMatch[1], 10); return port === parseInt(serviceInfoListener.port); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-handler.ts index 86f8d821ec..9f1638aa0f 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-handler.ts @@ -43,8 +43,7 @@ import { updateAIAgentTools, updateMCPToolKit, AIGetPackageVersionRequest, - getPackageVersion, - fixMissingImports + getPackageVersion } from "@wso2/ballerina-core"; import { Messenger } from "vscode-messenger"; import { AiAgentRpcManager } from "./rpc-manager"; @@ -60,7 +59,6 @@ export function registerAiAgentRpcHandlers(messenger: Messenger) { messenger.onRequest(getTool, (args: AIToolRequest) => rpcManger.getTool(args)); messenger.onRequest(getMcpTools, (args: McpToolsRequest) => rpcManger.getMcpTools(args)); messenger.onRequest(genTool, (args: AIGentToolsRequest) => rpcManger.genTool(args)); - messenger.onRequest(fixMissingImports, () => rpcManger.fixMissingImports()); messenger.onRequest(getPackageVersion, (args: AIGetPackageVersionRequest) => rpcManger.getPackageVersion(args)); messenger.onNotification(configureDefaultModelProvider, () => rpcManger.configureDefaultModelProvider()); messenger.onRequest(createAIAgent, (args: AIAgentRequest) => rpcManger.createAIAgent(args)); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-manager.ts index 60af5dea31..a6c4126c9e 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-agent/rpc-manager.ts @@ -51,7 +51,6 @@ import { StateMachine } from "../../stateMachine"; import { writeBallerinaFileDidOpen } from "../../utils/modification"; import { updateSourceCode } from "../../utils/source-utils"; import { isLibraryProject } from "../../utils/config"; -import { addMissingImports, checkProjectDiagnostics, removeUnusedImports } from "../ai-panel/repair-utils"; import { CONFIGURE_DEFAULT_MODEL_COMMAND } from "../../features/ai/constants"; @@ -150,17 +149,6 @@ export class AiAgentRpcManager implements AIAgentAPI { }); } - async fixMissingImports(): Promise { - const context = StateMachine.context(); - try { - const projectDiags = await checkProjectDiagnostics(context.langClient, context.projectPath); - await addMissingImports(projectDiags, context.langClient); - await removeUnusedImports(projectDiags, context.langClient); - } catch (e) { - console.log("fixMissingImports failed", e); - } - } - async getPackageVersion(params: AIGetPackageVersionRequest): Promise { const context = StateMachine.context(); try { @@ -514,19 +502,6 @@ export class AiAgentRpcManager implements AIAgentAPI { } else { toolsValue = `[${mcpToolKitVarName}]`; } - } else if (Array.isArray(toolsValue) && typeof mcpToolKitVarName === "string") { - const toolExists = toolsValue.some((tool: any) => tool.value === mcpToolKitVarName); - if (!toolExists) { - (toolsValue as any[]).push({ - metadata: { - label: mcpToolKitVarName, - description: "", - }, - value: mcpToolKitVarName, - optional: false, - editable: false, - }); - } } else { toolsValue = `[${mcpToolKitVarName}]`; } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/repair-utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/repair-utils.ts index f3142a6a67..7d66621943 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/repair-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/repair-utils.ts @@ -49,20 +49,15 @@ export async function checkProjectDiagnostics(langClient: ExtendedLangClient, te if (isAISchema) { projectUri = Uri.file(tempDir).with({ scheme: 'ai' }).toString(); } - const _diagStartMs = Date.now(); - console.log(`[DiagTiming] getProjectDiagnostics START — URI: ${projectUri}`); + console.log("Getting project diagnostics for URI:", projectUri); let response: ProjectDiagnosticsResponse = await langClient.getProjectDiagnostics({ projectRootIdentifier: { uri: projectUri } }); - const _diagDurationMs = Date.now() - _diagStartMs; if (!response.errorDiagnosticMap) { - console.log(`[DiagTiming] getProjectDiagnostics END — ${_diagDurationMs}ms — no errorDiagnosticMap (internal error)`); throw new Error("Internal error while getting diagnostics from language server"); } - const _fileCount = Object.keys(response.errorDiagnosticMap).length; - console.log(`[DiagTiming] getProjectDiagnostics END — ${_diagDurationMs}ms, ${_fileCount} files with errors`); if (Object.keys(response.errorDiagnosticMap).length === 0) { return []; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts index dfe0f81c32..95afd7e698 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts @@ -97,20 +97,11 @@ import { compactConversation, CompactConversationRequest, getShowContextUsage, - submitClarifyAnswer, - cancelClarify, - ClarifyAnswerRequest, - ClarifyCancelRequest, - getRunningServices, - stopRunningService, - StopRunningServiceRequest, } from "@wso2/ballerina-core"; import { workspace } from 'vscode'; import { Messenger } from "vscode-messenger"; import { AiPanelRpcManager } from "./rpc-manager"; import { sendConfigChangeNotification } from "../../features/ai/utils/ai-utils"; -import { runningServicesManager } from "../../features/ai/agent/tools/running-service-manager"; -import { notifyRunningServicesChanged } from "../../RPCLayer"; export function registerAiPanelRpcHandlers(messenger: Messenger) { const rpcManger = new AiPanelRpcManager(); @@ -175,13 +166,4 @@ export function registerAiPanelRpcHandlers(messenger: Messenger) { }); messenger.onRequest(enhancePrompt, (args: PromptEnhancementRequest) => rpcManger.enhancePrompt(args)); messenger.onNotification(promptForLogin, () => rpcManger.promptForLogin()); - messenger.onRequest(submitClarifyAnswer, (args: ClarifyAnswerRequest) => rpcManger.submitClarifyAnswer(args)); - messenger.onRequest(cancelClarify, (args: ClarifyCancelRequest) => rpcManger.cancelClarify(args)); - messenger.onRequest(getRunningServices, () => rpcManger.getRunningServices()); - messenger.onRequest(stopRunningService, (args: StopRunningServiceRequest) => rpcManger.stopRunningService(args)); - - // Push updates to the webview whenever the set of running services changes. - runningServicesManager.onChange = (services) => { - notifyRunningServicesChanged(services); - }; } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts index 1dd9be3fc4..3b82b97e36 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts @@ -50,10 +50,6 @@ import { WebToolApprovalRequest, CompactConversationRequest, CompactConversationResponse, - ClarifyAnswerRequest, - ClarifyCancelRequest, - RunningServiceInfo, - StopRunningServiceRequest, } from "@wso2/ballerina-core"; import * as fs from 'fs'; import path from "path"; @@ -87,7 +83,6 @@ import { import { addToIntegration, searchDocumentation } from "./utils"; import { createExecutorConfig, generateAgent, resolveProjectRootPath } from '../../features/ai/agent/index'; -import { clearCompactionDisabledWarning } from '../../features/ai/agent/AgentExecutor'; import { LLM_API_BASE_PATH, WI_EXTENSION_ID } from "../../features/ai/constants"; import { ContextTypesExecutor } from '../../features/ai/executors/datamapper/ContextTypesExecutor'; import { FunctionMappingExecutor } from '../../features/ai/executors/datamapper/FunctionMappingExecutor'; @@ -95,8 +90,9 @@ import { InlineMappingExecutor } from '../../features/ai/executors/datamapper/In import { approvalManager } from '../../features/ai/state/ApprovalManager'; import { cleanupTempProject } from "../../features/ai/utils/project/temp-project"; import { chatStateStorage } from '../../views/ai-panel/chatStateStorage'; +import { compactionManager } from '../../features/ai/compaction-manager'; +import { getAnthropicClient, ANTHROPIC_SONNET_4 } from '../../features/ai/utils/ai-client'; import { restoreWorkspaceSnapshot } from '../../views/ai-panel/checkpoint/checkpointUtils'; -import { runningServicesManager } from '../../features/ai/agent/tools/running-service-manager'; export class AiPanelRpcManager implements AIPanelAPI { @@ -603,14 +599,6 @@ export class AiPanelRpcManager implements AIPanelAPI { approvalManager.resolveWebToolApproval(params.requestId, false); } - async submitClarifyAnswer(params: ClarifyAnswerRequest): Promise { - approvalManager.resolveClarify(params.requestId, true, params.answers); - } - - async cancelClarify(params: ClarifyCancelRequest): Promise { - approvalManager.resolveClarify(params.requestId, false); - } - async restoreCheckpoint(params: RestoreCheckpointRequest): Promise { // Get project root path and thread identifiers const projectRootPath = resolveProjectRootPath(); @@ -652,7 +640,6 @@ export class AiPanelRpcManager implements AIPanelAPI { // Clear the workspace (all threads) await chatStateStorage.clearWorkspace(projectRootPath); - clearCompactionDisabledWarning(projectRootPath, 'default'); console.log(`[RPC] Cleared chat for projectRootPath: ${projectRootPath}`); } @@ -741,13 +728,48 @@ export class AiPanelRpcManager implements AIPanelAPI { return projectPath; } - async compactConversation(_params: CompactConversationRequest): Promise { - // Manual compaction is no longer supported. Context is managed automatically - // server-side via the compact_20260112 API during agent execution. - return { - success: false, - error: 'Manual compaction is not available. Context is automatically managed by the server during agent execution.', - }; + async compactConversation(params: CompactConversationRequest): Promise { + const workspaceId = resolveProjectRootPath(); + const threadId = 'default'; + + // M05: Reject manual compact if an AI generation is in progress + const activeExecution = chatStateStorage.getActiveExecution(workspaceId, threadId); + if (activeExecution) { + return { + success: false, + error: 'Cannot compact while a generation is in progress. Please wait for it to complete or stop it first.', + }; + } + + try { + const model = await getAnthropicClient(ANTHROPIC_SONNET_4); + + const result = await compactionManager.manualCompact( + workspaceId, + threadId, + model, + params.customInstructions + ); + + console.log( + `[RPC] Compacted conversation for workspace: ${workspaceId} ` + + `(${result.reductionPercentage.toFixed(1)}% reduction)` + ); + + return { + success: true, + originalTokens: result.originalTokens, + compactedTokens: result.compactedTokens, + reductionPercentage: result.reductionPercentage, + summary: result.summary, + }; + } catch (error) { + console.error(`[RPC] Compaction failed for workspace: ${workspaceId}`, error); + return { + success: false, + error: error instanceof Error ? error.message : 'Compaction failed', + }; + } } async getShowContextUsage(): Promise { @@ -838,12 +860,4 @@ export class AiPanelRpcManager implements AIPanelAPI { viewColumn: vscode.ViewColumn.One, }); } - - async getRunningServices(): Promise { - return runningServicesManager.getAll(); - } - - async stopRunningService(params: StopRunningServiceRequest): Promise { - return runningServicesManager.stopOne(params.taskId); - } } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts index 7743078acd..5748fa9521 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts @@ -22,9 +22,10 @@ import { Position, Range, Uri, workspace, WorkspaceEdit } from 'vscode'; import path from "path"; import * as fs from 'fs'; import { AIChatError } from "./utils/errors"; -import { generateMappingInstructionFromFiles, processDataMapperInput } from "../../features/ai/data-mapper/context-api"; +import { processDataMapperInput } from "../../features/ai/data-mapper/context-api"; import { DataMapperRequest, DataMapperResponse, FileData, RepairedMappings } from "../../features/ai/data-mapper/types"; import { getAskResponse } from "../../features/ai/ask/index"; +import { MappingFileRecord} from "./types"; import { generateAutoMappings, generateRepairCode } from "../../features/ai/data-mapper/index"; import { ArtifactNotificationHandler, ArtifactsUpdated } from "../../utils/project-artifacts-handler"; import { CopilotEventHandler } from "../../features/ai/utils/events"; @@ -155,13 +156,18 @@ export async function enrichModelWithMappingInstructions(mappingInstructionFiles mappingInstructionFiles.map(file => convertAttachmentToFileData(file)) ); - const mappingInstructions = await generateMappingInstructionFromFiles(fileDataArray); + const requestParams: DataMapperRequest = { + files: fileDataArray, + processType: "mapping_instruction" + }; + const response: DataMapperResponse = await processDataMapperInput(requestParams); + let parsedMappingInstructions: MappingFileRecord = JSON.parse(response.fileContent) as MappingFileRecord; return { ...currentDataMapperResponse, mappingsModel: { ...currentDataMapperResponse.mappingsModel, - mapping_fields: mappingInstructions.mapping_fields + mapping_fields: parsedMappingInstructions.mapping_fields } }; } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-handler.ts index efeb5a54cd..f88c976840 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-handler.ts @@ -26,7 +26,6 @@ import { addProjectToWorkspace, AddProjectToWorkspaceRequest, AIChatRequest, - InlineAgentChatRequest, BIAiSuggestionsRequest, BIAvailableNodesRequest, BIDeleteByComponentInfoRequest, @@ -131,8 +130,6 @@ import { ModelFromCodeRequest, openAIChat, OpenAPIClientDeleteRequest, - startInlineAgentChat, - cleanupAgentChatServices, OpenAPIClientGenerationRequest, OpenAPIGeneratedModulesRequest, openConfigToml, @@ -166,12 +163,7 @@ import { VerifyTypeDeleteRequest, VisibleTypesRequest, ValidateProjectFormRequest, - validateProjectPath, - getSuggestedProjectDefaults, - UpdateProjectTitleRequest, - UpdatePackageTitleRequest, - updateProjectTitle, - updatePackageTitle + validateProjectPath } from "@wso2/ballerina-core"; import { Messenger } from "vscode-messenger"; import { BiDiagramRpcManager } from "./rpc-manager"; @@ -217,8 +209,6 @@ export function registerBiDiagramRpcHandlers(messenger: Messenger) { messenger.onRequest(deployProject, (args: DeploymentRequest) => rpcManger.deployProject(args)); messenger.onRequest(deployWorkspace, (args: WorkspaceDeploymentRequest) => rpcManger.deployWorkspace(args)); messenger.onNotification(openAIChat, (args: AIChatRequest) => rpcManger.openAIChat(args)); - messenger.onNotification(startInlineAgentChat, (args: InlineAgentChatRequest) => rpcManger.startInlineAgentChat(args)); - messenger.onRequest(cleanupAgentChatServices, () => rpcManger.cleanupAgentChatServices()); messenger.onRequest(getSignatureHelp, (args: SignatureHelpRequest) => rpcManger.getSignatureHelp(args)); messenger.onNotification(buildProject, (args: BuildMode) => rpcManger.buildProject(args)); messenger.onNotification(runProject, () => rpcManger.runProject()); @@ -264,7 +254,4 @@ export function registerBiDiagramRpcHandlers(messenger: Messenger) { messenger.onRequest(generateOpenApiClient, (args: OpenAPIClientGenerationRequest) => rpcManger.generateOpenApiClient(args)); messenger.onRequest(getOpenApiGeneratedModules, (args: OpenAPIGeneratedModulesRequest) => rpcManger.getOpenApiGeneratedModules(args)); messenger.onRequest(deleteOpenApiGeneratedModules, (args: OpenAPIClientDeleteRequest) => rpcManger.deleteOpenApiGeneratedModules(args)); - messenger.onRequest(updateProjectTitle, (args: UpdateProjectTitleRequest) => rpcManger.updateProjectTitle(args)); - messenger.onRequest(updatePackageTitle, (args: UpdatePackageTitleRequest) => rpcManger.updatePackageTitle(args)); - messenger.onRequest(getSuggestedProjectDefaults, (args: { isInProject: boolean }) => rpcManger.getSuggestedProjectDefaults(args)); } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index 5a4e2c82c7..ce6599ee99 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -20,7 +20,6 @@ import { AIChatRequest, AddFieldRequest, - InlineAgentChatRequest, AddFunctionRequest, AddImportItemResponse, AddProjectToWorkspaceRequest, @@ -78,6 +77,7 @@ import { DevantMetadata, WorkspaceDevantMetadata, ProjectDevantMetadata, + Diagnostics, EndOfFileRequest, ExpressionCompletionsRequest, ExpressionCompletionsResponse, @@ -154,13 +154,7 @@ import { Item, Category, NodePosition, - PackageTomlValues, - EVENT_TYPE, - UpdateProjectTitleRequest, - UpdatePackageTitleRequest, - SuggestedProjectDefaultsResponse, - ProjectInfo, - PROJECT_KIND + PackageTomlValues } from "@wso2/ballerina-core"; import * as fs from "fs"; import * as path from 'path'; @@ -184,7 +178,7 @@ import { DebugProtocol } from "vscode-debugprotocol"; import { extension } from "../../BalExtensionContext"; import { OLD_BACKEND_URL } from "../../features/ai/utils"; import { fetchWithAuth } from "../../features/ai/utils/ai-client"; -import { getCurrentBIProject } from "../../features/config-generator/configGenerator"; +import { cleanAndValidateProject, getCurrentBIProject } from "../../features/config-generator/configGenerator"; import { BreakpointManager } from "../../features/debugger/breakpoint-manager"; import { StateMachine, updateView } from "../../stateMachine"; import { getAccessToken, getLoginMethod } from "../../utils/ai/auth"; @@ -198,9 +192,8 @@ import { createBIWorkspaceWithProject, createEmptyBIWorkspace, deleteProjectFromWorkspace, - openInVSCode, - validateProjectPath, - getSuggestedProjectDefaults + openInVSCode + , validateProjectPath } from "../../utils/bi"; import { writeBallerinaFileDidOpen } from "../../utils/modification"; import { updateSourceCode } from "../../utils/source-utils"; @@ -208,6 +201,7 @@ import { getView } from "../../utils/state-machine-utils"; import { isLibraryProject } from "../../utils/config"; import { PlatformExtRpcManager } from "../platform-ext/rpc-manager"; import { openAIPanelWithPrompt } from "../../views/ai-panel/aiMachine"; +import { checkProjectDiagnostics, removeUnusedImports } from "../ai-panel/repair-utils"; import { getCurrentBallerinaProject } from "../../utils/project-utils"; import { CommonRpcManager } from "../common/rpc-manager"; import * as toml from "@iarna/toml"; @@ -217,53 +211,7 @@ import { chatStateStorage } from "../../views/ai-panel/chatStateStorage"; import { getRepoRoot } from "../platform-ext/platform-utils"; import { WI_EXTENSION_ID } from "../../utils"; import { notifyOnIdentifierUpdated } from "../../RPCLayer"; -import { openView } from "../../stateMachine"; -function ensureGitignoreEntry(projectRoot: string): void { - const gitignorePath = path.join(projectRoot, '.gitignore'); - const entry = '_agent_chat.bal'; - - try { - let content = ''; - if (fs.existsSync(gitignorePath)) { - content = fs.readFileSync(gitignorePath, 'utf8'); - } - if (!content.split('\n').some(line => line.trim() === entry)) { - const newline = content.endsWith('\n') || content === '' ? '' : '\n'; - fs.appendFileSync(gitignorePath, `${newline}${entry}\n`); - } - } catch (e) { - console.warn('[agent-chat] Failed to update .gitignore:', e); - } -} - -/** - * Reads a Ballerina.toml file, sets `doc[section][field] = value`, and writes it back. - * Throws if the file is missing or unparseable. - */ -function setTomlSectionField(filePath: string, section: string, field: string, value: string): void { - let content: string; - try { - content = fs.readFileSync(filePath, 'utf-8'); - } catch { - throw new Error(`Ballerina.toml not found at ${filePath}`); - } - let doc: ReturnType; - try { - doc = toml.parse(content); - } catch (e) { - const message = e instanceof Error ? e.message : String(e); - throw new Error(`Invalid TOML in ${filePath}: ${message}`); - } - const sectionObj = doc[section]; - if (sectionObj !== null && typeof sectionObj === "object" && !Array.isArray(sectionObj)) { - (sectionObj as Record)[field] = value; - } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (doc as any)[section] = { [field]: value }; - } - fs.writeFileSync(filePath, toml.stringify(doc), "utf-8"); -} export class BiDiagramRpcManager implements BIDiagramAPI { OpenConfigTomlRequest: (params: OpenConfigTomlRequest) => Promise; @@ -760,12 +708,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { } async validateProjectPath(params: ValidateProjectFormRequest): Promise { - // When converting an integtatino/library to a project, the new directory is created as a sibling of the - // current integration/library (i.e. under path.dirname(projectPath)), not inside the project itself. - const basePath = params.createAsWorkspace - ? path.dirname(StateMachine.context().projectPath) - : params.projectPath; - return validateProjectPath(basePath, params.projectName, params.createDirectory, params.createAsWorkspace); + return validateProjectPath(params.projectPath, params.projectName, params.createDirectory, params.createAsWorkspace); } async deleteProject(params: DeleteProjectRequest): Promise { @@ -807,7 +750,6 @@ export class BiDiagramRpcManager implements BIDiagramAPI { } catch (error) { window.showErrorMessage("Error converting integration to workspace"); console.error("Error converting integration to workspace:", error); - return; } } else { try { @@ -861,7 +803,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { return new Promise(async (resolve) => { const projectPath = StateMachine.context().projectPath; if (!projectPath) { - resolve({ components: { packages: [] } }); + resolve({ components: {packages: []} }); return; } const components = await StateMachine.langClient().getBallerinaProjectComponents({ @@ -999,6 +941,8 @@ export class BiDiagramRpcManager implements BIDiagramAPI { async deleteFlowNode(params: BISourceCodeRequest): Promise { console.log(">>> requesting bi delete node from ls", params); + // Clean project diagnostics before deleting flow node + await cleanAndValidateProject(StateMachine.langClient(), StateMachine.context().projectPath); return new Promise((resolve) => { StateMachine.langClient() @@ -1242,7 +1186,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { window.showWarningMessage("No deployable projects found in the workspace."); return { isCompleted: true }; } - const integrations: ICreateNewIntegrationCmdIntegrations[] = []; + const integrations: ICreateNewIntegrationCmdIntegrations[]= []; // If there is only one project in the workspace and it has multiple integration types, // ask the user to pick the type similar to the single project deploy flow. @@ -1255,7 +1199,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { return { isCompleted: true }; } - integrations.push({ fsPath: projectPath, supportedIntegrationTypes: [integrationType] }); + integrations.push({fsPath: projectPath, supportedIntegrationTypes: [integrationType] }); } else { for (const projectScope of projectScopes) { const { projectPath, integrationTypes } = projectScope; @@ -1264,7 +1208,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { continue; } - integrations.push({ fsPath: projectPath, supportedIntegrationTypes: integrationTypes }); + integrations.push({fsPath: projectPath, supportedIntegrationTypes: integrationTypes }); } } @@ -1314,96 +1258,6 @@ export class BiDiagramRpcManager implements BIDiagramAPI { } } - async startInlineAgentChat(params: InlineAgentChatRequest): Promise { - try { - const confirm = await window.showInformationMessage( - 'This will generate a chat service for this agent in your project.', - 'Continue', - 'Cancel' - ); - if (confirm !== 'Continue') { return; } - - const agentChatFile = path.join(path.dirname(params.filePath), '_agent_chat.bal'); - const fileExisted = fs.existsSync(agentChatFile); - - const result: any = await StateMachine.langClient().sendRequest( - "agentManager/addAgentChatService", - { filePath: params.filePath, agentVariableName: params.agentVarName } - ); - - if (result.errorMsg) { - window.showErrorMessage(`Failed to add agent chat service: ${result.errorMsg}`); - return; - } - - const generatedFilePath = result.filePath as string; - const servicePosition = { - startLine: result.startLine as number, - startColumn: result.startColumn as number, - endLine: result.endLine as number, - endColumn: result.endColumn as number, - }; - - ensureGitignoreEntry(path.dirname(generatedFilePath)); - - // Notify LS about the file change so it reloads before navigation - const fileUri = vscode.Uri.file(generatedFilePath); - await StateMachine.langClient().sendNotification( - 'workspace/didChangeWatchedFiles', - { changes: [{ uri: fileUri.toString(), type: fileExisted ? 2 : 1 }] } - ); - - // Navigate to the chat resource function flow diagram - openView(EVENT_TYPE.OPEN_VIEW, { - documentUri: generatedFilePath, - position: servicePosition, - }); - - // Auto-trigger the chat panel - vscode.commands.executeCommand('ballerina.tryIt', - false, undefined, - { basePath: `/agent-chat/${params.agentVarName}`, listener: 'agentChatListener' }, - generatedFilePath, true, - ); - } catch (error) { - window.showErrorMessage(`Failed to set up agent chat: ${error}`); - } - } - - async cleanupAgentChatServices(): Promise { - try { - const projectRoot = StateMachine.context().projectPath; - if (!projectRoot) { return false; } - const chatFile = path.join(projectRoot, '_agent_chat.bal'); - if (fs.existsSync(chatFile)) { - const confirm = await window.showWarningMessage( - 'Remove all test chat services from your project?', - { modal: true }, - 'Remove' - ); - if (confirm !== 'Remove') { return false; } - - fs.unlinkSync(chatFile); - console.log(`[agent-chat] Deleted ${chatFile}`); - - // Notify the LS that the file was deleted so it re-indexes - const fileUri = vscode.Uri.file(chatFile); - await StateMachine.langClient().sendNotification( - 'workspace/didChangeWatchedFiles', - { changes: [{ uri: fileUri.toString(), type: 3 /* Deleted */ }] } - ); - // Wait for LS to process the deletion - await new Promise(resolve => setTimeout(resolve, 500)); - - return true; - } - return false; - } catch (error) { - console.error('[agent-chat] Failed to delete _agent_chat.bal:', error); - return false; - } - } - async getModuleNodes(): Promise { console.log(">>> requesting bi module nodes from ls"); return new Promise((resolve) => { @@ -1544,6 +1398,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { async deleteByComponentInfo(params: BIDeleteByComponentInfoRequest): Promise { console.log(">>> requesting bi delete node from ls by componentInfo", params); + const projectDiags: Diagnostics[] = await checkProjectDiagnostics(StateMachine.langClient(), StateMachine.context().projectPath); const position: NodePosition = { startLine: params.component?.startLine, @@ -1587,7 +1442,25 @@ export class BiDiagramRpcManager implements BIDiagramAPI { } - return performDelete(); + // If there are diagnostics, remove unused imports first, then delete component + if (projectDiags.length > 0) { + return new Promise((resolve, reject) => { + removeUnusedImports(projectDiags, StateMachine.langClient()) + .then(() => { + // After removing unused imports, proceed with component deletion + return performDelete(); + }) + .then((result) => { + resolve(result); + }) + .catch((error) => { + reject("Error during delete operation: " + error); + }); + }); + } else { + // No diagnostics, directly delete component + return performDelete(); + } } async getFormDiagnostics(params: FormDiagnosticsRequest): Promise { @@ -2531,43 +2404,6 @@ export class BiDiagramRpcManager implements BIDiagramAPI { }); }); } - - async updateProjectTitle(params: UpdateProjectTitleRequest): Promise { - setTomlSectionField(path.join(params.projectPath, 'Ballerina.toml'), 'workspace', 'title', params.title); - const currentProjectInfo = StateMachine.context().projectInfo; - if (currentProjectInfo.projectPath === params.projectPath) { - StateMachine.updateProjectInfo({ ...currentProjectInfo, title: params.title }); - } else { - StateMachine.refreshProjectInfo(); - } - } - - async updatePackageTitle(params: UpdatePackageTitleRequest): Promise { - setTomlSectionField(path.join(params.packagePath, 'Ballerina.toml'), 'package', 'title', params.title); - // Update projectInfo so any subsequent buildProjectsStructure call uses the new title. - const currentProjectInfo = StateMachine.context().projectInfo; - let updatedProjectInfo: ProjectInfo; - if ( - currentProjectInfo.projectKind === PROJECT_KIND.WORKSPACE_PROJECT && - currentProjectInfo.children?.length > 0 - ) { - updatedProjectInfo = { - ...currentProjectInfo, - children: currentProjectInfo.children.map((child) => - child.projectPath === params.packagePath - ? { ...child, title: params.title } - : child - ), - }; - } else { - updatedProjectInfo = { ...currentProjectInfo, title: params.title }; - } - StateMachine.updateProjectInfo(updatedProjectInfo, { silent: true }); - } - - async getSuggestedProjectDefaults(params: { isInProject: boolean }): Promise { - return getSuggestedProjectDefaults(params.isInProject); - } } export async function getBallerinaFiles(dir: string): Promise { diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-handler.ts index 12588d24b6..1cd026337f 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-handler.ts @@ -37,7 +37,6 @@ import { getBallerinaDiagnostics, getCurrentProjectTomlValues, getDefaultOrgName, - getPreferredTryItOption, getTypeCompletions, getWorkspaceFiles, getWorkspaceRoot, @@ -51,7 +50,6 @@ import { SampleDownloadRequest, selectFileOrDirPath, selectFileOrFolderPath, - setPreferredTryItOption, showErrorMessage, ShowInfoModalRequest, showQuickPick, @@ -86,6 +84,4 @@ export function registerCommonRpcHandlers(messenger: Messenger) { messenger.onRequest(getDefaultOrgName, () => rpcManger.getDefaultOrgName()); messenger.onRequest(publishToCentral, () => rpcManger.publishToCentral()); messenger.onRequest(hasCentralPATConfigured, () => rpcManger.hasCentralPATConfigured()); - messenger.onRequest(getPreferredTryItOption, () => rpcManger.getPreferredTryItOption()); - messenger.onRequest(setPreferredTryItOption, (option: string) => rpcManger.setPreferredTryItOption(option)); } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-manager.ts index f259fa0730..ca1e69ce01 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-manager.ts @@ -57,7 +57,6 @@ import * as unzipper from 'unzipper'; import { commands, env, MarkdownString, ProgressLocation, QuickPickItem, Uri, window, workspace } from "vscode"; import { URI } from "vscode-uri"; import { parse } from "@iarna/toml"; -import { load as loadYaml } from "js-yaml"; import { extension } from "../../BalExtensionContext"; import { StateMachine } from "../../stateMachine"; import { @@ -204,14 +203,14 @@ export class CommonRpcManager implements CommonRPCAPI { async selectFileOrDirPath(params: FileOrDirRequest): Promise { return new Promise(async (resolve) => { if (params.isFile) { - const selectedFile = await askFilePath(params.filters); + const selectedFile = await askFilePath(); if (!selectedFile || selectedFile.length === 0) { window.showErrorMessage('A file must be selected'); resolve({ path: "" }); } else { const filePath = selectedFile[0].fsPath; const projectPath = StateMachine.context().projectPath; - if (!params.allowOutsideProject && projectPath && !filePath.startsWith(projectPath)) { + if (projectPath && !filePath.startsWith(projectPath)) { const resp = await window.showErrorMessage('The selected file is not within your project. Do you want to move it inside the project?', { modal: true }, 'Yes'); if (resp === 'Yes') { // Move the file inside the project @@ -479,20 +478,6 @@ export class CommonRpcManager implements CommonRPCAPI { } async getDefaultOrgName(): Promise { - try { - const projectPath = StateMachine.context()?.workspacePath; - - if (projectPath) { - const contextYamlPath = path.join(projectPath, ".choreo", "context.yaml"); - const content = await fs.promises.readFile(contextYamlPath, "utf-8"); - const parsed = loadYaml(content); - if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].org === "string" && parsed[0].org) { - return { orgName: parsed[0].org }; - } - } - } catch { - // fall through to default - } return { orgName: getUsername() }; } @@ -750,14 +735,6 @@ export class CommonRpcManager implements CommonRPCAPI { } } - async getPreferredTryItOption(): Promise { - return extension.context.globalState.get("ballerina.bi.preferredTryItOption"); - } - - async setPreferredTryItOption(option: string): Promise { - await extension.context.globalState.update("ballerina.bi.preferredTryItOption", option); - } - async hasCentralPATConfigured(): Promise { // check if the central PAT is configured in the environment variable const token = process.env.BALLERINA_CENTRAL_ACCESS_TOKEN; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts index ce92cdc4a8..7f0632f0b2 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts @@ -91,13 +91,13 @@ export async function askProjectPath() { }); } -export async function askFilePath(filters?: Record) { +export async function askFilePath() { return await window.showOpenDialog({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: Uri.file(StateMachine.context().projectPath ?? os.homedir()), - filters: filters ?? { + filters: { 'Files': ['yaml', 'json', 'yml', 'graphql', 'wsdl'] }, title: "Select a file", diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/icp-service/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/icp-service/rpc-manager.ts index dfe00a8670..b474db00c6 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/icp-service/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/icp-service/rpc-manager.ts @@ -30,7 +30,7 @@ import { updateSourceCode } from "../../utils/source-utils"; import { getOrgAndPackageName } from "../../utils"; import { parse, stringify } from "@iarna/toml"; import { getStoredICPSecret } from "../../features/icp/setup"; -import { ensureICPServerRunning, isICPServerRunning, getICPUrl } from "../../features/icp"; +import { ensureICPServerRunning, isICPServerRunning } from "../../features/icp"; const ICP_IMPORTS = [ 'import wso2/icp.runtime.bridge as _;', @@ -320,7 +320,7 @@ export class ICPServiceRpcManager implements ICPServiceAPI { async viewInICP(params: ICPEnabledRequest): Promise { try { - const icpUrl = getICPUrl(); + const icpUrl = vscode.workspace.getConfiguration('ballerina').get('icpUrl') || 'https://localhost:9445'; if (isICPServerRunning()) { await vscode.env.openExternal(vscode.Uri.parse(icpUrl)); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-handler.ts index f23e061267..b36acc83ff 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-handler.ts @@ -37,18 +37,6 @@ import { import { Messenger } from "vscode-messenger"; import { MigrateIntegrationRpcManager } from "./rpc-manager"; -// Defined locally to avoid depending on a rebuilt @wso2/ballerina-core -const getActiveMigrationSession = { method: "migrate-integration/getActiveMigrationSession" } as const; -const markEnhancementCompleteMethod = { method: "migrate-integration/markEnhancementComplete" } as const; -const startMigrationEnhancementMethod = { method: "migrate-integration/startMigrationEnhancement" } as const; -const migrationPanelReadyMethod = { method: "migrate-integration/migrationPanelReady" } as const; -const abortMigrationAgentMethod = { method: "migrate-integration/abortMigrationAgent" } as const; -const setMigrationModelMethod = { method: "migrate-integration/setMigrationModel" } as const; -const wizardEnhancementReadyMethod = { method: "migrate-integration/wizardEnhancementReady" } as const; -const openMigratedProjectMethod = { method: "migrate-integration/openMigratedProject" } as const; -const seedMigrationHistoryMethod = { method: "migrate-integration/seedMigrationHistory" } as const; -const getMigrationHistoryMessagesMethod = { method: "migrate-integration/getMigrationHistoryMessages" } as const; - export function registerMigrateIntegrationRpcHandlers(messenger: Messenger) { const rpcManger = MigrateIntegrationRpcManager.getInstance(); messenger.onRequest(getMigrationTools, () => rpcManger.getMigrationTools()); @@ -59,14 +47,4 @@ export function registerMigrateIntegrationRpcHandlers(messenger: Messenger) { messenger.onNotification(storeSubProjectReports, (args: StoreSubProjectReportsRequest) => rpcManger.storeSubProjectReports(args)); messenger.onNotification(saveMigrationReport, (args: SaveMigrationReportRequest) => rpcManger.saveMigrationReport(args)); messenger.onNotification(migrateProject, (args: MigrateRequest) => rpcManger.migrateProject(args)); - messenger.onRequest(getActiveMigrationSession, () => rpcManger.getActiveMigrationSession()); - messenger.onRequest(markEnhancementCompleteMethod, () => rpcManger.markEnhancementComplete()); - messenger.onRequest(startMigrationEnhancementMethod, () => rpcManger.startMigrationEnhancement()); - messenger.onRequest(migrationPanelReadyMethod, () => rpcManger.migrationPanelReady()); - messenger.onRequest(wizardEnhancementReadyMethod, () => rpcManger.wizardEnhancementReady()); - messenger.onRequest(openMigratedProjectMethod, () => rpcManger.openMigratedProjectInVSCode()); - messenger.onRequest(abortMigrationAgentMethod, () => rpcManger.abortMigrationAgent()); - messenger.onRequest(setMigrationModelMethod, (args: { modelId: string }) => rpcManger.setMigrationModel(args.modelId)); - messenger.onRequest(seedMigrationHistoryMethod, () => rpcManger.seedMigrationHistory()); - messenger.onRequest(getMigrationHistoryMessagesMethod, () => rpcManger.getMigrationHistoryMessages()); } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-manager.ts index 79387488e7..9aaeedbe36 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/migrate-integration/rpc-manager.ts @@ -27,7 +27,7 @@ import { OpenMigrationReportRequest, OpenSubProjectReportRequest, SaveMigrationReportRequest, - StoreSubProjectReportsRequest, + StoreSubProjectReportsRequest } from "@wso2/ballerina-core"; import os from "os"; import path from "path"; @@ -36,25 +36,6 @@ import { StateMachine } from "../../stateMachine"; import { createBIProjectFromMigration, getUsername, sanitizeName } from "../../utils/bi"; import { pullMigrationTool } from "../../utils/migrate-integration"; import { MigrationReportWebview } from "../../views/migration-report/webview"; -import { - getActiveMigrationSessionState, - markEnhancementComplete, - startMigrationEnhancement, - runMigrationAgent, - abortMigrationAgent, - setMigrationModelId, - setWizardProjectRoot, - runWizardMigrationEnhancement, - openMigratedProject, - seedMigrationHistoryIntoChatState, - getMigrationHistoryMessages, -} from "../../features/ai/migration/orchestrator"; - -interface ActiveMigrationSession { - isActive: boolean; - aiFeatureUsed: boolean; - fullyEnhanced: boolean; -} export class MigrateIntegrationRpcManager implements MigrateIntegrationAPI { private static instance: MigrateIntegrationRpcManager; @@ -173,7 +154,7 @@ export class MigrateIntegrationRpcManager implements MigrateIntegrationAPI { const aggregateReportPath = path.join(baseDir.fsPath, params.defaultFileName); await vscode.workspace.fs.writeFile( vscode.Uri.file(aggregateReportPath), - new TextEncoder().encode(params.reportContent) + Buffer.from(params.reportContent, 'utf8') ); console.log(`Aggregate migration report saved to ${aggregateReportPath}`); @@ -188,7 +169,7 @@ export class MigrateIntegrationRpcManager implements MigrateIntegrationAPI { // Write project report await vscode.workspace.fs.writeFile( vscode.Uri.file(projectReportPath), - new TextEncoder().encode(reportContent) + Buffer.from(reportContent, 'utf8') ); console.log(`Project migration report saved to ${projectReportPath}`); } @@ -212,82 +193,13 @@ export class MigrateIntegrationRpcManager implements MigrateIntegrationAPI { if (saveUri) { // Write the report content to the selected file - await vscode.workspace.fs.writeFile(saveUri, new TextEncoder().encode(params.reportContent)); + await vscode.workspace.fs.writeFile(saveUri, Buffer.from(params.reportContent, 'utf8')); vscode.window.showInformationMessage(`Migration report saved to ${saveUri.fsPath}`); } } } async migrateProject(params: MigrateRequest): Promise { - const projectRoot = await createBIProjectFromMigration(params); - - if (params.aiFeatureUsed === true && projectRoot) { - // Store the project root for wizard-level AI enhancement. - // The webview will call wizardEnhancementReady once it shows the - // enhancement step, which triggers the agent. - setWizardProjectRoot(projectRoot, params.sourcePath); - } - } - - async getActiveMigrationSession(): Promise { - return getActiveMigrationSessionState() as ActiveMigrationSession; - } - - async markEnhancementComplete(): Promise { - markEnhancementComplete(); - } - - async startMigrationEnhancement(): Promise { - await startMigrationEnhancement(); - } - - /** - * Called by the migration panel webview once it's mounted and ready to - * receive streaming events. If the session is active and not yet enhanced, - * kicks off the migration agent. - */ - async migrationPanelReady(): Promise { - const session = getActiveMigrationSessionState(); - if (session.isActive && !session.fullyEnhanced) { - // Fire and forget – the agent streams events back to the panel - runMigrationAgent().catch((err) => - console.error("[MigrateIntegrationRpc] Migration agent failed:", err) - ); - } - } - - /** - * Called by the Visualizer webview (ImportIntegration wizard) once the - * AI enhancement step is visible and ready to receive streaming events. - * Kicks off the wizard-level migration agent. - */ - async wizardEnhancementReady(): Promise { - runWizardMigrationEnhancement().catch((err) => - console.error("[MigrateIntegrationRpc] Wizard migration agent failed:", err) - ); - } - - /** - * Opens the migrated project in VS Code after wizard-level AI enhancement - * completes or the user skips it. - */ - async openMigratedProjectInVSCode(): Promise { - openMigratedProject(); - } - - async abortMigrationAgent(): Promise { - abortMigrationAgent(); - } - - async setMigrationModel(modelId: string): Promise { - setMigrationModelId(modelId); - } - - async seedMigrationHistory(): Promise { - return seedMigrationHistoryIntoChatState(); - } - - async getMigrationHistoryMessages(): Promise> { - return getMigrationHistoryMessages(); + createBIProjectFromMigration(params); } } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/platform-utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/platform-utils.ts index 779051a3ea..4473af96db 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/platform-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/platform-utils.ts @@ -93,7 +93,7 @@ export const addProxyConfigurable = async (configBalFileUri: Uri) => { (member) => STKindChecker.isModuleVarDecl(member) && (member.typedBindingPattern?.bindingPattern as CaptureBindingPattern)?.variableName?.value === - ProxyConfigEnvVars.proxyConfig.varName, + "wso2CloudProxyConfig", ) ) { const proxyConfigTemplate = Templates.proxyConfigurable(); @@ -339,9 +339,9 @@ export const Templates = { }, proxyConfigurable: () => { const template = Handlebars.compile(` -configurable string? ${ProxyConfigEnvVars.proxyHost.varName} = (); -configurable int? ${ProxyConfigEnvVars.proxyPort.varName} = (); -http:ProxyConfig? ${ProxyConfigEnvVars.proxyConfig.varName} = ${ProxyConfigEnvVars.proxyHost.varName} is string && ${ProxyConfigEnvVars.proxyPort.varName} is int ? { host: ${ProxyConfigEnvVars.proxyHost.varName}, port: ${ProxyConfigEnvVars.proxyPort.varName} } : (); +configurable string? wso2CloudProxyHost = (); +configurable int? wso2CloudProxyPort = (); +http:ProxyConfig? wso2CloudProxyConfig = wso2CloudProxyHost is string && wso2CloudProxyPort is int ? { host: wso2CloudProxyHost, port: wso2CloudProxyPort } : (); \n`); return template({}); }, @@ -362,7 +362,7 @@ http:ProxyConfig? ${ProxyConfigEnvVars.proxyConfig.varName} = ${ProxyConfigEnvVa return `final ${params.MODULE_NAME}:Client ${ params.CONNECTION_NAME } = check new (apiKeyConfig = { choreoAPIKey: ${params.API_KEY_VAR_NAME} }, config = { ${ - params.requireProxy ? `proxy: ${ProxyConfigEnvVars.proxyConfig.varName}, ` : "" + params.requireProxy ? "proxy: wso2CloudProxyConfig, " : "" }timeout: 60 }, serviceUrl = ${params.SERVICE_URL_VAR_NAME});\n`; }, newConnectionWithOAuth: (params: { @@ -380,7 +380,7 @@ http:ProxyConfig? ${ProxyConfigEnvVars.proxyConfig.varName} = ${ProxyConfigEnvVa params.CONNECTION_NAME } = check new (config = { auth: { tokenUrl: ${params.TOKEN_URL}, clientId: ${params.CLIENT_ID}, clientSecret: ${ params.CLIENT_SECRET - } }, ${params.requireProxy ? `proxy: ${ProxyConfigEnvVars.proxyConfig.varName}, ` : ""}timeout: 60 }, serviceUrl = ${ + } }, ${params.requireProxy ? "proxy: wso2CloudProxyConfig, " : ""}timeout: 60 }, serviceUrl = ${ params.SERVICE_URL_VAR_NAME });\n`; }, @@ -448,17 +448,3 @@ export const findUniqueConnectionName = (name: string, existingMarketplaceItems: return uniqueName; }; - -export const ProxyConfigEnvVars = { - proxyHost:{ - varName: "wso2CloudProxyHost", - envName: "BAL_CONFIG_VAR_WSO2CLOUDPROXYHOST", - }, - proxyPort:{ - varName: "wso2CloudProxyPort", - envName: "BAL_CONFIG_VAR_WSO2CLOUDPROXYPORT", - }, - proxyConfig:{ - varName: "wso2CloudProxyConfig" - } -}; \ No newline at end of file diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/rpc-manager.ts index 2c811eb0e6..a990263859 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/platform-ext/rpc-manager.ts @@ -94,7 +94,6 @@ import { getConfigFileUri, hasContextYaml, processOpenApiWithApiKeyAuth, - ProxyConfigEnvVars, Templates, } from "./platform-utils"; import { debounce } from "lodash"; @@ -577,11 +576,11 @@ export class PlatformExtRpcManager implements PlatformExtAPI { if (devantProxyResp?.proxyServerPort) { debugConfig.env = { ...(debugConfig.env || {}), ...devantProxyResp.envVars }; if (devantProxyResp.requiresProxy) { - debugConfig.env[ProxyConfigEnvVars.proxyHost.envName] = "127.0.0.1"; - debugConfig.env[ProxyConfigEnvVars.proxyPort.envName] = `${devantProxyResp.proxyServerPort}`; + debugConfig.env.BAL_CONFIG_VAR_DEVANTPROXYHOST = "127.0.0.1"; + debugConfig.env.BAL_CONFIG_VAR_DEVANTPROXYPORT = `${devantProxyResp.proxyServerPort}`; } else { - delete debugConfig.env[ProxyConfigEnvVars.proxyHost.envName]; - delete debugConfig.env[ProxyConfigEnvVars.proxyPort.envName]; + delete debugConfig.env.BAL_CONFIG_VAR_DEVANTPROXYHOST; + delete debugConfig.env.BAL_CONFIG_VAR_DEVANTPROXYPORT; } const disposable = vscode.debug.onDidTerminateDebugSession((session) => { @@ -610,7 +609,7 @@ export class PlatformExtRpcManager implements PlatformExtAPI { (member) => STKindChecker.isModuleVarDecl(member) && (member.typedBindingPattern?.bindingPattern as CaptureBindingPattern)?.variableName?.value === - ProxyConfigEnvVars.proxyConfig.varName, + "wso2CloudProxyConfig", ) ) { requiresProxy = true; @@ -659,7 +658,7 @@ export class PlatformExtRpcManager implements PlatformExtAPI { const resp = await window.withProgress( { location: vscode.ProgressLocation.Notification, - title: "Connecting to WSO2 Cloud before running/debugging the application...", + title: "Connecting to Devant before running/debugging the application...", }, () => platformExt?.startProxyServer({ diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index 52b6da542d..950cee5dcc 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -37,7 +37,7 @@ import { } from './utils/state-machine-utils'; import * as path from 'path'; import { extension } from './BalExtensionContext'; -import { AIStateMachine, openAIPanelWithPrompt } from './views/ai-panel/aiMachine'; +import { AIStateMachine } from './views/ai-panel/aiMachine'; import { StateMachinePopup } from './stateMachinePopup'; import { checkIsBallerinaPackage, checkIsBI, fetchScope, getOrgPackageName, UndoRedoManager, getProjectTomlValues, getOrgAndPackageName, checkIsBallerinaWorkspace, isInWI } from './utils'; import { activateDevantFeatures } from './features/devant/activator'; @@ -66,7 +66,6 @@ interface MachineContext extends VisualizerLocation { export let history: History; export let undoRedoManager: IUndoRedoManager; let pendingProjectRootUpdateResolvers: Array<() => void> = []; -let scaffoldPromptTriggered = false; const stateMachine = createMachine( { @@ -150,9 +149,7 @@ const stateMachine = createMachine( async (context, event) => { // Rebuild project structure with updated project info await buildProjectsStructure(event.projectInfo, StateMachine.langClient(), true); - if (!event.silent) { - openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.WorkspaceOverview }); - } + openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.WorkspaceOverview }); } ] }, @@ -394,22 +391,6 @@ const stateMachine = createMachine( } }, viewReady: { - entry: () => { - if (!scaffoldPromptTriggered) { - const scaffoldPrompt = process.env.INITIAL_SCAFFOLD_PROMPT; - const scaffoldSteps = process.env.INITIAL_SCAFFOLD_STEPS; - if (scaffoldPrompt && scaffoldSteps) { - scaffoldPromptTriggered = true; - openAIPanelWithPrompt({ - type: 'text', - text: scaffoldPrompt, - planMode: true, - autoSubmit: true, - hiddenContext: scaffoldSteps - }); - } - } - }, on: { OPEN_VIEW: { target: "viewInit", @@ -865,9 +846,6 @@ export const StateMachine = { refreshProjectInfo: () => { stateService.send({ type: 'REFRESH_PROJECT_INFO' }); }, - updateProjectInfo: (projectInfo: ProjectInfo, options?: { silent?: boolean }) => { - stateService.send({ type: 'UPDATE_PROJECT_INFO', projectInfo, silent: options?.silent }); - }, resetToExtensionReady: () => { stateService.send({ type: 'RESET_TO_EXTENSION_READY' }); }, diff --git a/workspaces/ballerina/ballerina-extension/src/utils/ai-service-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/ai-service-utils.ts index 501b80b6fe..f379d700dc 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/ai-service-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/ai-service-utils.ts @@ -106,11 +106,7 @@ export const handleAIAgentServiceDeletion = async ( } // Remove agent definitions first - // Skip for test chat services (_agent_chat.bal) — they wrap existing agents that shouldn't be deleted - const isTestChatService = component.path?.endsWith('_agent_chat.bal'); - if (!isTestChatService) { - await removeAgentNodesForServiceArtifact(component, rpcClient); - } + await removeAgentNodesForServiceArtifact(component, rpcClient); // Delete the service using the correct service-level position const componentInfo: ComponentInfo = { diff --git a/workspaces/ballerina/ballerina-extension/src/utils/bi.ts b/workspaces/ballerina/ballerina-extension/src/utils/bi.ts index dfddf3282d..e083ce9206 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/bi.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/bi.ts @@ -32,8 +32,7 @@ import { STModification, SyntaxTreeResponse, WorkspaceTomlValues, - ValidateProjectFormErrorField, - SuggestedProjectDefaultsResponse + ValidateProjectFormErrorField } from "@wso2/ballerina-core"; import { StateMachine, history, openView } from "../stateMachine"; import { applyModifications, modifyFileContent, writeBallerinaFileDidOpen } from "./modification"; @@ -43,9 +42,7 @@ import { debug } from "./logger"; import { parse } from "@iarna/toml"; import { getProjectTomlValues, isLibraryProject, VALIDATOR_PACKAGE_NAME } from "./config"; import { extension } from "../BalExtensionContext"; -import { scheduleMigrationEnhancement, writeEnhanceToml } from "../features/ai/migration/orchestrator"; import { runBackgroundTerminalCommand } from "./runCommand"; -import { stringify as stringifyYaml } from "yaml"; export const README_FILE = "README.md"; export const FUNCTIONS_FILE = "functions.bal"; @@ -61,7 +58,6 @@ interface ProcessedProjectInfo { finalVersion: string; packageName: string; integrationName: string; - orgHandle: string; } const settingsJsonContent = ` @@ -308,47 +304,19 @@ function setupProjectInfo(projectRequest: ProjectRequest): ProcessedProjectInfo finalOrgName, finalVersion, packageName: projectRequest.packageName, - integrationName: projectRequest.projectName, - orgHandle: projectRequest.orgHandle + integrationName: projectRequest.projectName }; } -/** - * Writes a local context file for the given project. - * Creates (if missing) `{projectRoot}/.choreo/context.yaml` and stores the org/project handles with `local: true`. - * @param projectRoot - Absolute path to the project root directory - * @param orgHandle - Choreo organization handle - * @param projectHandle - Choreo project handle - */ -export async function writeLocalContextYaml( - projectRoot: string, - orgHandle: string, - projectHandle: string -): Promise { - try { - const choreoDir = path.join(projectRoot, '.choreo'); - const localProjectFile = path.join(choreoDir, 'context.yaml'); - const content = stringifyYaml([{ org: orgHandle, project: projectHandle, local: true }]); - await fs.promises.mkdir(choreoDir, { recursive: true }); - await fs.promises.writeFile(localProjectFile, content, { encoding: 'utf8' }); - } catch (error) { - console.warn("Failed to write context.yaml (non-critical):", error); - } -} - export async function createEmptyBIWorkspace(projectRequest: ProjectRequest): Promise { const ballerinaTomlContent = ` [workspace] -title = "${projectRequest.workspaceName}" packages = [] `; // Use the workspace-specific directory resolver - const workspaceRoot = resolveWorkspacePath( - projectRequest.projectPath, - projectRequest?.projectHandle ?? projectRequest.workspaceName - ); + const workspaceRoot = resolveWorkspacePath(projectRequest.projectPath, projectRequest.workspaceName); // Create Ballerina.toml file const ballerinaTomlPath = path.join(workspaceRoot, 'Ballerina.toml'); @@ -364,16 +332,12 @@ packages = [] export async function createBIWorkspaceWithProject(projectRequest: ProjectRequest): Promise { const ballerinaTomlContent = ` [workspace] -title = "${projectRequest.workspaceName}" -packages = ["${sanitizeName(projectRequest.packageName)}"] +packages = ["${projectRequest.packageName}"] `; // Use the workspace-specific directory resolver - const workspaceRoot = resolveWorkspacePath( - projectRequest.projectPath, - projectRequest?.projectHandle ?? projectRequest.workspaceName - ); + const workspaceRoot = resolveWorkspacePath(projectRequest.projectPath, projectRequest.workspaceName); // Create Ballerina.toml file const ballerinaTomlPath = path.join(workspaceRoot, 'Ballerina.toml'); @@ -391,14 +355,7 @@ packages = ["${sanitizeName(projectRequest.packageName)}"] export async function createBIProjectPure(projectRequest: ProjectRequest): Promise { const projectInfo = setupProjectInfo(projectRequest); - const { - projectRoot, - finalOrgName, - finalVersion, - packageName, - integrationName, - orgHandle - } = projectInfo; + const { projectRoot, finalOrgName, finalVersion, packageName: finalPackageName, integrationName } = projectInfo; const EMPTY = "\n"; @@ -410,8 +367,8 @@ export async function createBIProjectPure(projectRequest: ProjectRequest): Promi const ballerinaTomlContent = ` [package] -org = "${orgHandle ?? finalOrgName}" -name = "${packageName}" +org = "${finalOrgName}" +name = "${finalPackageName}" version = "${finalVersion}" ${distributionLine}title = "${integrationName}" @@ -488,47 +445,36 @@ export async function convertProjectToWorkspace(params: AddProjectToWorkspaceReq throw new Error('No package name found in Ballerina.toml'); } - const projectDirectoryName = params.projectHandle ?? params.workspaceName; - const newDirectory = path.join(path.dirname(currentProjectPath), projectDirectoryName); + const newDirectory = path.join(path.dirname(currentProjectPath), params.workspaceName); - try { - fs.mkdirSync(newDirectory); - } catch (err: unknown) { - const code = (err as NodeJS.ErrnoException).code; - if (code === 'EEXIST') { - throw new Error(`A directory named "${projectDirectoryName}" already exists at the selected location`); - } - throw err; + if (!fs.existsSync(newDirectory)) { + fs.mkdirSync(newDirectory, { recursive: true }); } const updatedProjectPath = path.join(newDirectory, path.basename(currentProjectPath)); fs.renameSync(currentProjectPath, updatedProjectPath); - const existingProjectDirName = path.basename(currentProjectPath); - createWorkspaceToml(newDirectory, params.workspaceName, existingProjectDirName); - addToWorkspaceToml(newDirectory, sanitizeName(params.packageName)); + createWorkspaceToml(newDirectory, currentPackageName); + addToWorkspaceToml(newDirectory, params.packageName); await createProjectInWorkspace(params, newDirectory); // create settings.json file createVSCodeSettings(newDirectory); - // write local context file - await writeLocalContextYaml(newDirectory, params.orgHandle, params.projectHandle); openInVSCode(newDirectory); } export async function addProjectToExistingWorkspace(params: AddProjectToWorkspaceRequest): Promise { const workspacePath = StateMachine.context().workspacePath; - addToWorkspaceToml(workspacePath, sanitizeName(params.packageName)); + addToWorkspaceToml(workspacePath, params.packageName); await createProjectInWorkspace(params, workspacePath); } -function createWorkspaceToml(workspacePath: string, projectTitle: string, packageName: string) { +function createWorkspaceToml(workspacePath: string, packageName: string) { const ballerinaTomlContent = ` [workspace] -title = "${projectTitle}" packages = ["${packageName}"] `; const ballerinaTomlPath = path.join(workspacePath, 'Ballerina.toml'); @@ -572,12 +518,11 @@ export function deleteProjectFromWorkspace(workspacePath: string, packagePath: s const tomlData = parse(ballerinaTomlContent) as Partial; const existingPackages: string[] = tomlData?.workspace?.packages ?? []; - const matchedEntry = existingPackages.find(p => path.normalize(p) === relativeProjectPath); - if (!matchedEntry) { + if (!existingPackages.includes(relativeProjectPath)) { return; // Package not found } - const updatedContent = removePackageFromToml(ballerinaTomlContent, matchedEntry); + const updatedContent = removePackageFromToml(ballerinaTomlContent, relativeProjectPath); fs.writeFileSync(ballerinaTomlPath, updatedContent); // send didChange event to the language server @@ -643,10 +588,8 @@ async function createProjectInWorkspace(params: AddProjectToWorkspaceRequest, wo projectPath: workspacePath, createDirectory: true, orgName: params.orgName, - orgHandle: params.orgHandle, version: params.version, - isLibrary: params.isLibrary, - projectHandle: params.projectHandle + isLibrary: params.isLibrary }; return await createBIProjectPure(projectRequest); @@ -668,7 +611,7 @@ export async function createBIProjectFromMigration(params: MigrateRequest) { if (fileName === "Ballerina.toml") { content = content.replace(/name = ".*?"/, `name = "${sanitizedPackageName}"`); - content = content.replace(/org = ".*?"/, `org = "${projectInfo.orgHandle ?? projectInfo.finalOrgName}"`); + content = content.replace(/org = ".*?"/, `org = "${projectInfo.finalOrgName}"`); // Remove any existing distribution line content = content.replace(/^\s*distribution\s*=\s*".*?"\n?/m, ''); @@ -695,28 +638,7 @@ export async function createBIProjectFromMigration(params: MigrateRequest) { fs.writeFileSync(gitignorePath, gitignoreContent.trim()); debug(`BI project created successfully at ${projectRoot}`); - - const resolvedRoot = path.resolve(projectRoot); - const aiEnabled = params.aiFeatureUsed ?? false; - - // Write the AI enhancement state file – acts as the source of truth for the - // migration UI banner. This is done for ALL values of aiFeatureUsed so - // the card can offer a "Start Enhancement" button even when the user skipped. - writeEnhanceToml(resolvedRoot, aiEnabled, false, params.sourcePath); - - if (aiEnabled) { - // When AI enhancement is enabled, return the project root to the caller - // so the wizard can run the enhancement pipeline before opening the folder. - // The caller (RPC manager) will notify the webview with the project root - // and kick off the agent; vscode.openFolder is deferred until the - // enhancement completes or the user skips. - return resolvedRoot; - } - - // No AI enhancement – open the project immediately. - scheduleMigrationEnhancement(aiEnabled, resolvedRoot, params.sourcePath); - commands.executeCommand('vscode.openFolder', Uri.file(resolvedRoot)); - return resolvedRoot; + commands.executeCommand('vscode.openFolder', Uri.file(path.resolve(projectRoot))); } async function createProjectFiles(project: ProjectMigrationResult, projectRoot: string) { @@ -865,55 +787,3 @@ export async function handleFunctionCreation(targetFile: string, params: Compone export function sanitizeName(name: string): string { return name.replace(/[^a-z0-9]_./gi, '_').toLowerCase(); // Replace invalid characters with underscores } - -export async function getSuggestedProjectDefaults(isInProject: boolean): Promise { - const BASE_PROJECT_NAME = "Default"; - const BASE_INTEGRATION_NAME = "Untitled"; - - if (!isInProject) { - const currentProjectPath = StateMachine.context().projectPath; - const parentDir = path.dirname(currentProjectPath); - const tomlValues = await getProjectTomlValues(currentProjectPath); - const currentPackageName = tomlValues?.package?.name ?? ""; - - const baseHandle = BASE_PROJECT_NAME.toLowerCase(); - let projectName = BASE_PROJECT_NAME; - let projectHandle = baseHandle; - if (fs.existsSync(path.join(parentDir, baseHandle))) { - for (let i = 2; ; i++) { - projectHandle = `${baseHandle}-${i}`; - if (!fs.existsSync(path.join(parentDir, projectHandle))) { - projectName = `${BASE_PROJECT_NAME} ${i}`; - break; - } - } - } - - const basePackageName = BASE_INTEGRATION_NAME.toLowerCase(); - let integrationName = BASE_INTEGRATION_NAME; - let packageName = basePackageName; - if (packageName === currentPackageName) { - for (let i = 2; ; i++) { - packageName = `${basePackageName}_${i}`; - if (packageName !== currentPackageName) { - integrationName = `${BASE_INTEGRATION_NAME} ${i}`; - break; - } - } - } - - return { projectName, projectHandle, integrationName, packageName }; - } else { - const workspacePath = StateMachine.context().workspacePath; - const basePackageName = BASE_INTEGRATION_NAME.toLowerCase(); - if (!fs.existsSync(path.join(workspacePath, basePackageName))) { - return { projectName: BASE_PROJECT_NAME, projectHandle: BASE_PROJECT_NAME.toLowerCase(), integrationName: BASE_INTEGRATION_NAME, packageName: basePackageName }; - } - for (let i = 2; ; i++) { - const packageName = `${basePackageName}_${i}`; - if (!fs.existsSync(path.join(workspacePath, packageName))) { - return { projectName: BASE_PROJECT_NAME, projectHandle: BASE_PROJECT_NAME.toLowerCase(), integrationName: `${BASE_INTEGRATION_NAME} ${i}`, packageName }; - } - } - } -} diff --git a/workspaces/ballerina/ballerina-extension/src/utils/project-artifacts.ts b/workspaces/ballerina/ballerina-extension/src/utils/project-artifacts.ts index 84dac20785..80e436e23e 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/project-artifacts.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/project-artifacts.ts @@ -220,6 +220,7 @@ async function getComponents( async function getEntryValue(artifact: BaseArtifact, projectPath: string, icon: string, moduleName?: string) { const targetFile = Utils.joinPath(URI.file(projectPath), artifact.location.fileName).fsPath; + const isPublic = artifact.scope?.toLowerCase() === "global"; const entryValue: ProjectStructureArtifactResponse = { id: artifact.id, name: artifact.name, @@ -229,7 +230,7 @@ async function getEntryValue(artifact: BaseArtifact, projectPath: string, icon: icon: artifact.module ? `bi-${artifact.module}` : icon, context: artifact.name === "automation" ? "main" : artifact.name, resources: [], - visibility: artifact.visibility, + isPublic, position: { endColumn: artifact.location.endLine.offset, endLine: artifact.location.endLine.line, @@ -494,11 +495,9 @@ async function traverseUpdatedComponents(publishedArtifacts: Artifacts, currentP const projectPath = StateMachine.context().projectPath; const project = currentProjectStructure.projects.find(project => project.projectPath === projectPath); try { - if (project) { - for (const key of Object.keys(project.directoryMap)) { - if (project.directoryMap[key]) { - project.directoryMap[key].sort((a, b) => a.name.localeCompare(b.name)); - } + for (const key of Object.keys(project.directoryMap)) { + if (project.directoryMap[key]) { + project.directoryMap[key].sort((a, b) => a.name.localeCompare(b.name)); } } } catch (error) { diff --git a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts index 38e5526545..0873268117 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts @@ -28,13 +28,6 @@ import * as path from 'path'; import { notifyCurrentWebview } from '../RPCLayer'; import { applyBallerinaTomlEdit } from '../rpc-managers/bi-diagram/utils'; -/** True while any migration AI enhancement is actively running. */ -let _migrationEnhancementActive = false; -/** Called by the migration orchestrator to suppress disruptive UI side-effects during enhancement. */ -export function setMigrationEnhancementActive(active: boolean): void { - _migrationEnhancementActive = active; -} - export interface UpdateSourceCodeRequest { textEdits: { [key: string]: TextEdit[]; @@ -120,23 +113,11 @@ export async function updateSourceCode(updateSourceCodeRequest: UpdateSourceCode } } if (edits.length === 0) { - if (!skipUndoRedoStack) { - undoRedoManager?.cancelBatchOperation(); - } StateMachine.setReadyMode(); return []; } } - // If modificationRequests is empty, return empty array - if (Object.keys(modificationRequests).length === 0) { - if (!skipUndoRedoStack) { - undoRedoManager?.cancelBatchOperation(); - } - StateMachine.setReadyMode(); - return []; - } - // Iterate through modificationRequests and apply modifications try { // <-------- Using simply the text edits to update the source code --------> @@ -253,11 +234,7 @@ export async function updateSourceCode(updateSourceCodeRequest: UpdateSourceCode console.log("No artifact update notification received within 10 seconds"); unsubscribe(); StateMachine.setReadyMode(); - // Don't navigate away while migration enhancement is running — it would - // disrupt the agent pipeline and cause repeated "no project found" errors. - if (!_migrationEnhancementActive) { - openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.PackageOverview }); - } + openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.PackageOverview }); reject(new Error("Operation timed out. Please try again.")); }, 10000); diff --git a/workspaces/ballerina/ballerina-extension/src/utils/state-machine-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/state-machine-utils.ts index 9446f4e8bd..b5defbb435 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/state-machine-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/state-machine-utils.ts @@ -292,19 +292,6 @@ function getViewByArtifacts(documentUri: string, position: NodePosition, project } } } - // If no artifact matched but we're already on a BIDiagram for this file, - // stay on it instead of redirecting to Overview. This handles newly created files that aren't in the project structure yet. - const ctx = StateMachine.context(); - if (ctx?.view === MACHINE_VIEW.BIDiagram && documentUri === ctx?.documentUri) { - return { - location: { - view: MACHINE_VIEW.BIDiagram, - documentUri, - position, - projectPath, - } - }; - } // If no view is found, return the overview view return { location: { view: MACHINE_VIEW.PackageOverview, documentUri: documentUri } }; } diff --git a/workspaces/ballerina/ballerina-extension/src/utils/webview-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/webview-utils.ts index 9ebb4a3f02..0061b1ae34 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/webview-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/webview-utils.ts @@ -132,7 +132,7 @@ function getComposerURI(webView: Webview): string { function getComposerCSSFiles(disableComDebug: boolean, devHost: string, webView: Webview): string[] { const filePath = join((extension.ballerinaExtInstance.context as ExtensionContext).extensionPath, 'resources', 'jslibs', 'themes', 'ballerina-default.min.css'); return [ - (isDevMode && !disableComDebug) ? new URL('themes/ballerina-default.min.css', devHost).toString() + (isDevMode && !disableComDebug) ? join(devHost, 'themes', 'ballerina-default.min.css') : webView.asWebviewUri(Uri.file(filePath)).toString() ]; } @@ -140,7 +140,7 @@ function getComposerCSSFiles(disableComDebug: boolean, devHost: string, webView: function getComposerJSFiles(componentName: string, disableComDebug: boolean, devHost: string, webView: Webview): string[] { const filePath = join((extension.ballerinaExtInstance.context as ExtensionContext).extensionPath, 'resources', 'jslibs') + sep + componentName + '.js'; return [ - (isDevMode && !disableComDebug) ? new URL(componentName + '.js', devHost).toString() + (isDevMode && !disableComDebug) ? join(devHost, componentName + '.js') : webView.asWebviewUri(Uri.file(filePath)).toString(), isDevMode ? 'http://localhost:8097' : '' // For React Dev Tools ]; diff --git a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/migration-panel/activate.ts deleted file mode 100644 index 090fc6ad3e..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/activate.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import * as vscode from "vscode"; -import { BallerinaExtension } from "../../core"; -import { MigrationPanelWebview } from "./webview"; - -const OPEN_MIGRATION_PANEL = "ballerina.openMigrationPanel"; - -export function activateMigrationPanel(ballerinaExtInstance: BallerinaExtension): void { - ballerinaExtInstance.context.subscriptions.push( - vscode.commands.registerCommand(OPEN_MIGRATION_PANEL, () => { - MigrationPanelWebview.render(); - }) - ); - console.log("Migration Panel activated"); -} - -/** - * Programmatic helper – open (or reveal) the migration panel from anywhere - * in the extension without going through vscode.commands. - */ -export function openMigrationPanel(): void { - MigrationPanelWebview.render(); -} diff --git a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/index.ts b/workspaces/ballerina/ballerina-extension/src/views/migration-panel/index.ts deleted file mode 100644 index f3aaee9ed3..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./activate"; diff --git a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/webview.ts b/workspaces/ballerina/ballerina-extension/src/views/migration-panel/webview.ts deleted file mode 100644 index 043db3ef1b..0000000000 --- a/workspaces/ballerina/ballerina-extension/src/views/migration-panel/webview.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import { Disposable, Webview, WebviewPanel, window, Uri, ViewColumn } from "vscode"; -import * as path from "path"; -import { WebViewOptions, getComposerWebViewOptions, getLibraryWebViewContent } from "../../utils/webview-utils"; -import { extension } from "../../BalExtensionContext"; -import { RPCLayer } from "../../RPCLayer"; - -/** - * Standalone Migration Enhancement Panel. - * - * Decoupled from the AI Chat (BI Copilot) panel so that users can choose any - * LLM model through the VS Code Language Model API or direct provider keys, - * without requiring WSO2 Copilot authentication. - */ -export class MigrationPanelWebview { - public static currentPanel: MigrationPanelWebview | undefined; - public static readonly viewType = "ballerina.migration-panel"; - private readonly _panel: WebviewPanel; - private _disposables: Disposable[] = []; - - private constructor(panel: WebviewPanel) { - this._panel = panel; - this._panel.onDidDispose(() => this.dispose(), null, this._disposables); - this._panel.webview.html = this._getWebviewContent(this._panel.webview); - RPCLayer.create(this._panel); - } - - /** - * Reveal an existing panel or create a new one. - */ - public static render(): void { - if (MigrationPanelWebview.currentPanel) { - MigrationPanelWebview.currentPanel._panel.reveal(ViewColumn.Beside); - return; - } - - const panel = window.createWebviewPanel( - MigrationPanelWebview.viewType, - "Migration Assistant", - ViewColumn.Beside, - { - enableScripts: true, - localResourceRoots: [Uri.file(path.join(extension.context.extensionPath, "resources"))], - retainContextWhenHidden: true, - } - ); - - panel.iconPath = { - light: Uri.file(path.join(extension.context.extensionPath, "resources", "icons", "dark-ai-chat.svg")), - dark: Uri.file(path.join(extension.context.extensionPath, "resources", "icons", "light-ai-chat.svg")), - }; - - MigrationPanelWebview.currentPanel = new MigrationPanelWebview(panel); - } - - public getWebview(): WebviewPanel { - return this._panel; - } - - public dispose(): void { - MigrationPanelWebview.currentPanel = undefined; - this._panel.dispose(); - while (this._disposables.length) { - const d = this._disposables.pop(); - if (d) { - d.dispose(); - } - } - } - - // ----- Private helpers ------------------------------------------------- - - private _getWebviewContent(webView: Webview): string { - const body = `
-
-
-
-
`; - const bodyCss = ``; - const styles = ` - .container { - background-color: var(--vscode-editor-background); - height: 100vh; - width: 100%; - } - .loader-wrapper { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - } - .loader { - width: 32px; - aspect-ratio: 1; - border-radius: 50%; - border: 4px solid var(--vscode-button-background); - animation: - l20-1 0.8s infinite linear alternate, - l20-2 1.6s infinite linear; - } - @keyframes l20-1{ - 0% {clip-path: polygon(50% 50%,0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0% )} - 12.5% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0% )} - 25% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100% )} - 50% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )} - 62.5% {clip-path: polygon(50% 50%,100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )} - 75% {clip-path: polygon(50% 50%,100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100% )} - 100% {clip-path: polygon(50% 50%,50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100% )} - } - @keyframes l20-2{ - 0% {transform:scaleY(1) rotate(0deg)} - 49.99%{transform:scaleY(1) rotate(135deg)} - 50% {transform:scaleY(-1) rotate(0deg)} - 100% {transform:scaleY(-1) rotate(-135deg)} - } - `; - const scripts = ` - function loadedScript() { - function renderDiagrams() { - visualizerWebview.renderWebview("migration", document.getElementById("webview-container")); - } - renderDiagrams(); - } - `; - - const webViewOptions: WebViewOptions = { - ...getComposerWebViewOptions("Visualizer", webView), - body, - scripts, - styles, - bodyCss, - }; - - return getLibraryWebViewContent(webViewOptions, webView); - } -} diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts index e88d2037ec..0bc2cfa6a0 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts @@ -40,7 +40,6 @@ export class VisualizerWebview { public static readonly biTitle = "WSO2 Integrator"; private _panel: vscode.WebviewPanel | undefined; private _disposables: vscode.Disposable[] = []; - private _pendingProjectInfoRefresh = false; constructor() { this._panel = VisualizerWebview.createWebview(); @@ -55,10 +54,6 @@ export class VisualizerWebview { } }, 500); - const debouncedRefreshWorkspaceProjectInfo = debounce(() => { - StateMachine.refreshProjectInfo(); - }, 500); - const debouncedRefreshDataMapper = debounce(async () => { const stateMachineContext = StateMachine.context(); const { documentUri, dataMapperMetadata: { codeData, name } } = stateMachineContext; @@ -86,18 +81,11 @@ export class VisualizerWebview { if (document?.contentChanges.length === 0 || !machineReady) { return; } const balFileModified = document?.document.languageId === LANGUAGE.BALLERINA; - const configTomlModified = document.document.languageId === LANGUAGE.TOML && document.document.fileName.endsWith("Config.toml") && vscode.window.visibleTextEditors.some(editor => editor.document.fileName === document.document.fileName ); - - const workspacePath = StateMachine.context().workspacePath; - const workspaceBallerinaTomlModified = !!workspacePath && - document.document.languageId === LANGUAGE.TOML && - document.document.fileName === path.join(workspacePath, "Ballerina.toml"); - const dataMapperModified = balFileModified && ( StateMachine.context().view === MACHINE_VIEW.InlineDataMapper || @@ -107,9 +95,6 @@ export class VisualizerWebview { if (dataMapperModified) { debouncedRefreshDataMapper(); - } else if (workspaceBallerinaTomlModified) { - // Defer the project info refresh until the webview is active - this._pendingProjectInfoRefresh = true; } else if ((this._panel?.active || AiPanelWebview.currentPanel?.getWebview()?.active) && balFileModified) { sendUpdateNotificationToWebview(); } else if (configTomlModified) { @@ -139,12 +124,7 @@ export class VisualizerWebview { const machineReady = typeof state === 'object' && 'viewActive' in state && state.viewActive === "viewReady"; const popupActive = typeof popupState === 'object' && 'open' in popupState && popupState.open === "active"; if (this._panel?.active && machineReady && !popupActive) { - if (this._pendingProjectInfoRefresh) { - this._pendingProjectInfoRefresh = false; - debouncedRefreshWorkspaceProjectInfo(); - } else { - sendUpdateNotificationToWebview(true); - } + sendUpdateNotificationToWebview(true); } }); diff --git a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/index.ts b/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/index.ts deleted file mode 100644 index c2c9adfccf..0000000000 --- a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -import * as path from "path"; -import Mocha from "mocha"; -import { glob } from "glob"; - -export async function run(): Promise { - const mocha = new Mocha({ - ui: "tdd", - color: true, - timeout: 10000, - }); - - const testsRoot = path.resolve(__dirname); - const files = await glob("**/*.test.js", { cwd: testsRoot }); - files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); - - return new Promise((resolve, reject) => { - mocha.run((failures) => { - if (failures > 0) { - reject(new Error(`${failures} tests failed.`)); - } else { - resolve(); - } - }); - }); -} - -// Allow running directly with node -if (require.main === module) { - run().then( - () => process.exit(0), - (err) => { - console.error(err); - process.exit(1); - } - ); -} diff --git a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/expected/library-get-tool.txt b/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/expected/library-get-tool.txt deleted file mode 100644 index 8ddd51d592..0000000000 --- a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/expected/library-get-tool.txt +++ /dev/null @@ -1,7018 +0,0 @@ -// ============================================================ -// Library: ballerinax/trigger.salesforce -// Listen to [Salesforce Streaming API](https://developer.salesforce.com/docs/atlas.en-us.api_streaming.meta/api_streaming/intro_stream.htm) from Ballerina -// ============================================================ -import ballerinax/trigger.salesforce; - -// ============================================================ -// Library: ballerinax/salesforce -// Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) software, provided by Salesforce.Inc. Salesforce enable users to efficiently manage sales and customer relationships through its APIs, robust and secure databases, and analytics services. Sales cloud provides serveral API packages to make operations on sObjects and metadata, execute queries and searches, and listen to change events through API calls using REST, SOAP, and CometD protocols. -// ============================================================ -import ballerinax/salesforce; - -// --- Types --- - - -type ConnectionConfig record { - string baseUrl; - http:BearerTokenConfig|config:OAuth2RefreshTokenGrantConfig|config:OAuth2PasswordGrantConfig|config:OAuth2ClientCredentialsGrantConfig auth; // Special Agent Note: BearerTokenConfig FROM ballerina/http package, OAuth2RefreshTokenGrantConfig, OAuth2PasswordGrantConfig, OAuth2ClientCredentialsGrantConfig FROM ballerinax/client.config package - string apiVersion?; - http:HttpVersion httpVersion?; // Special Agent Note: HttpVersion FROM ballerina/http package - config:ClientHttp1Settings http1Settings?; // Special Agent Note: ClientHttp1Settings FROM ballerinax/client.config package - http:ClientHttp2Settings http2Settings?; // Special Agent Note: ClientHttp2Settings FROM ballerina/http package - decimal timeout?; - string forwarded?; - http:PoolConfiguration poolConfig?; // Special Agent Note: PoolConfiguration FROM ballerina/http package - http:CacheConfig cache?; // Special Agent Note: CacheConfig FROM ballerina/http package - http:Compression compression?; // Special Agent Note: Compression FROM ballerina/http package - http:CircuitBreakerConfig circuitBreaker?; // Special Agent Note: CircuitBreakerConfig FROM ballerina/http package - http:RetryConfig retryConfig?; // Special Agent Note: RetryConfig FROM ballerina/http package - http:ResponseLimitConfigs responseLimits?; // Special Agent Note: ResponseLimitConfigs FROM ballerina/http package - http:ClientSecureSocket secureSocket?; // Special Agent Note: ClientSecureSocket FROM ballerina/http package - http:ProxyConfig proxy?; // Special Agent Note: ProxyConfig FROM ballerina/http package - boolean validation?; -}; - -// --- Client --- - -# Ballerina Salesforce connector provides the capability to access Salesforce REST API. -# This connector lets you to perform operations for SObjects, query using SOQL, search using SOSL, and describe SObjects -# and organizational data. -client class Client { - function init(ConnectionConfig config) returns error?; - - # Executes the specified SOQL query. - # - remote function query(string soql, record {|anydata...;|} returnType = record {|anydata...;|}) returns stream|error; - - # Gets an object record by ID. - # - remote function getById(string sobjectName, string id, record {|anydata...;|} returnType = record {|anydata...;|}) returns returnType|error; -} - -// --- Service --- - -service on new salesforce:Listener(salesforce:ListenerConfig listenerConfig = ) { - # The `onCreate` method is triggered when a new record create event is received from Salesforce. - remote function onCreate(salesforce:EventData payload) returns error?; - - # The `onUpdate` method is triggered when a new record update event is received from Salesforce. - remote function onUpdate(salesforce:EventData payload) returns error?; - - # The `onDelete` method is triggered when a new record delete event is received from Salesforce. - remote function onDelete(salesforce:EventData payload) returns error?; - - # The `onRestore` method is triggered when a new record restore event is received from Salesforce. - remote function onRestore(salesforce:EventData payload) returns error?; -} - -// ============================================================ -// Library: ballerinax/docusign.dsesign -// [DocuSign](https://www.docusign.com) is a digital transaction management platform that enables users to securely sign, send, and manage documents electronically. -// ============================================================ -import ballerinax/docusign.dsesign; - -// --- Types --- - - -type ClientHttp1Settings record { - http:KeepAlive keepAlive?; // Special Agent Note: KeepAlive FROM ballerina/http package - http:Chunking chunking?; // Special Agent Note: Chunking FROM ballerina/http package - ProxyConfig proxy?; -}; - - -type ConnectionConfig record { - http:HttpVersion httpVersion?; // Special Agent Note: HttpVersion FROM ballerina/http package - ClientHttp1Settings http1Settings?; - http:ClientHttp2Settings http2Settings?; // Special Agent Note: ClientHttp2Settings FROM ballerina/http package - decimal timeout?; - string forwarded?; - http:PoolConfiguration poolConfig?; // Special Agent Note: PoolConfiguration FROM ballerina/http package - http:CacheConfig cache?; // Special Agent Note: CacheConfig FROM ballerina/http package - http:Compression compression?; // Special Agent Note: Compression FROM ballerina/http package - http:CircuitBreakerConfig circuitBreaker?; // Special Agent Note: CircuitBreakerConfig FROM ballerina/http package - http:RetryConfig retryConfig?; // Special Agent Note: RetryConfig FROM ballerina/http package - http:ResponseLimitConfigs responseLimits?; // Special Agent Note: ResponseLimitConfigs FROM ballerina/http package - http:ClientSecureSocket secureSocket?; // Special Agent Note: ClientSecureSocket FROM ballerina/http package - http:ProxyConfig proxy?; // Special Agent Note: ProxyConfig FROM ballerina/http package - boolean validation?; - http:CredentialsConfig|http:BearerTokenConfig|http:JwtIssuerConfig|http:OAuth2ClientCredentialsGrantConfig|http:OAuth2PasswordGrantConfig|http:OAuth2RefreshTokenGrantConfig|http:OAuth2JwtBearerGrantConfig|() auth?; // Special Agent Note: CredentialsConfig, BearerTokenConfig, JwtIssuerConfig, OAuth2ClientCredentialsGrantConfig, OAuth2PasswordGrantConfig, OAuth2RefreshTokenGrantConfig, OAuth2JwtBearerGrantConfig FROM ballerina/http package -}; - - -type EnvelopeDefinition record { - string accessControlListBase64?; - string accessibility?; - string allowComments?; - string allowMarkup?; - string allowReassign?; - string allowRecipientRecursion?; - string allowViewHistory?; - string anySigner?; - string asynchronous?; - Attachment[] attachments?; - string attachmentsUri?; - string authoritativeCopy?; - string authoritativeCopyDefault?; - string autoNavigation?; - string brandId?; - string brandLock?; - string burnDefaultTabData?; - string certificateUri?; - string completedDateTime?; - CompositeTemplate[] compositeTemplates?; - string copyRecipientData?; - string createdDateTime?; - AccountCustomFields customFields?; - string customFieldsUri?; - string declinedDateTime?; - string deletedDateTime?; - string deliveredDateTime?; - string disableResponsiveDocument?; - string documentBase64?; - Document[] documents?; - string documentsCombinedUri?; - string documentsUri?; - string emailBlurb?; - EmailSettings emailSettings?; - string emailSubject?; - string enableWetSign?; - string enforceSignerVisibility?; - Attachment[] envelopeAttachments?; - EnvelopeCustomMetadata envelopeCustomMetadata?; - EnvelopeDocument[] envelopeDocuments?; - string envelopeId?; - string envelopeIdStamping?; - string envelopeLocation?; - EnvelopeMetadata envelopeMetadata?; - string envelopeUri?; - EventNotification eventNotification?; - string expireAfter?; - string expireDateTime?; - string expireEnabled?; - string externalEnvelopeId?; - Folder[] folders?; - string hasComments?; - string hasFormDataChanged?; - string hasWavFile?; - string holder?; - string initialSentDateTime?; - string is21CFRPart11?; - string isDynamicEnvelope?; - string isSignatureProviderEnvelope?; - string lastModifiedDateTime?; - string location?; - EnvelopeLocks lockInformation?; - string messageLock?; - Notification notification?; - string notificationUri?; - string password?; - PowerForm powerForm?; - string purgeCompletedDate?; - string purgeRequestDate?; - string purgeState?; - EnvelopeRecipients recipients?; - string recipientsLock?; - string recipientsUri?; - RecipientViewRequest recipientViewRequest?; - UserInfo sender?; - string sentDateTime?; - string signerCanSignOnMobile?; - string signingLocation?; - string status?; - string statusChangedDateTime?; - string statusDateTime?; - string templateId?; - TemplateRole[] templateRoles?; - string templatesUri?; - string transactionId?; - string useDisclosure?; - string voidedDateTime?; - string voidedReason?; - Workflow workflow?; -}; - - -type EnvelopeSummary record { - BulkEnvelopeStatus bulkEnvelopeStatus?; - string envelopeId?; - ErrorDetails errorDetails?; - string recipientSigningUri?; - string recipientSigningUriError?; - string status?; - string statusDateTime?; - string uri?; -}; - - -type EnvelopesInformation record { - string continuationToken?; - string endPosition?; - Envelope[] envelopes?; - string envelopeSearchSource?; - EnvelopeTransactionStatus[] envelopeTransactionStatuses?; - Folder[] folders?; - string lastQueriedDateTime?; - string nextUri?; - string previousUri?; - string resultSetSize?; - string startPosition?; - string totalSetSize?; -}; - - -type Envelope record { - string accessControlListBase64?; - string allowComments?; - string allowMarkup?; - string allowReassign?; - string allowViewHistory?; - string? anySigner?; - string asynchronous?; - string attachmentsUri?; - string authoritativeCopy?; - string authoritativeCopyDefault?; - string autoNavigation?; - string brandId?; - string brandLock?; - string burnDefaultTabData?; - string certificateUri?; - string completedDateTime?; - string copyRecipientData?; - string createdDateTime?; - AccountCustomFields customFields?; - string customFieldsUri?; - string declinedDateTime?; - string deletedDateTime?; - string deliveredDateTime?; - string disableResponsiveDocument?; - string documentBase64?; - string documentsCombinedUri?; - string documentsUri?; - string emailBlurb?; - EmailSettings emailSettings?; - string emailSubject?; - string enableWetSign?; - string enforceSignerVisibility?; - Attachment[] envelopeAttachments?; - EnvelopeCustomMetadata envelopeCustomMetadata?; - EnvelopeDocument[] envelopeDocuments?; - string envelopeId?; - string envelopeIdStamping?; - string envelopeLocation?; - EnvelopeMetadata envelopeMetadata?; - string envelopeUri?; - string expireAfter?; - string expireDateTime?; - string expireEnabled?; - string externalEnvelopeId?; - Folder[] folders?; - string hasComments?; - string hasFormDataChanged?; - string hasWavFile?; - string holder?; - string initialSentDateTime?; - string is21CFRPart11?; - string isDynamicEnvelope?; - string isSignatureProviderEnvelope?; - string lastModifiedDateTime?; - string location?; - EnvelopeLocks lockInformation?; - string messageLock?; - Notification notification?; - string notificationUri?; - PowerForm powerForm?; - string purgeCompletedDate?; - string purgeRequestDate?; - string purgeState?; - EnvelopeRecipients recipients?; - string recipientsLock?; - string recipientsUri?; - UserInfo sender?; - string sentDateTime?; - string signerCanSignOnMobile?; - string signingLocation?; - string status?; - string statusChangedDateTime?; - string statusDateTime?; - string templatesUri?; - string transactionId?; - string useDisclosure?; - string voidedDateTime?; - string voidedReason?; - Workflow workflow?; -}; - - -type ProxyConfig record { - string host?; - int port?; - string userName?; - string password?; -}; - - -type Attachment record { - string accessControl?; - string attachmentId?; - string attachmentType?; - string data?; - string label?; - string name?; - string remoteUrl?; -}; - - -type CompositeTemplate record { - string compositeTemplateId?; - Document document?; - InlineTemplate[] inlineTemplates?; - string pdfMetaDataTemplateSequence?; - ServerTemplate[] serverTemplates?; -}; - - -type AccountCustomFields record { - ListCustomField[] listCustomFields?; - TextCustomField[] textCustomFields?; -}; - - -type Document record { - string applyAnchorTabs?; - string assignTabsToRecipientId?; - boolean authoritativeCopy?; - string display?; - DocGenFormField[] docGenFormFields?; - string documentBase64?; - NameValue[] documentFields?; - string documentId?; - string encryptedWithKeyManager?; - string fileExtension?; - string fileFormatHint?; - DocumentHtmlDefinition htmlDefinition?; - string includeInDownload?; - string isDocGenDocument?; - MatchBox[] matchBoxes?; - string name?; - string 'order?; - string pages?; - string password?; - string pdfFormFieldOption?; - string remoteUrl?; - string signerMustAcknowledge?; - boolean signerMustAcknowledgeUseAccountDefault?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string transformPdfFields?; - string uri?; -}; - - -type EmailSettings record { - BccEmailAddress[] bccEmailAddresses?; - string replyEmailAddressOverride?; - string replyEmailNameOverride?; -}; - - -type EnvelopeCustomMetadata record { - NameValue[] envelopeCustomMetadataDetails?; -}; - - -type EnvelopeDocument record { - string[] addedRecipientIds?; - string attachmentTabId?; - string authoritativeCopy?; - PropertyMetadata authoritativeCopyMetadata?; - SignatureType[] availableDocumentTypes?; - string containsPdfFormFields?; - string display?; - PropertyMetadata displayMetadata?; - string docGenDocumentStatus?; - DocGenSyntaxError[] docGenErrors?; - DocGenFormField[] docGenFormFields?; - string documentBase64?; - NameValue[] documentFields?; - string documentId?; - string documentIdGuid?; - ErrorDetails errorDetails?; - string includeInDownload?; - PropertyMetadata includeInDownloadMetadata?; - string isAceGenDocument?; - string isDocGenDocument?; - string name?; - PropertyMetadata nameMetadata?; - string 'order?; - Page[] pages?; - string signerMustAcknowledge?; - PropertyMetadata signerMustAcknowledgeMetadata?; - string sizeBytes?; - string templateLocked?; - string templateRequired?; - string 'type?; - string uri?; -}; - - -type EnvelopeMetadata record { - string allowAdvancedCorrect?; - string allowCorrect?; - string enableSignWithNotary?; -}; - - -type EventNotification record { - string deliveryMode?; - EnvelopeEvent[] envelopeEvents?; - ConnectEventData eventData?; - string[] events?; - string includeCertificateOfCompletion?; - string includeCertificateWithSoap?; - string includeDocumentFields?; - string includeDocuments?; - string includeEnvelopeVoidReason?; - string includeHMAC?; - string includeOAuth?; - string includeSenderAccountAsCustomField?; - string includeTimeZone?; - string integratorManaged?; - string loggingEnabled?; - RecipientEvent[] recipientEvents?; - string requireAcknowledgment?; - string signMessageWithX509Cert?; - string soapNameSpace?; - string url?; - string useSoapInterface?; -}; - - -type Folder record { - ErrorDetails errorDetails?; - Filter filter?; - string folderId?; - FolderItem_v2[] folderItems?; - Folder[] folders?; - string hasAccess?; - string hasSubFolders?; - string itemCount?; - string name?; - UserInfo owner?; - string parentFolderId?; - string parentFolderUri?; - string subFolderCount?; - string 'type?; - string uri?; -}; - - -type EnvelopeLocks record { - ErrorDetails errorDetails?; - string lockDurationInSeconds?; - string lockedByApp?; - UserInfo lockedByUser?; - string lockedUntilDateTime?; - string lockToken?; - string lockType?; - string useScratchPad?; -}; - - -type Notification record { - Expirations expirations?; - Reminders reminders?; - string useAccountDefaults?; -}; - - -type PowerForm record { - string createdBy?; - string createdDateTime?; - string emailBody?; - string emailSubject?; - Envelope[] envelopes?; - ErrorDetails errorDetails?; - string instructions?; - string isActive?; - string lastUsed?; - string limitUseInterval?; - string limitUseIntervalEnabled?; - string limitUseIntervalUnits?; - string maxUseEnabled?; - string name?; - string powerFormId?; - string powerFormUrl?; - PowerFormRecipient[] recipients?; - string senderName?; - string senderUserId?; - string signingMode?; - string templateId?; - string templateName?; - string timesUsed?; - string uri?; - string usesRemaining?; -}; - - -type EnvelopeRecipients record { - Agent[] agents?; - CarbonCopy[] carbonCopies?; - CertifiedDelivery[] certifiedDeliveries?; - string currentRoutingOrder?; - Editor[] editors?; - ErrorDetails errorDetails?; - InPersonSigner[] inPersonSigners?; - Intermediary[] intermediaries?; - NotaryRecipient[] notaries?; - Participant[] participants?; - string recipientCount?; - SealSign[] seals?; - Signer[] signers?; - Witness[] witnesses?; -}; - - -type RecipientViewRequest record { - string assertionId?; - string authenticationInstant?; - string authenticationMethod?; - RecipientTokenClientURLs clientURLs?; - string clientUserId?; - string email?; - string[] frameAncestors?; - string[] messageOrigins?; - string pingFrequency?; - string pingUrl?; - string recipientId?; - string returnUrl?; - string securityDomain?; - string userId?; - string userName?; - string xFrameOptions?; - string xFrameOptionsAllowFromUrl?; -}; - - -type UserInfo record { - string accountId?; - string accountName?; - string activationAccessCode?; - string email?; - ErrorDetails errorDetails?; - string ipAddress?; - string loginStatus?; - string membershipId?; - string sendActivationEmail?; - string uri?; - string userId?; - string userName?; - string userStatus?; - string userType?; -}; - - -type TemplateRole record { - string accessCode?; - RecipientAdditionalNotification[] additionalNotifications?; - string clientUserId?; - string defaultRecipient?; - string deliveryMethod?; - string email?; - RecipientEmailNotification emailNotification?; - string embeddedRecipientStartURL?; - string inPersonSignerName?; - string name?; - RecipientPhoneNumber phoneNumber?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string roleName?; - string routingOrder?; - string signingGroupId?; - EnvelopeRecipientTabs tabs?; -}; - - -type Workflow record { - string currentWorkflowStepId?; - string resumeDate?; - ScheduledSending scheduledSending?; - string workflowStatus?; - WorkflowStep[] workflowSteps?; -}; - - -type BulkEnvelopeStatus record { - string batchId?; - string batchSize?; - BulkEnvelope[] bulkEnvelopes?; - string bulkEnvelopesBatchUri?; - string endPosition?; - string failed?; - string nextUri?; - string previousUri?; - string queued?; - string resultSetSize?; - string sent?; - string startPosition?; - string submittedDate?; - string totalSetSize?; -}; - - -type ErrorDetails record { - string errorCode?; - string message?; -}; - - -type EnvelopeTransactionStatus record { - string envelopeId?; - ErrorDetails errorDetails?; - string status?; - string transactionId?; -}; - - -type InlineTemplate record { - AccountCustomFields customFields?; - Document[] documents?; - Envelope envelope?; - EnvelopeRecipients recipients?; - string sequence?; -}; - - -type ServerTemplate record { - string sequence?; - string templateId?; -}; - - -type ListCustomField record { - string configurationType?; - ErrorDetails errorDetails?; - string fieldId?; - string[] listItems?; - string name?; - string required?; - string show?; - string value?; -}; - - -type TextCustomField record { - string configurationType?; - ErrorDetails errorDetails?; - string fieldId?; - string name?; - string required?; - string show?; - string value?; -}; - - -type DocGenFormField record { - string description?; - string label?; - string name?; - DocGenFormFieldOption[] options?; - string predefinedValidation?; - string required?; - string 'type?; - DocGenFormFieldValidation validation?; - string value?; -}; - - -type NameValue record { - ErrorDetails errorDetails?; - string name?; - string originalValue?; - string value?; -}; - - -type DocumentHtmlDefinition record { - string displayAnchorPrefix?; - DocumentHtmlDisplayAnchor[] displayAnchors?; - string displayOrder?; - string displayPageNumber?; - string documentGuid?; - string documentId?; - string headerLabel?; - string maxScreenWidth?; - string removeEmptyTags?; - string showMobileOptimizedToggle?; - string 'source?; -}; - - -type MatchBox record { - string height?; - string pageNumber?; - string width?; - string xPosition?; - string yPosition?; -}; - - -type EnvelopeRecipientTabs record { - Approve[] approveTabs?; - Checkbox[] checkboxTabs?; - CommentThread[] commentThreadTabs?; - CommissionCounty[] commissionCountyTabs?; - CommissionExpiration[] commissionExpirationTabs?; - CommissionNumber[] commissionNumberTabs?; - CommissionState[] commissionStateTabs?; - Company[] companyTabs?; - DateSigned[] dateSignedTabs?; - Date[] dateTabs?; - Decline[] declineTabs?; - Draw[] drawTabs?; - EmailAddress[] emailAddressTabs?; - Email[] emailTabs?; - EnvelopeId[] envelopeIdTabs?; - FirstName[] firstNameTabs?; - FormulaTab[] formulaTabs?; - FullName[] fullNameTabs?; - InitialHere[] initialHereTabs?; - LastName[] lastNameTabs?; - List[] listTabs?; - Notarize[] notarizeTabs?; - NotarySeal[] notarySealTabs?; - Note[] noteTabs?; - Number[] numberTabs?; - Numerical[] numericalTabs?; - PhoneNumber[] phoneNumberTabs?; - PolyLineOverlay[] polyLineOverlayTabs?; - PrefillTabs prefillTabs?; - RadioGroup[] radioGroupTabs?; - SignerAttachment[] signerAttachmentTabs?; - SignHere[] signHereTabs?; - SmartSection[] smartSectionTabs?; - Ssn[] ssnTabs?; - TabGroup[] tabGroups?; - Text[] textTabs?; - Title[] titleTabs?; - View[] viewTabs?; - Zip[] zipTabs?; -}; - - -type BccEmailAddress record { - string bccEmailAddressId?; - string email?; -}; - - -type PropertyMetadata record { - string[] options?; - string rights?; -}; - - -type SignatureType record { - string isDefault?; - string 'type?; -}; - - -type DocGenSyntaxError record { - string errorCode?; - string message?; - string tagIdentifier?; -}; - - -type Page record { - string dpi?; - ErrorDetails errorDetails?; - string height?; - string imageBytes?; - string mimeType?; - string pageId?; - string sequence?; - string width?; -}; - - -type EnvelopeEvent record { - string envelopeEventStatusCode?; - string includeDocuments?; -}; - - -type ConnectEventData record { - string format?; - string[] includeData?; - string version?; -}; - - -type RecipientEvent record { - string includeDocuments?; - string recipientEventStatusCode?; -}; - - -type Filter record { - string actionRequired?; - string expires?; - string folderIds?; - string fromDateTime?; - string isTemplate?; - string 'order?; - string orderBy?; - string searchTarget?; - string searchText?; - string status?; - string toDateTime?; -}; - - -type FolderItem_v2 record { - string completedDateTime?; - string createdDateTime?; - string envelopeId?; - string envelopeUri?; - string expireDateTime?; - string folderId?; - string folderUri?; - string is21CFRPart11?; - string lastModifiedDateTime?; - string ownerName?; - EnvelopeRecipients recipients?; - string recipientsUri?; - string senderCompany?; - string senderEmail?; - string senderName?; - string senderUserId?; - string sentDateTime?; - string status?; - string subject?; - string templateId?; - string templateUri?; -}; - - -type Expirations record { - string expireAfter?; - string expireEnabled?; - string expireWarn?; -}; - - -type Reminders record { - string reminderDelay?; - string reminderEnabled?; - string reminderFrequency?; -}; - - -type PowerFormRecipient record { - string accessCode?; - string accessCodeLocked?; - string accessCodeRequired?; - string email?; - string emailLocked?; - string idCheckConfigurationName?; - string idCheckRequired?; - string name?; - string recipientType?; - string roleName?; - string routingOrder?; - string templateRequiresIdLookup?; - string userNameLocked?; -}; - - -type Agent record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type CarbonCopy record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string agentCanEditEmail?; - string agentCanEditName?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string linkedAccountConfigurationId?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientProofFile proofFile?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type CertifiedDelivery record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string agentCanEditEmail?; - string agentCanEditName?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientProofFile proofFile?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type Editor record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type InPersonSigner record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - string allowSystemOverrideForLockedRecipient?; - string autoNavigation?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string canSignOffline?; - string clientUserId?; - string completedCount?; - string creationReason?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string defaultRecipient?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string hostEmail?; - PropertyMetadata hostEmailMetadata?; - string hostName?; - PropertyMetadata hostNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string inPersonSigningType?; - PropertyMetadata inPersonSigningTypeMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - NotaryHost notaryHost?; - string notaryId?; - string note?; - PropertyMetadata noteMetadata?; - OfflineAttributes offlineAttributes?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string recipientSuppliesTabs?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string requireSignerCertificate?; - string requireSignOnPaper?; - string requireUploadSignature?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - RecipientSignatureInformation signatureInfo?; - string signedDateTime?; - string signerEmail?; - PropertyMetadata signerEmailMetadata?; - string signerFirstName?; - PropertyMetadata signerFirstNameMetadata?; - string signerLastName?; - PropertyMetadata signerLastNameMetadata?; - string signerName?; - PropertyMetadata signerNameMetadata?; - string signInEachLocation?; - PropertyMetadata signInEachLocationMetadata?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type Intermediary record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type NotaryRecipient record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string agentCanEditEmail?; - string agentCanEditName?; - string allowSystemOverrideForLockedRecipient?; - string autoNavigation?; - string autoRespondedReason?; - string bulkRecipientsUri?; - string bulkSendV2Recipient?; - string canSignOffline?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string creationReason?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string defaultRecipient?; - DelegationInfo delegatedBy?; - DelegationInfo[] delegatedTo?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string isBulkRecipient?; - PropertyMetadata isBulkRecipientMetadata?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string liveOakStartURL?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string notaryId?; - string notarySignerEmailSent?; - string[] notarySigners?; - string notarySourceType?; - string notaryThirdPartyPartner?; - string notaryType?; - string note?; - PropertyMetadata noteMetadata?; - OfflineAttributes offlineAttributes?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientProofFile proofFile?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string recipientSuppliesTabs?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string requireSignerCertificate?; - string requireSignOnPaper?; - string requireUploadSignature?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - RecipientSignatureInformation signatureInfo?; - string signedDateTime?; - string signInEachLocation?; - PropertyMetadata signInEachLocationMetadata?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type Participant record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - string participateFor?; - string participateForGuid?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type SealSign record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - RecipientEmailNotification emailNotification?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type Signer record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string agentCanEditEmail?; - string agentCanEditName?; - string allowSystemOverrideForLockedRecipient?; - string autoNavigation?; - string autoRespondedReason?; - string bulkRecipientsUri?; - string bulkSendV2Recipient?; - string canSignOffline?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string creationReason?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string defaultRecipient?; - DelegationInfo delegatedBy?; - DelegationInfo[] delegatedTo?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string isBulkRecipient?; - PropertyMetadata isBulkRecipientMetadata?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string notaryId?; - string notarySignerEmailSent?; - string note?; - PropertyMetadata noteMetadata?; - OfflineAttributes offlineAttributes?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientProofFile proofFile?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string recipientSuppliesTabs?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string requireSignerCertificate?; - string requireSignOnPaper?; - string requireUploadSignature?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - RecipientSignatureInformation signatureInfo?; - string signedDateTime?; - string signInEachLocation?; - PropertyMetadata signInEachLocationMetadata?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type Witness record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - RecipientAdditionalNotification[] additionalNotifications?; - string agentCanEditEmail?; - string agentCanEditName?; - string allowSystemOverrideForLockedRecipient?; - string autoNavigation?; - string autoRespondedReason?; - string bulkRecipientsUri?; - string bulkSendV2Recipient?; - string canSignOffline?; - string clientUserId?; - string completedCount?; - ConsentDetails[] consentDetailsList?; - string creationReason?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string defaultRecipient?; - DelegationInfo delegatedBy?; - DelegationInfo[] delegatedTo?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string emailRecipientPostSigningURL?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string[] excludedDocuments?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string firstName?; - PropertyMetadata firstNameMetadata?; - string fullName?; - PropertyMetadata fullNameMetadata?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string isBulkRecipient?; - PropertyMetadata isBulkRecipientMetadata?; - string lastName?; - PropertyMetadata lastNameMetadata?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string notaryId?; - string notarySignerEmailSent?; - string note?; - PropertyMetadata noteMetadata?; - OfflineAttributes offlineAttributes?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientPhoneNumber phoneNumber?; - RecipientProofFile proofFile?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - RecipientSignatureProvider[] recipientSignatureProviders?; - string recipientSuppliesTabs?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string requireSignerCertificate?; - string requireSignOnPaper?; - string requireUploadSignature?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - RecipientSignatureInformation signatureInfo?; - string signedDateTime?; - string signInEachLocation?; - PropertyMetadata signInEachLocationMetadata?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; - string witnessFor?; - string witnessForGuid?; -}; - - -type RecipientTokenClientURLs record { - string onAccessCodeFailed?; - string onCancel?; - string onDecline?; - string onException?; - string onFaxPending?; - string onIdCheckFailed?; - string onSessionTimeout?; - string onSigningComplete?; - string onTTLExpired?; - string onViewingComplete?; -}; - - -type RecipientAdditionalNotification record { - RecipientPhoneNumber phoneNumber?; - string secondaryDeliveryMethod?; - PropertyMetadata secondaryDeliveryMethodMetadata?; - string secondaryDeliveryStatus?; -}; - - -type RecipientEmailNotification record { - string emailBody?; - PropertyMetadata emailBodyMetadata?; - string emailSubject?; - PropertyMetadata emailSubjectMetadata?; - string supportedLanguage?; - PropertyMetadata supportedLanguageMetadata?; -}; - - -type RecipientPhoneNumber record { - string countryCode?; - PropertyMetadata countryCodeMetadata?; - string number?; - PropertyMetadata numberMetadata?; -}; - - -type RecipientSignatureProvider record { - string sealDocumentsWithTabsOnly?; - string sealName?; - string signatureProviderName?; - PropertyMetadata signatureProviderNameMetadata?; - RecipientSignatureProviderOptions signatureProviderOptions?; -}; - - -type ScheduledSending record { - string bulkListId?; - string resumeDate?; - EnvelopeDelayRule[] rules?; - string status?; -}; - - -type WorkflowStep record { - string action?; - string completedDate?; - DelayedRouting delayedRouting?; - string itemId?; - RecipientRouting recipientRouting?; - string status?; - string triggeredDate?; - string triggerOnItem?; - string workflowStepId?; -}; - - -type BulkEnvelope record { - string bulkRecipientRow?; - string bulkStatus?; - string email?; - string envelopeId?; - string envelopeUri?; - ErrorDetails errorDetails?; - string name?; - string submittedDateTime?; - string transactionId?; -}; - - -type DocGenFormFieldOption record { - string description?; - string label?; - string selected?; - string value?; -}; - - -type DocGenFormFieldValidation record { - string errorMessage?; - string expression?; -}; - - -type DocumentHtmlDisplayAnchor record { - boolean caseSensitive?; - DocumentHtmlDisplaySettings displaySettings?; - string endAnchor?; - boolean removeEndAnchor?; - boolean removeStartAnchor?; - string startAnchor?; -}; - - -type Approve record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string buttonText?; - PropertyMetadata buttonTextMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Checkbox record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string selected?; - PropertyMetadata selectedMetadata?; - string selectedOriginal?; - PropertyMetadata selectedOriginalMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type CommentThread record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - Comment[] comments?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string threadId?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type CommissionCounty record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type CommissionExpiration record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type CommissionNumber record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type CommissionState record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Company record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type DateSigned record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Date record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Decline record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string buttonText?; - PropertyMetadata buttonTextMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string declineReason?; - PropertyMetadata declineReasonMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Draw record { - string allowSignerUpload?; - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string useBackgroundAsCanvas?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type EmailAddress record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Email record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type EnvelopeId record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type FirstName record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type FormulaTab record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string formula?; - PropertyMetadata formulaMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string hidden?; - PropertyMetadata hiddenMetadata?; - PropertyMetadata isPaymentAmountMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - PaymentDetails paymentDetails?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string roundDecimalPlaces?; - PropertyMetadata roundDecimalPlacesMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type FullName record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type InitialHere record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string handDrawRequired?; - string height?; - PropertyMetadata heightMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string optional?; - PropertyMetadata optionalMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string scaleValue?; - PropertyMetadata scaleValueMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type LastName record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type List record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - ListItem[] listItems?; - string listSelectedValue?; - PropertyMetadata listSelectedValueMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Notarize record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type NotarySeal record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string scaleValue?; - PropertyMetadata scaleValueMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Note record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Number record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string formula?; - PropertyMetadata formulaMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Numerical record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - string maxNumericalValue?; - MergeField mergeField?; - string mergeFieldXml?; - string minNumericalValue?; - string name?; - PropertyMetadata nameMetadata?; - string numericalValue?; - string originalNumericalValue?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationType?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type PhoneNumber record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type PolyLineOverlay record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - GraphicsContext graphicsContext?; - string height?; - PropertyMetadata heightMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string overlayType?; - PropertyMetadata overlayTypeMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - PolyLine[] polyLines?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type PrefillTabs record { - Checkbox[] checkboxTabs?; - Date[] dateTabs?; - Email[] emailTabs?; - Number[] numberTabs?; - RadioGroup[] radioGroupTabs?; - SenderCompany[] senderCompanyTabs?; - SenderName[] senderNameTabs?; - Ssn[] ssnTabs?; - TabGroup[] tabGroups?; - Text[] textTabs?; - Zip[] zipTabs?; -}; - - -type RadioGroup record { - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - string groupName?; - PropertyMetadata groupNameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - Radio[] radios?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata tooltipMetadata?; - string value?; - PropertyMetadata valueMetadata?; -}; - - -type SignerAttachment record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string handDrawRequired?; - string height?; - PropertyMetadata heightMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string optional?; - PropertyMetadata optionalMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string scaleValue?; - PropertyMetadata scaleValueMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type SignHere record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string handDrawRequired?; - string height?; - PropertyMetadata heightMetadata?; - string isSealSignTab?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string optional?; - PropertyMetadata optionalMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string scaleValue?; - PropertyMetadata scaleValueMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - Stamp stamp?; - string stampType?; - PropertyMetadata stampTypeMetadata?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type SmartSection record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - boolean caseSensitive?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - SmartSectionDisplaySettings displaySettings?; - string documentId?; - PropertyMetadata documentIdMetadata?; - string endAnchor?; - SmartSectionAnchorPosition endPosition?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string overlayType?; - PropertyMetadata overlayTypeMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - boolean removeEndAnchor?; - boolean removeStartAnchor?; - string shared?; - PropertyMetadata sharedMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string startAnchor?; - SmartSectionAnchorPosition startPosition?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Ssn record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type TabGroup record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string groupLabel?; - PropertyMetadata groupLabelMetadata?; - string groupRule?; - PropertyMetadata groupRuleMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string maximumAllowed?; - PropertyMetadata maximumAllowedMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string minimumRequired?; - PropertyMetadata minimumRequiredMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabScope?; - PropertyMetadata tabScopeMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Text record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string formula?; - PropertyMetadata formulaMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Title record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type View record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string buttonText?; - PropertyMetadata buttonTextMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requiredRead?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Zip record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string concealValueOnDocument?; - PropertyMetadata concealValueOnDocumentMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string disableAutoSize?; - PropertyMetadata disableAutoSizeMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - string locked?; - PropertyMetadata lockedMetadata?; - string maxLength?; - PropertyMetadata maxLengthMetadata?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string originalValue?; - PropertyMetadata originalValueMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - string requireAll?; - PropertyMetadata requireAllMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string requireInitialOnSharedChange?; - PropertyMetadata requireInitialOnSharedChangeMetadata?; - string senderRequired?; - PropertyMetadata senderRequiredMetadata?; - string shared?; - PropertyMetadata sharedMetadata?; - string shareToRecipients?; - PropertyMetadata shareToRecipientsMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string useDash4?; - PropertyMetadata useDash4Metadata?; - string validationMessage?; - PropertyMetadata validationMessageMetadata?; - string validationPattern?; - PropertyMetadata validationPatternMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type ConsentDetails record { - string consentKey?; - string deliveryMethod?; - string signerConsentStatus?; -}; - - -type DocumentVisibility record { - string documentId?; - ErrorDetails errorDetails?; - string recipientId?; - string rights?; - string visible?; -}; - - -type IdCheckInformationInput record { - AddressInformationInput addressInformationInput?; - DobInformationInput dobInformationInput?; - Ssn4InformationInput ssn4InformationInput?; - Ssn9InformationInput ssn9InformationInput?; -}; - - -type RecipientIdentityVerification record { - RecipientIdentityInputOption[] inputOptions?; - string workflowId?; - PropertyMetadata workflowIdMetadata?; - string workflowLabel?; -}; - - -type RecipientPhoneAuthentication record { - string recipMayProvideNumber?; - PropertyMetadata recipMayProvideNumberMetadata?; - string recordVoicePrint?; - PropertyMetadata recordVoicePrintMetadata?; - string[] senderProvidedNumbers?; - PropertyMetadata senderProvidedNumbersMetadata?; - string validateRecipProvidedNumber?; - PropertyMetadata validateRecipProvidedNumberMetadata?; -}; - - -type RecipientAttachment record { - string attachmentId?; - string attachmentType?; - string data?; - string label?; - string name?; - string remoteUrl?; -}; - - -type AuthenticationStatus record { - EventResult accessCodeResult?; - EventResult ageVerifyResult?; - EventResult anySocialIDResult?; - EventResult facebookResult?; - EventResult googleResult?; - EventResult identityVerificationResult?; - EventResult idLookupResult?; - EventResult idQuestionsResult?; - EventResult linkedinResult?; - EventResult liveIDResult?; - EventResult ofacResult?; - EventResult openIDResult?; - EventResult phoneAuthResult?; - EventResult salesforceResult?; - EventResult signatureProviderResult?; - EventResult smsAuthResult?; - EventResult sTANPinResult?; - EventResult twitterResult?; - EventResult yahooResult?; -}; - - -type FeatureAvailableMetadata record { - string availabilty?; - string featureName?; -}; - - -type RecipientSMSAuthentication record { - string[] senderProvidedNumbers?; - PropertyMetadata senderProvidedNumbersMetadata?; -}; - - -type SocialAuthentication record { - string authentication?; -}; - - -type RecipientProofFile record { - string hasIdentityAttempts?; - string isInProofFile?; -}; - - -type NotaryHost record { - string accessCode?; - PropertyMetadata accessCodeMetadata?; - string addAccessCodeToEmail?; - string allowSystemOverrideForLockedRecipient?; - string autoRespondedReason?; - string bulkSendV2Recipient?; - string clientUserId?; - string completedCount?; - string[] customFields?; - string declinedDateTime?; - string declinedReason?; - string deliveredDateTime?; - string deliveryMethod?; - PropertyMetadata deliveryMethodMetadata?; - string designatorId?; - string designatorIdGuid?; - DocumentVisibility[] documentVisibility?; - string email?; - PropertyMetadata emailMetadata?; - RecipientEmailNotification emailNotification?; - string embeddedRecipientStartURL?; - ErrorDetails errorDetails?; - string faxNumber?; - PropertyMetadata faxNumberMetadata?; - string hostRecipientId?; - string idCheckConfigurationName?; - PropertyMetadata idCheckConfigurationNameMetadata?; - IdCheckInformationInput idCheckInformationInput?; - RecipientIdentityVerification identityVerification?; - string inheritEmailNotificationConfiguration?; - string lockedRecipientPhoneAuthEditable?; - string lockedRecipientSmsEditable?; - string name?; - PropertyMetadata nameMetadata?; - string note?; - PropertyMetadata noteMetadata?; - RecipientPhoneAuthentication phoneAuthentication?; - RecipientAttachment[] recipientAttachments?; - AuthenticationStatus recipientAuthenticationStatus?; - FeatureAvailableMetadata[] recipientFeatureMetadata?; - string recipientId?; - string recipientIdGuid?; - string recipientType?; - PropertyMetadata recipientTypeMetadata?; - string requireIdLookup?; - PropertyMetadata requireIdLookupMetadata?; - string roleName?; - string routingOrder?; - PropertyMetadata routingOrderMetadata?; - string sentDateTime?; - string signedDateTime?; - string signingGroupId?; - PropertyMetadata signingGroupIdMetadata?; - string signingGroupName?; - UserInfo[] signingGroupUsers?; - RecipientSMSAuthentication smsAuthentication?; - SocialAuthentication[] socialAuthentications?; - string status?; - string statusCode?; - string suppressEmails?; - EnvelopeRecipientTabs tabs?; - string templateLocked?; - string templateRequired?; - string totalTabCount?; - string userId?; -}; - - -type OfflineAttributes record { - string accountEsignId?; - string deviceModel?; - string deviceName?; - string gpsLatitude?; - string gpsLongitude?; - string offlineSigningHash?; -}; - - -type RecipientSignatureInformation record { - string fontStyle?; - string signatureInitials?; - string signatureName?; -}; - - -type DelegationInfo record { - string Email?; - string Name?; - string UserAuthorizationId?; - string UserId?; -}; - - -type RecipientSignatureProviderOptions record { - string cpfNumber?; - PropertyMetadata cpfNumberMetadata?; - string oneTimePassword?; - PropertyMetadata oneTimePasswordMetadata?; - string signerRole?; - PropertyMetadata signerRoleMetadata?; - string sms?; - PropertyMetadata smsMetadata?; -}; - - -type EnvelopeDelayRule record { - string delay?; - string resumeDate?; -}; - - -type DelayedRouting record { - string resumeDate?; - EnvelopeDelayRule[] rules?; - string status?; -}; - - -type RecipientRouting record { - RecipientRules rules?; -}; - - -type DocumentHtmlDisplaySettings record { - string cellStyle?; - DocumentHtmlCollapsibleDisplaySettings collapsibleSettings?; - string display?; - string displayLabel?; - ballerina/lang.int:0.0.0:Signed32 displayOrder?; - ballerina/lang.int:0.0.0:Signed32 displayPageNumber?; - boolean hideLabelWhenOpened?; - string inlineOuterStyle?; - string labelWhenOpened?; - string preLabel?; - boolean scrollToTopWhenOpened?; - string tableStyle?; -}; - - -type LocalePolicyTab record { - string addressFormat?; - string calendarType?; - string cultureName?; - string currencyCode?; - string currencyNegativeFormat?; - string currencyPositiveFormat?; - string customDateFormat?; - string customTimeFormat?; - string dateFormat?; - string initialFormat?; - string nameFormat?; - string timeFormat?; - string timeZone?; - string useLongCurrencyFormat?; -}; - - -type MergeField record { - string allowSenderToEdit?; - PropertyMetadata allowSenderToEditMetadata?; - string configurationType?; - PropertyMetadata configurationTypeMetadata?; - string path?; - PathExtendedElement[] pathExtended?; - PropertyMetadata pathExtendedMetadata?; - PropertyMetadata pathMetadata?; - string row?; - PropertyMetadata rowMetadata?; - string writeBack?; - PropertyMetadata writeBackMetadata?; -}; - - -type SmartContractInformation record { - string code?; - string uri?; -}; - - -type Comment record { - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - Comment[] comments?; - ballerina/lang.int:0.0.0:Signed32 count?; - string endTimetoken?; - string startTimetoken?; -}; - - -type PaymentDetails record { - string[] allowedPaymentMethods?; - string chargeId?; - string currencyCode?; - PropertyMetadata currencyCodeMetadata?; - string customerId?; - string customMetadata?; - boolean customMetadataRequired?; - string gatewayAccountId?; - PropertyMetadata gatewayAccountIdMetadata?; - string gatewayDisplayName?; - string gatewayName?; - PaymentLineItem[] lineItems?; - string paymentOption?; - string paymentSourceId?; - PaymentSignerValues signerValues?; - string status?; - string subGatewayName?; - Money total?; -}; - - -type ListItem record { - string selected?; - PropertyMetadata selectedMetadata?; - string text?; - PropertyMetadata textMetadata?; - string value?; - PropertyMetadata valueMetadata?; -}; - - -type GraphicsContext record { - string fillColor?; - string lineColor?; - string lineWeight?; -}; - - -type PolyLine record { - string x1?; - string x2?; - string y1?; - string y2?; -}; - - -type SenderCompany record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type SenderName record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - string conditionalParentLabel?; - PropertyMetadata conditionalParentLabelMetadata?; - string conditionalParentValue?; - PropertyMetadata conditionalParentValueMetadata?; - string customTabId?; - PropertyMetadata customTabIdMetadata?; - string documentId?; - PropertyMetadata documentIdMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string formOrder?; - PropertyMetadata formOrderMetadata?; - string formPageLabel?; - PropertyMetadata formPageLabelMetadata?; - string formPageNumber?; - PropertyMetadata formPageNumberMetadata?; - string height?; - PropertyMetadata heightMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - LocalePolicyTab localePolicy?; - MergeField mergeField?; - string mergeFieldXml?; - string name?; - PropertyMetadata nameMetadata?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string recipientId?; - string recipientIdGuid?; - PropertyMetadata recipientIdGuidMetadata?; - PropertyMetadata recipientIdMetadata?; - SmartContractInformation smartContractInformation?; - string 'source?; - string status?; - PropertyMetadata statusMetadata?; - string[] tabGroupLabels?; - PropertyMetadata tabGroupLabelsMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabLabel?; - PropertyMetadata tabLabelMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string tabType?; - PropertyMetadata tabTypeMetadata?; - string templateLocked?; - PropertyMetadata templateLockedMetadata?; - string templateRequired?; - PropertyMetadata templateRequiredMetadata?; - string tooltip?; - PropertyMetadata toolTipMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string width?; - PropertyMetadata widthMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Radio record { - string anchorAllowWhiteSpaceInCharacters?; - PropertyMetadata anchorAllowWhiteSpaceInCharactersMetadata?; - string anchorCaseSensitive?; - PropertyMetadata anchorCaseSensitiveMetadata?; - string anchorHorizontalAlignment?; - PropertyMetadata anchorHorizontalAlignmentMetadata?; - string anchorIgnoreIfNotPresent?; - PropertyMetadata anchorIgnoreIfNotPresentMetadata?; - string anchorMatchWholeWord?; - PropertyMetadata anchorMatchWholeWordMetadata?; - string anchorString?; - PropertyMetadata anchorStringMetadata?; - string anchorTabProcessorVersion?; - PropertyMetadata anchorTabProcessorVersionMetadata?; - string anchorUnits?; - PropertyMetadata anchorUnitsMetadata?; - string anchorXOffset?; - PropertyMetadata anchorXOffsetMetadata?; - string anchorYOffset?; - PropertyMetadata anchorYOffsetMetadata?; - string bold?; - PropertyMetadata boldMetadata?; - string caption?; - PropertyMetadata captionMetadata?; - ErrorDetails errorDetails?; - string font?; - string fontColor?; - PropertyMetadata fontColorMetadata?; - PropertyMetadata fontMetadata?; - string fontSize?; - PropertyMetadata fontSizeMetadata?; - string italic?; - PropertyMetadata italicMetadata?; - string locked?; - PropertyMetadata lockedMetadata?; - string mergeFieldXml?; - string pageNumber?; - PropertyMetadata pageNumberMetadata?; - string required?; - PropertyMetadata requiredMetadata?; - string selected?; - PropertyMetadata selectedMetadata?; - string status?; - PropertyMetadata statusMetadata?; - string tabId?; - PropertyMetadata tabIdMetadata?; - string tabOrder?; - PropertyMetadata tabOrderMetadata?; - string underline?; - PropertyMetadata underlineMetadata?; - string value?; - PropertyMetadata valueMetadata?; - string xPosition?; - PropertyMetadata xPositionMetadata?; - string yPosition?; - PropertyMetadata yPositionMetadata?; -}; - - -type Stamp record { - string adoptedDateTime?; - string createdDateTime?; - string customField?; - DateStampProperties dateStampProperties?; - string disallowUserResizeStamp?; - ErrorDetails errorDetails?; - string externalID?; - string imageBase64?; - string imageType?; - string lastModifiedDateTime?; - string phoneticName?; - string signatureName?; - string stampFormat?; - string stampImageUri?; - string stampSizeMM?; - string status?; -}; - - -type SmartSectionDisplaySettings record { - string cellStyle?; - SmartSectionCollapsibleDisplaySettings collapsibleSettings?; - string display?; - string displayLabel?; - ballerina/lang.int:0.0.0:Signed32 displayOrder?; - ballerina/lang.int:0.0.0:Signed32 displayPageNumber?; - boolean hideLabelWhenOpened?; - string inlineOuterStyle?; - string labelWhenOpened?; - string preLabel?; - boolean scrollToTopWhenOpened?; - string tableStyle?; -}; - - -type SmartSectionAnchorPosition record { - ballerina/lang.int:0.0.0:Signed32 pageNumber?; - decimal xPosition?; - decimal yPosition?; -}; - - -type AddressInformationInput record { - AddressInformation addressInformation?; - string displayLevelCode?; - string receiveInResponse?; -}; - - -type DobInformationInput record { - string dateOfBirth?; - string displayLevelCode?; - string receiveInResponse?; -}; - - -type Ssn4InformationInput record { - string displayLevelCode?; - string receiveInResponse?; - string ssn4?; -}; - - -type Ssn9InformationInput record { - string displayLevelCode?; - string ssn9?; -}; - - -type RecipientIdentityInputOption record { - string name?; - RecipientIdentityPhoneNumber[] phoneNumberList?; - string valueType?; -}; - - -type EventResult record { - string eventTimestamp?; - string failureDescription?; - string status?; - string vendorFailureStatusCode?; -}; - - -type RecipientRules record { - ConditionalRecipientRule[] conditionalRecipients?; -}; - - -type DocumentHtmlCollapsibleDisplaySettings record { - string arrowClosed?; - string arrowColor?; - string arrowLocation?; - string arrowOpen?; - string arrowSize?; - string arrowStyle?; - string containerStyle?; - string labelStyle?; - boolean onlyArrowIsClickable?; - string outerLabelAndArrowStyle?; -}; - - -type PathExtendedElement record { - string name?; - string 'type?; - string typeName?; -}; - - -type PaymentLineItem record { - string amountReference?; - string description?; - string itemCode?; - string name?; -}; - - -type PaymentSignerValues record { - string paymentOption?; -}; - - -type Money record { - string amountInBaseUnit?; - string currency?; - string displayAmount?; -}; - - -type DateStampProperties record { - string dateAreaHeight?; - string dateAreaWidth?; - string dateAreaX?; - string dateAreaY?; -}; - - -type SmartSectionCollapsibleDisplaySettings record { - string arrowClosed?; - string arrowColor?; - string arrowLocation?; - string arrowOpen?; - string arrowSize?; - string arrowStyle?; - string containerStyle?; - string labelStyle?; - boolean onlyArrowIsClickable?; - string outerLabelAndArrowStyle?; -}; - - -type AddressInformation record { - string address1?; - string address2?; - string city?; - string country?; - string fax?; - string phone?; - string postalCode?; - string stateOrProvince?; - string zipPlus4?; -}; - - -type RecipientIdentityPhoneNumber record { - string countryCode?; - string countryCodeLock?; - PropertyMetadata countryCodeMetadata?; - string extension?; - PropertyMetadata extensionMetadata?; - string number?; - PropertyMetadata numberMetadata?; -}; - - -type ConditionalRecipientRule record { - ConditionalRecipientRuleCondition[] conditions?; - string 'order?; - RecipientGroup recipientGroup?; - string recipientId?; -}; - - -type ConditionalRecipientRuleCondition record { - ConditionalRecipientRuleFilter[] filters?; - string 'order?; - string recipientLabel?; -}; - - -type RecipientGroup record { - string groupMessage?; - string groupName?; - RecipientOption[] recipients?; -}; - - -type ConditionalRecipientRuleFilter record { - string operator?; - string recipientId?; - string scope?; - string tabId?; - string tabLabel?; - string tabType?; - string value?; -}; - - -type RecipientOption record { - string email?; - string name?; - string recipientLabel?; - string roleName?; - string signingGroupId?; -}; - -// --- Client --- - -# The DocuSign REST API provides you with a powerful, convenient, and simple Web services API for interacting with DocuSign. -client class Client { - function init(string serviceUrl, http:HttpVersion httpVersion = http:HTTP_2_0, ClientHttp1Settings http1Settings = {}, http:ClientHttp2Settings http2Settings = {}, decimal timeout = 60, string forwarded = "disable", http:PoolConfiguration poolConfig = {}, http:CacheConfig cache = {}, http:Compression compression = AUTO, http:CircuitBreakerConfig circuitBreaker = {}, http:RetryConfig retryConfig = {}, http:ResponseLimitConfigs responseLimits = {}, http:ClientSecureSocket secureSocket = {}, http:ProxyConfig proxy = {}, boolean validation = true, http:CredentialsConfig|http:BearerTokenConfig|http:JwtIssuerConfig|http:OAuth2ClientCredentialsGrantConfig|http:OAuth2PasswordGrantConfig|http:OAuth2RefreshTokenGrantConfig|http:OAuth2JwtBearerGrantConfig|() auth = (), ConnectionConfig config) returns error?; // Special Agent Note: HttpVersion, ClientHttp2Settings, PoolConfiguration, CacheConfig, Compression, CircuitBreakerConfig, RetryConfig, ResponseLimitConfigs, ClientSecureSocket, ProxyConfig, CredentialsConfig, BearerTokenConfig, JwtIssuerConfig, OAuth2ClientCredentialsGrantConfig, OAuth2PasswordGrantConfig, OAuth2RefreshTokenGrantConfig, OAuth2JwtBearerGrantConfig FROM ballerina/http package - - # Creates an envelope. - # - resource function post accounts/[string accountId]/envelopes(EnvelopeDefinition payload, string|() cdse_mode = (), string|() change_routing_order = (), string|() completed_documents_only = (), string|() merge_roles_on_draft = ()) returns EnvelopeSummary|error; - - # Search for specific sets of envelopes by using search filters. - # - resource function get accounts/[string accountId]/envelopes(string|() ac_status = (), string|() block = (), string|() cdse_mode = (), string|() continuation_token = (), string|() count = (), string|() custom_field = (), string|() email = (), string|() envelope_ids = (), string|() exclude = (), string|() folder_ids = (), string|() folder_types = (), string|() from_date = (), string|() from_to_status = (), string|() include = (), string|() include_purge_information = (), string|() intersecting_folder_ids = (), string|() last_queried_date = (), string|() 'order = (), string|() order_by = (), string|() powerformids = (), string|() query_budget = (), string|() requester_date_format = (), string|() search_mode = (), string|() search_text = (), string|() start_position = (), string|() status = (), string|() to_date = (), string|() transaction_ids = (), string|() user_filter = (), string|() user_id = (), string|() user_name = ()) returns EnvelopesInformation|error; - - # Gets the status of a single envelope. - # - resource function get accounts/[string accountId]/envelopes/[string envelopeId](string|() advanced_update = (), string|() include = ()) returns Envelope|error; -} - -// ============================================================ -// Library: ballerina/http -// This module provides APIs for connecting and interacting with HTTP and HTTP2 endpoints. It facilitates two types of network entry points as the `Client` and `Listener`. -// ============================================================ -import ballerina/http; - -// --- Types --- - -# Represents token for Bearer token authentication. -# - -type BearerTokenConfig record { - # Bearer token for authentication - string token; -}; - -# Defines the supported HTTP protocols. -enum HttpVersion { - HTTP_2_0, - HTTP_1_1, - HTTP_1_0 -} - -# Provides settings related to HTTP/2 protocol. -# - -type ClientHttp2Settings record { - # Configuration to enable HTTP/2 prior knowledge - boolean http2PriorKnowledge?; - # Configuration to change the initial window size - int http2InitialWindowSize?; -}; - -# Configurations for managing HTTP client connection pool. -# - -type PoolConfiguration record { - # Max active connections per route(host:port). Default value is -1 which indicates unlimited. - int maxActiveConnections?; - # Maximum number of idle connections allowed per pool. - int maxIdleConnections?; - # Maximum amount of time (in seconds), the client should wait for an idle connection before it sends an error when the pool is exhausted - decimal waitTime?; - # Maximum active streams per connection. This only applies to HTTP/2. Default value is 100 - int maxActiveStreamsPerConnection?; - # Minimum evictable time for an idle connection in seconds. Default value is 5 minutes - decimal minEvictableIdleTime?; - # Time between eviction runs in seconds. Default value is 30 seconds - decimal timeBetweenEvictionRuns?; - # Minimum time in seconds for a connection to be kept open which has received a GOAWAY. -This only applies for HTTP/2. Default value is 5 minutes. If the value is set to -1, -the connection will be closed after all in-flight streams are completed - decimal minIdleTimeInStaleState?; - # Time between the connection stale eviction runs in seconds. This only applies for HTTP/2. -Default value is 30 seconds - decimal timeBetweenStaleEviction?; -}; - -# Provides a set of configurations for controlling the caching behaviour of the endpoint. -# - -type CacheConfig record { - # Specifies whether HTTP caching is enabled. Caching is enabled by default. - boolean enabled?; - # Specifies whether the HTTP caching layer should behave as a public cache or a private cache - boolean isShared?; - # The capacity of the cache - int capacity?; - # The fraction of entries to be removed when the cache is full. The value should be -between 0 (exclusive) and 1 (inclusive). - float evictionFactor?; - # Gives the user some control over the caching behaviour. By default, this is set to -`CACHE_CONTROL_AND_VALIDATORS`. The default behaviour is to allow caching only when the `cache-control` -header and either the `etag` or `last-modified` header are present. - CachingPolicy policy?; -}; - -# Options to compress using gzip or deflate. -# -# `AUTO`: When service behaves as a HTTP gateway inbound request/response accept-encoding option is set as the -# outbound request/response accept-encoding/content-encoding option -# `ALWAYS`: Always set accept-encoding/content-encoding in outbound request/response -# `NEVER`: Never set accept-encoding/content-encoding header in outbound request/response -type Compression; - -# Provides a set of configurations for controlling the behaviour of the Circuit Breaker. - -type CircuitBreakerConfig record { - # The `http:RollingWindow` options of the `CircuitBreaker` - RollingWindow rollingWindow?; - # The threshold for request failures. When this threshold exceeds, the circuit trips. The threshold should be a -value between 0 and 1 - float failureThreshold?; - # The time period (in seconds) to wait before attempting to make another request to the upstream service - decimal resetTime?; - # Array of HTTP response status codes which are considered as failures - int[] statusCodes?; -}; - -# Provides configurations for controlling the retrying behavior in failure scenarios. -# - -type RetryConfig record { - # Number of retry attempts before giving up - int count?; - # Retry interval in seconds - decimal interval?; - # Multiplier, which increases the retry interval exponentially. - float backOffFactor?; - # Maximum time of the retry interval in seconds - decimal maxWaitInterval?; - # HTTP response status codes which are considered as failures - int[] statusCodes?; -}; - -# Provides inbound response status line, total header and entity body size threshold configurations. -# - -type ResponseLimitConfigs record { - # Maximum allowed length for response status line(`HTTP/1.1 200 OK`). Exceeding this limit will -result in a `ClientError` - int maxStatusLineLength?; - # Maximum allowed size for headers. Exceeding this limit will result in a `ClientError` - int maxHeaderSize?; - # Maximum allowed size for the entity body. By default it is set to -1 which means there is no -restriction `maxEntityBodySize`, On the Exceeding this limit will result in a `ClientError` - int maxEntityBodySize?; -}; - -# Provides configurations for facilitating secure communication with a remote HTTP endpoint. -# - -type ClientSecureSocket record { - # Enable SSL validation - boolean enable?; - # Configurations associated with `crypto:TrustStore` or single certificate file that the client trusts - crypto:TrustStore|string cert?; // Special Agent Note: TrustStore FROM ballerina/crypto package - # Configurations associated with `crypto:KeyStore` or combination of certificate and private key of the client - crypto:KeyStore|CertKey key?; // Special Agent Note: KeyStore FROM ballerina/crypto package - # SSL/TLS protocol related options - record {|ballerina/http:2.16.0:Protocol name; string[] versions;|} protocol?; - # Certificate validation against OCSP_CRL, OCSP_STAPLING related options - record {|ballerina/http:2.16.0:CertValidationType 'type; int cacheSize; int cacheValidityPeriod;|} certValidation?; - # List of ciphers to be used -eg: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - string[] ciphers?; - # Enable/disable host name verification - boolean verifyHostName?; - # Enable/disable new SSL session creation - boolean shareSession?; - # SSL handshake time out - decimal handshakeTimeout?; - # SSL session time out - decimal sessionTimeout?; - # Server name indication(SNI) to be used. If this is not present, hostname from the target URL will be used - string serverName?; -}; - -# Proxy server configurations to be used with the HTTP client endpoint. -# - -type ProxyConfig record { - # Host name of the proxy server - string host?; - # Proxy server port - int port?; - # Proxy server username - string userName?; - # proxy server password - string password?; -}; - -# Represents credentials for Basic Auth authentication. - -type CredentialsConfig record { - string username; - string password; -}; - -# Represents JWT issuer configurations for JWT authentication. - -type JwtIssuerConfig record { - string issuer?; - string username?; - string|string[] audience?; - string jwtId?; - string keyId?; - map customClaims?; - decimal expTime?; - jwt:IssuerSignatureConfig signatureConfig?; // Special Agent Note: IssuerSignatureConfig FROM ballerina/jwt package -}; - -# Represents OAuth2 client credentials grant configurations for OAuth2 authentication. - -type OAuth2ClientCredentialsGrantConfig record { - string tokenUrl; - string clientId; - string clientSecret; - string|string[] scopes?; - decimal defaultTokenExpTime?; - decimal clockSkew?; - map optionalParams?; - oauth2:CredentialBearer credentialBearer?; // Special Agent Note: CredentialBearer FROM ballerina/oauth2 package - oauth2:ClientConfiguration clientConfig?; // Special Agent Note: ClientConfiguration FROM ballerina/oauth2 package -}; - -# Represents OAuth2 password grant configurations for OAuth2 authentication. - -type OAuth2PasswordGrantConfig record { - string tokenUrl; - string username; - string password; - string clientId?; - string clientSecret?; - string|string[] scopes?; - oauth2:RefreshConfig|"INFER_REFRESH_CONFIG" refreshConfig?; // Special Agent Note: RefreshConfig FROM ballerina/oauth2 package - decimal defaultTokenExpTime?; - decimal clockSkew?; - map optionalParams?; - oauth2:CredentialBearer credentialBearer?; // Special Agent Note: CredentialBearer FROM ballerina/oauth2 package - oauth2:ClientConfiguration clientConfig?; // Special Agent Note: ClientConfiguration FROM ballerina/oauth2 package -}; - -# Represents OAuth2 refresh token grant configurations for OAuth2 authentication. - -type OAuth2RefreshTokenGrantConfig record { - string refreshUrl; - string refreshToken; - string clientId; - string clientSecret; - string|string[] scopes?; - decimal defaultTokenExpTime?; - decimal clockSkew?; - map optionalParams?; - oauth2:CredentialBearer credentialBearer?; // Special Agent Note: CredentialBearer FROM ballerina/oauth2 package - oauth2:ClientConfiguration clientConfig?; // Special Agent Note: ClientConfiguration FROM ballerina/oauth2 package -}; - -# Represents OAuth2 JWT bearer grant configurations for OAuth2 authentication. - -type OAuth2JwtBearerGrantConfig record { - string tokenUrl; - string assertion; - string clientId?; - string clientSecret?; - string|string[] scopes?; - decimal defaultTokenExpTime?; - decimal clockSkew?; - map optionalParams?; - oauth2:CredentialBearer credentialBearer?; // Special Agent Note: CredentialBearer FROM ballerina/oauth2 package - oauth2:ClientConfiguration clientConfig?; // Special Agent Note: ClientConfiguration FROM ballerina/oauth2 package -}; - -# Defines the possible values for the keep-alive configuration in service and client endpoints. -type KeepAlive; - -# Defines the possible values for the chunking configuration in HTTP services and clients. -# -# `AUTO`: If the payload is less than 8KB, content-length header is set in the outbound request/response, -# otherwise chunking header is set in the outbound request/response -# `ALWAYS`: Always set chunking header in the response -# `NEVER`: Never set the chunking header even if the payload is larger than 8KB in the outbound request/response -type Chunking; - -// --- Service --- - -// --- Service (generic) --- -// Listener: Listener(int port) -// Instructions: -# Service writing instructions - -- HTTP Service always requires a http listener to be attatched to it. Always declare the listener in the module level as variable and then use it in the service declaration. (eg; listener http:Listener ep = check new http:Listener(8080);) -- Only can contain resource functions inside the service. -- Path paramters must be specified in the resource function path. (eg: resource function get v1/user/[int userId]/profile()) -- In the resource function parameters, you can specify query parameters, headers and body as parameters. - - Body - use `@http:Payload` annotation to specify the body parameter. Note: The annotation is optional if there is only one parameter and if the type is a record. - - Query parameters - use `@http:Query` annotation to specify query parameters. - - Headers - use `@http:Header` annotation to specify header parameters. - -``` -import ballerina/http; - -listener http:Listener ep = check new http:Listener(8080); - -type Person record { - string name; - int age; -}; - -service /v1 on ep { - - // Prefer types as return type. can be anydata such as string, json, record, etc. - resource function get foo() returns Person|error { - return { name: "John", age: 30}; - } - - // Query parameters - resource function get bar(@http:Query string id) returns Person|error { - return { name: "John", age: 30}; - } - - // Path parameters - resource function get customers/[int id]/accounts() returns Person|error { - return { name: "John", age: 30}; - } - - // Body with data binding and header parameters - resource function post customers/[int id]/accounts(@http:Payload Person account, @http:Header string customHeader) returns Person|error { - return account; - } -} - -``` - - -# Client writing instructions - -- Always declare clients in module level as final variables. -- Use direct data binding to bind the response to a type whenever possible. -- Only use `http:Response` type as the return type when you need to access headers or status code of the response. - -``` -import ballerina/http; - -listener http:Listener ep = check new http:Listener(8080); - -// Always declare clients in module level as final variables. -final http:Client cl = check new("http://localhost:9090"); - -type Person record { - string name; - int age; -}; - -service /v1 on ep { - resource function get user() returns Person|error { - // If only the body of the response is needed use direct data binding. - Person p = check cl->get("/foo/bar"); - - // If the full response is needed use http:Response - http:Response res = check cl->get("/foo/bar"); - json payload = check res.getJsonPayload(); - Person p1 = check payload.cloneWithType(); - - - // get a specific header - string contentTypeHeader = check res.getHeader("Content-Type"); - - // get status code - int statusCode = res.statusCode; - - // send a http request with query params and headers. Note both these are optional. - Person p3 = check cl->get("/foo/bar?queryParam1=value&queryParam2=val2", headers = { - "x-Custom-Header": "custom-value" - }); - - return p1; - } -} -``` - -// ============================================================ -// Library: ballerinax/client.config -// Contains client config utils and Ballerina types for ballerinax connectors. -// ============================================================ -import ballerinax/client.config; - -// --- Types --- - -# Represents OAuth2 refresh token grant configurations for OAuth2 authentication. -# - -type OAuth2RefreshTokenGrantConfig record { - # Refresh token URL of the token endpoint - string refreshUrl; - # Refresh token for the token endpoint - string refreshToken; - # Client ID of the client authentication - string clientId; - # Client secret of the client authentication - string clientSecret; - # Scope(s) of the access request - string[] scopes?; - # Expiration time (in seconds) of the tokens if the token endpoint response does not contain an `expires_in` field - decimal defaultTokenExpTime?; - # Clock skew (in seconds) that can be used to avoid token validation failures due to clock synchronization problems - decimal clockSkew?; - # Map of the optional parameters used for the token endpoint - map optionalParams?; - # Bearer of the authentication credentials, which is sent to the token endpoint - CredentialBearer credentialBearer?; -}; - -# Represents the data structure, which is used to configure the OAuth2 password grant type. -# - -type OAuth2PasswordGrantConfig record { - # Token URL of the token endpoint - string tokenUrl; - # Username for the password grant type - string username; - # Password for the password grant type - string password; - # Client ID of the client authentication - string clientId?; - # Client secret of the client authentication - string clientSecret?; - # Scope(s) of the access request - string[] scopes?; - # Configurations for refreshing the access token - record {|string refreshUrl; string[] scopes?; map optionalParams?; ballerinax/client.config:1.0.1:CredentialBearer credentialBearer?;|} refreshConfig?; - # Expiration time (in seconds) of the tokens if the token endpoint response does not contain an `expires_in` field - decimal defaultTokenExpTime?; - # Clock skew (in seconds) that can be used to avoid token validation failures due to clock synchronization problems - decimal clockSkew?; - # Map of the optional parameters used for the token endpoint - map optionalParams?; - # Bearer of the authentication credentials, which is sent to the token endpoint - CredentialBearer credentialBearer?; -}; - -# Represents the data structure, which is used to configure the OAuth2 client credentials grant type. -# - -type OAuth2ClientCredentialsGrantConfig record { - # Token URL of the token endpoint - string tokenUrl; - # Client ID of the client authentication - string clientId; - # Client secret of the client authentication - string clientSecret; - # Scope(s) of the access request - string[] scopes?; - # Expiration time (in seconds) of the tokens if the token endpoint response does not contain an `expires_in` field - decimal defaultTokenExpTime?; - # Clock skew (in seconds) that can be used to avoid token validation failures due to clock synchronization problems - decimal clockSkew?; - # Map of the optional parameters used for the token endpoint - map optionalParams?; - # Bearer of the authentication credentials, which is sent to the token endpoint - CredentialBearer credentialBearer?; -}; - -# Provides settings related to HTTP/1.x protocol. -# - -type ClientHttp1Settings record { - # Specifies whether to reuse a connection for multiple requests - KeepAlive keepAlive?; - # The chunking behaviour of the request - Chunking chunking?; - # Proxy server related options - ProxyConfig proxy?; -}; diff --git a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/sample-libraries.json b/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/sample-libraries.json deleted file mode 100644 index 7e4100b2c4..0000000000 --- a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/resources/sample-libraries.json +++ /dev/null @@ -1,575 +0,0 @@ -[ - { - "name": "ballerina/http", - "description": "This module provides APIs for connecting and interacting with HTTP and HTTP2 endpoints.", - "typeDefs": [ - { - "name": "CacheConfig", - "description": "Provides a set of configurations for controlling the caching behaviour of the endpoint.\n", - "type": "Record", - "fields": [ - { - "name": "enabled", - "description": "Specifies whether HTTP caching is enabled. Caching is enabled by default.", - "type": { "name": "boolean" }, - "optional": true - }, - { - "name": "isShared", - "description": "Specifies whether the HTTP caching layer should behave as a public cache or a private cache", - "type": { "name": "boolean" }, - "optional": true - }, - { - "name": "capacity", - "description": "The capacity of the cache", - "type": { "name": "int" }, - "optional": true - }, - { - "name": "evictionFactor", - "description": "The fraction of entries to be removed when the cache is full. The value should be between 0 (exclusive) and 1 (inclusive).", - "type": { "name": "float" }, - "optional": true - }, - { - "name": "policy", - "description": "Gives the user some control over the caching behaviour. By default, this is set to CACHE_CONTROL_AND_VALIDATORS.", - "type": { - "name": "CachingPolicy", - "links": [{ "category": "internal", "recordName": "CachingPolicy" }] - }, - "optional": true - } - ] - }, - { - "name": "HttpVersion", - "description": "Defines the supported HTTP protocols.", - "type": "Enum", - "members": [ - { "name": "HTTP_2_0", "description": "Represents HTTP/2.0 protocol" }, - { "name": "HTTP_1_1", "description": "Represents HTTP/1.1 protocol" }, - { "name": "HTTP_1_0", "description": "Represents HTTP/1.0 protocol" } - ] - }, - { - "name": "Compression", - "description": "Options to compress using gzip or deflate.\nAUTO: When service behaves as a HTTP gateway...\nALWAYS: Always set accept-encoding...\nNEVER: Never set accept-encoding...", - "type": "Union", - "members": [ - { "name": "COMPRESSION_AUTO", "type": { "name": "string" } }, - { "name": "COMPRESSION_ALWAYS", "type": { "name": "string" } }, - { "name": "COMPRESSION_NEVER", "type": { "name": "string" } } - ] - }, - { - "name": "StatusCode", - "description": "Represents an HTTP status code type.", - "type": "Union", - "members": [] - }, - { - "name": "AUTH_HEADER", - "description": "Represents the Authorization header name.", - "type": "Constant", - "value": "Authorization", - "varType": { "name": "string" } - }, - { - "name": "DEFAULT_PORT", - "description": "Default HTTP listener port.", - "type": "Constant", - "value": "9090", - "varType": { "name": "int" } - }, - { - "name": "PersistentCookieHandler", - "description": "Provides persistence for cookies.", - "type": "Class", - "functions": [] - } - ], - "clients": [], - "functions": [ - { - "name": "authenticateResource", - "type": "Normal Function", - "description": "Uses for declarative auth design.", - "parameters": [ - { - "name": "serviceRef", - "description": "", - "type": { "name": "Service" } - }, - { - "name": "methodName", - "description": "", - "type": { "name": "string" } - }, - { - "name": "resourcePath", - "description": "", - "type": { "name": "string[]" } - } - ], - "return": { - "type": { "name": "()" } - } - } - ], - "services": [ - { - "type": "generic", - "instructions": "# Service writing instructions\n\n- HTTP Service always requires a http listener to be attached to it.", - "listener": { - "name": "Listener", - "parameters": [ - { - "name": "port", - "description": "Listening port of the HTTP service listener", - "type": { "name": "int" } - } - ] - } - } - ] - }, - { - "name": "ballerinax/salesforce", - "description": "Salesforce Sales Cloud is one of the leading Customer Relationship Management(CRM) solutions.", - "typeDefs": [ - { - "name": "ConnectionConfig", - "type": "Record", - "description": "", - "fields": [ - { "name": "baseUrl", "description": "", "type": { "name": "string" } }, - { - "name": "auth", - "description": "", - "type": { - "name": "BearerTokenConfig|OAuth2RefreshTokenGrantConfig|OAuth2PasswordGrantConfig|OAuth2ClientCredentialsGrantConfig", - "links": [ - { "category": "external", "recordName": "BearerTokenConfig", "libraryName": "ballerina/http" }, - { "category": "external", "recordName": "OAuth2RefreshTokenGrantConfig", "libraryName": "ballerina/http" } - ] - } - } - ] - } - ], - "clients": [ - { - "name": "Client", - "description": "Ballerina Salesforce connector provides the capability to access Salesforce REST API.", - "functions": [ - { - "name": "init", - "type": "Constructor", - "description": "Initializes the connector.", - "parameters": [ - { - "name": "config", - "description": "", - "type": { - "name": "ConnectionConfig", - "links": [{ "category": "internal", "recordName": "ConnectionConfig" }] - } - } - ], - "return": { - "description": "`salesforce:Error` on failure of initialization or else `()`", - "type": { "name": "error?" } - } - }, - { - "name": "query", - "type": "Remote Function", - "description": "Executes the specified SOQL query.\n", - "parameters": [ - { - "name": "soql", - "description": "SOQL query", - "type": { "name": "string" } - }, - { - "name": "returnType", - "description": "The payload, which is expected to be returned after data binding", - "type": { "name": "record {|anydata...;|}" }, - "optional": true, - "default": "record {|anydata...;|}" - } - ], - "return": { - "description": "`stream<{returnType}, error?>` if successful. Else, the occurred `error`", - "type": { "name": "stream|error" } - } - } - ] - } - ], - "functions": [], - "services": [ - { - "type": "fixed", - "listener": { - "name": "salesforce:Listener", - "parameters": [ - { - "name": "listenerConfig", - "description": "The configuration of Salesforce ASB listener.", - "type": { "name": "salesforce:ListenerConfig" }, - "default": "" - } - ] - }, - "methods": [ - { - "type": "remote", - "description": "The `onCreate` method is triggered when a new record create event is received from Salesforce.", - "parameters": [ - { - "name": "payload", - "description": "The information about the triggered event.", - "type": { "name": "salesforce:EventData" }, - "optional": false - } - ], - "return": { "type": { "name": "error?" } }, - "optional": false - }, - { - "type": "remote", - "description": "The `onUpdate` method is triggered when a new record update event is received from Salesforce.", - "parameters": [ - { - "name": "payload", - "description": "The information about the triggered event.", - "type": { "name": "salesforce:EventData" }, - "optional": false - } - ], - "return": { "type": { "name": "error?" } }, - "optional": false - }, - { - "type": "remote", - "description": "The `onDelete` method is triggered when a new record delete event is received from Salesforce.", - "parameters": [ - { - "name": "payload", - "description": "The information about the triggered event.", - "type": { "name": "salesforce:EventData" }, - "optional": false - } - ], - "return": { "type": { "name": "error?" } }, - "optional": true - } - ] - } - ] - }, - { - "name": "ballerina/io", - "description": "This module provides file read/write APIs and console print/read APIs.", - "typeDefs": [], - "clients": [], - "functions": [ - { - "name": "fileWriteBytes", - "type": "Normal Function", - "description": "Write a set of bytes to a file.", - "parameters": [ - { - "name": "path", - "description": "The path of the file", - "type": { "name": "string" } - }, - { - "name": "content", - "description": "Byte content to write", - "type": { "name": "byte[]" } - }, - { - "name": "option", - "description": "To indicate whether to overwrite or append the given content", - "type": { - "name": "FileWriteOption", - "links": [{ "category": "internal", "recordName": "FileWriteOption" }] - }, - "optional": true, - "default": "OVERWRITE" - } - ], - "return": { - "description": "An `io:Error` or else `()`", - "type": { - "name": "Error|()", - "links": [{ "category": "internal", "recordName": "Error" }] - } - } - } - ], - "services": [] - }, - { - "name": "ballerinax/docusign.dsesign", - "description": "DocuSign is a digital transaction management platform that enables users to securely sign, send, and manage documents electronically.", - "typeDefs": [ - { - "name": "ClientHttp1Settings", - "description": "", - "type": "Record", - "fields": [ - { - "name": "keepAlive", - "description": "", - "type": { - "name": "KeepAlive", - "links": [{ "category": "external", "recordName": "KeepAlive", "libraryName": "ballerina/http" }] - }, - "optional": true - }, - { - "name": "chunking", - "description": "", - "type": { - "name": "Chunking", - "links": [{ "category": "external", "recordName": "Chunking", "libraryName": "ballerina/http" }] - }, - "optional": true - }, - { - "name": "proxy", - "description": "", - "type": { - "name": "ProxyConfig", - "links": [{ "category": "internal", "recordName": "ProxyConfig" }] - }, - "optional": true - } - ] - } - ], - "clients": [ - { - "name": "Client", - "description": "The DocuSign REST API provides you with a powerful, convenient, and simple Web services API for interacting with DocuSign.", - "functions": [ - { - "name": "init", - "type": "Constructor", - "description": "Initializes the connector.", - "parameters": [ - { - "name": "config", - "description": "", - "type": { - "name": "ConnectionConfig", - "links": [{ "category": "internal", "recordName": "ConnectionConfig" }] - } - } - ], - "return": { - "type": { "name": "error?" } - } - }, - { - "type": "Resource Function", - "description": "Creates an envelope.\n", - "accessor": "post", - "paths": [ - "accounts", - { "name": "accountId", "type": "string" }, - "envelopes" - ], - "parameters": [ - { - "name": "accountId", - "description": "The external account number (int) or account ID GUID.", - "type": { "name": "string" } - }, - { - "name": "payload", - "description": "", - "type": { - "name": "EnvelopeDefinition", - "links": [{ "category": "internal", "recordName": "EnvelopeDefinition" }] - } - }, - { - "name": "cdse_mode", - "description": "Reserved for DocuSign.", - "type": { "name": "string|()" }, - "optional": true, - "default": "()" - }, - { - "name": "change_routing_order", - "description": "When true, users can define the routing order of recipients while sending documents for signature.", - "type": { "name": "string|()" }, - "optional": true, - "default": "()" - }, - { - "name": "completed_documents_only", - "description": "Reserved for DocuSign.", - "type": { "name": "string|()" }, - "optional": true, - "default": "()" - }, - { - "name": "merge_roles_on_draft", - "description": "When true, template roles will be merged, and empty recipients will be removed.", - "type": { "name": "string|()" }, - "optional": true, - "default": "()" - } - ], - "return": { - "type": { - "name": "EnvelopeSummary|error", - "links": [{ "category": "internal", "recordName": "EnvelopeSummary" }] - } - } - } - ] - } - ], - "functions": [], - "services": [] - }, - { - "name": "ballerinax/postgresql", - "description": "This module provides the functionality required to access and manipulate data stored in a PostgreSQL database.", - "typeDefs": [], - "clients": [ - { - "name": "Client", - "description": "Represents a PostgreSQL database client.", - "functions": [ - { - "name": "init", - "type": "Constructor", - "description": "Initializes the PostgreSQL Client.", - "parameters": [ - { "name": "host", "description": "Hostname of the PostgreSQL server", "type": { "name": "string" }, "optional": true, "default": "\"localhost\"" }, - { "name": "username", "description": "", "type": { "name": "string|()" }, "optional": true, "default": "\"postgres\"" }, - { "name": "password", "description": "The password of the PostgreSQL server for the provided username", "type": { "name": "string|()" }, "optional": true, "default": "()" }, - { "name": "database", "description": "The name of the database", "type": { "name": "string|()" }, "optional": true, "default": "()" }, - { "name": "port", "description": "Port number of the PostgreSQL server", "type": { "name": "int" }, "optional": true, "default": "5432" }, - { "name": "options", "description": "The database specific PostgreSQL connection properties", "type": { "name": "Options|()", "links": [{ "category": "internal", "recordName": "Options" }] }, "optional": true, "default": "()" }, - { - "name": "connectionPool", - "description": "The `sql:ConnectionPool` object to be used within the client", - "type": { - "name": "ConnectionPool|()", - "links": [{ "category": "external", "recordName": "ConnectionPool", "libraryName": "ballerina/sql" }] - }, - "optional": true, - "default": "()" - } - ], - "return": { - "description": "An `sql:Error` if the client creation fails", - "type": { "name": "ballerina/sql:1.16.0:Error?" } - } - }, - { - "name": "queryRow", - "type": "Remote Function", - "description": "Executes the query, which is expected to return at most one row of the result.\nIf the query does not return any results, an `sql:NoRowsError` is returned.", - "parameters": [ - { - "name": "sqlQuery", - "description": "The SQL query such as `` `SELECT * from Album WHERE name=${albumName}` ``", - "type": { - "name": "ParameterizedQuery", - "links": [{ "category": "external", "recordName": "ParameterizedQuery", "libraryName": "ballerina/sql" }] - } - }, - { - "name": "returnType", - "description": "The `typedesc` of the record to which the result needs to be returned", - "type": { "name": "anydata" }, - "optional": true, - "default": "anydata" - } - ], - "return": { - "description": "Result in the `returnType` type or an `sql:Error`", - "type": { - "name": "returnType|Error", - "links": [ - { "category": "internal", "recordName": "returnType" }, - { "category": "external", "recordName": "Error", "libraryName": "ballerina/sql" } - ] - } - } - } - ] - } - ], - "functions": [], - "services": [] - }, - { - "name": "ballerinax/custom.integration", - "description": "A custom integration library with instructions.", - "instructions": "// Use this library for custom integrations.\n// Always initialize the client before calling any function.", - "typeDefs": [ - { - "name": "RecordWithDefault", - "description": "A record with a field that has a default value.", - "type": "Record", - "fields": [ - { - "name": "timeout", - "description": "Connection timeout in seconds.", - "type": { "name": "int" }, - "optional": true, - "default": "60" - }, - { - "name": "retryCount", - "description": "Number of retries.", - "type": { "name": "int" }, - "optional": true - } - ] - } - ], - "clients": [], - "functions": [ - { - "name": "process", - "type": "Normal Function", - "description": "Processes a request with multi-package external dependencies.", - "parameters": [ - { - "name": "req", - "description": "The HTTP request", - "type": { - "name": "Request", - "links": [{ "category": "external", "recordName": "Request", "libraryName": "ballerina/http" }] - } - }, - { - "name": "msg", - "description": "The Kafka message", - "type": { - "name": "Message", - "links": [{ "category": "external", "recordName": "Message", "libraryName": "ballerinax/kafka" }] - } - } - ], - "return": { - "type": { "name": "error?" } - } - } - ], - "services": [] - } -] diff --git a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string-snapshot.test.ts b/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string-snapshot.test.ts deleted file mode 100644 index e4ac614d53..0000000000 --- a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string-snapshot.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -import * as assert from "assert"; -import * as path from "path"; -import * as fs from "fs"; -import { Library } from "../../../../src/features/ai/utils/libs/library-types"; -import { toSyntaxString } from "../../../../src/features/ai/utils/libs/to-syntax-string"; - -const INPUT_DIR = path.join(__dirname, "resources", "input"); -const EXPECTED_DIR = path.join(__dirname, "resources", "expected"); - -/** - * When called, writes the actual output as the expected .txt file. - * This is used to bootstrap expected files for new inputs. - * Comment out the call to this function once snapshots are reviewed and committed. - */ -function updateExpected(name: string, actual: string): void { - if (!fs.existsSync(EXPECTED_DIR)) { - fs.mkdirSync(EXPECTED_DIR, { recursive: true }); - } - fs.writeFileSync(path.join(EXPECTED_DIR, `${name}.txt`), actual, "utf-8"); -} - -/** - * Discovers all .json files in the input directory. - */ -function discoverInputs(): string[] { - if (!fs.existsSync(INPUT_DIR)) { - return []; - } - return fs.readdirSync(INPUT_DIR) - .filter((f) => f.endsWith(".json")) - .map((f) => f.replace(/\.json$/, "")); -} - -suite("toSyntaxString — snapshot tests", () => { - const inputs = discoverInputs(); - - if (inputs.length === 0) { - test("no input files found", () => { - assert.fail("No .json files found in resources/input/. Add at least one input file."); - }); - return; - } - - for (const name of inputs) { - test(`snapshot: ${name}`, () => { - const inputPath = path.join(INPUT_DIR, `${name}.json`); - const expectedPath = path.join(EXPECTED_DIR, `${name}.txt`); - - const libraries: Library[] = JSON.parse(fs.readFileSync(inputPath, "utf-8")); - const actual = toSyntaxString(libraries); - - // --- Auto-generate expected file if missing --- - // Comment out the next line once you've reviewed and committed the .txt snapshots. - updateExpected(name, actual); - - assert.ok( - fs.existsSync(expectedPath), - `Expected file not found: ${expectedPath}. Run tests once with updateExpected enabled to generate it.` - ); - - const expected = fs.readFileSync(expectedPath, "utf-8"); - assert.strictEqual(actual, expected, `Snapshot mismatch for "${name}". If the change is intentional, delete ${expectedPath} and re-run to regenerate.`); - }); - } -}); diff --git a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string.test.ts b/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string.test.ts deleted file mode 100644 index 61f71d68bf..0000000000 --- a/workspaces/ballerina/ballerina-extension/test/ai/unit_tests/libs/to-syntax-string.test.ts +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved. - -// 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. - -import * as assert from "assert"; -import * as path from "path"; -import * as fs from "fs"; -import { Library } from "../../../../src/features/ai/utils/libs/library-types"; -import { toSyntaxString, deriveModulePrefix } from "../../../../src/features/ai/utils/libs/to-syntax-string"; - -const RESOURCES_DIR = path.join(__dirname, "resources"); - -function loadLibraries(filename: string): Library[] { - const filePath = path.join(RESOURCES_DIR, filename); - const raw = fs.readFileSync(filePath, "utf-8"); - return JSON.parse(raw) as Library[]; -} - -/** - * Helper: render a single library by name from the fixture. - */ -function renderLibrary(allLibs: Library[], name: string): string { - const lib = allLibs.find((l) => l.name === name); - assert.ok(lib, `Library ${name} not found in fixture`); - return toSyntaxString([lib!]); -} - -suite("toSyntaxString", () => { - let allLibraries: Library[]; - let fullResult: string; - - suiteSetup(() => { - allLibraries = loadLibraries("sample-libraries.json"); - fullResult = toSyntaxString(allLibraries); - }); - - // ---------------------------------------------------------------- - // Design Doc: Implementation Notes — Module prefix derivation - // ---------------------------------------------------------------- - suite("deriveModulePrefix", () => { - test("should derive correct module prefixes from the design doc table", () => { - assert.strictEqual(deriveModulePrefix("ballerina/http"), "http"); - assert.strictEqual(deriveModulePrefix("ballerinax/salesforce"), "salesforce"); - assert.strictEqual(deriveModulePrefix("ballerinax/client.config"), "config"); - assert.strictEqual(deriveModulePrefix("ballerinax/docusign.dsesign"), "dsesign"); - assert.strictEqual(deriveModulePrefix("ballerina/oauth2"), "oauth2"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §13: Library (top-level structure) - // ---------------------------------------------------------------- - suite("§13 Library top-level structure", () => { - test("should render library header with separator, name, description, and import", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("// ============================================================")); - assert.ok(result.includes("// Library: ballerina/http")); - assert.ok(result.includes("// This module provides APIs for connecting and interacting with HTTP and HTTP2 endpoints.")); - assert.ok(result.includes("import ballerina/http;")); - }); - - test("should render section headers only when section is non-empty", () => { - // ballerina/http has types, functions, services — but no clients - const httpResult = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(httpResult.includes("// --- Types ---"), "Should have Types section"); - assert.ok(httpResult.includes("// --- Functions ---"), "Should have Functions section"); - assert.ok(httpResult.includes("// --- Service ---"), "Should have Service section"); - assert.ok(!httpResult.includes("// --- Client ---"), "Should NOT have Client section (empty)"); - - // ballerina/io has only functions — no types, clients, services - const ioResult = renderLibrary(allLibraries, "ballerina/io"); - assert.ok(!ioResult.includes("// --- Types ---"), "io should NOT have Types section"); - assert.ok(!ioResult.includes("// --- Client ---"), "io should NOT have Client section"); - assert.ok(ioResult.includes("// --- Functions ---"), "io should have Functions section"); - assert.ok(!ioResult.includes("// --- Service ---"), "io should NOT have Service section"); - }); - - test("should prepend library instructions before everything when present", () => { - const result = renderLibrary(allLibraries, "ballerinax/custom.integration"); - const importIdx = result.indexOf("import ballerinax/custom.integration;"); - const instructionsIdx = result.indexOf("// Use this library for custom integrations."); - const typesIdx = result.indexOf("// --- Types ---"); - assert.ok(instructionsIdx > importIdx, "Instructions should come after import"); - assert.ok(instructionsIdx < typesIdx, "Instructions should come before Types section"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §1: RecordTypeDefinition - // ---------------------------------------------------------------- - suite("§1 RecordTypeDefinition", () => { - test("should render record with internal links only (CacheConfig from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - // Record-level description as # comment - assert.ok(result.includes("# Provides a set of configurations for controlling the caching behaviour of the endpoint.")); - assert.ok(result.includes("type CacheConfig record {")); - // Field-level descriptions - assert.ok(result.includes(" # Specifies whether HTTP caching is enabled. Caching is enabled by default.")); - // Optional fields with ? - assert.ok(result.includes("boolean enabled?;")); - assert.ok(result.includes("boolean isShared?;")); - assert.ok(result.includes("int capacity?;")); - assert.ok(result.includes("float evictionFactor?;")); - // Internal link — no prefix, no Special Agent Note - assert.ok(result.includes("CachingPolicy policy?;")); - assert.ok(!result.includes("CachingPolicy policy?; //"), "Internal link should have no agent note"); - assert.ok(result.includes("};")); - }); - - test("should render record with external links and Special Agent Note (ConnectionConfig from ballerinax/salesforce)", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - // No description → no # comment before record - assert.ok(result.includes("type ConnectionConfig record {")); - // No field descriptions → no # comments on fields - assert.ok(result.includes(" string baseUrl;")); - // External links: prefix + Special Agent Note - assert.ok( - result.includes("http:BearerTokenConfig|http:OAuth2RefreshTokenGrantConfig|OAuth2PasswordGrantConfig|OAuth2ClientCredentialsGrantConfig auth;"), - "Should prefix external types and leave non-external types unprefixed" - ); - assert.ok( - result.includes("// Special Agent Note: BearerTokenConfig, OAuth2RefreshTokenGrantConfig FROM ballerina/http package"), - "Should add grouped Special Agent Note" - ); - }); - - test("should render per-field external notes (ClientHttp1Settings from ballerinax/docusign.dsesign)", () => { - const result = renderLibrary(allLibraries, "ballerinax/docusign.dsesign"); - assert.ok(result.includes("type ClientHttp1Settings record {")); - // Each external field gets its own note - assert.ok( - result.includes("http:KeepAlive keepAlive?; // Special Agent Note: KeepAlive FROM ballerina/http package"), - "keepAlive should have its own agent note" - ); - assert.ok( - result.includes("http:Chunking chunking?; // Special Agent Note: Chunking FROM ballerina/http package"), - "chunking should have its own agent note" - ); - // Internal link — no prefix, no note - assert.ok(result.includes("ProxyConfig proxy?;")); - const proxyLine = result.split("\n").find((l) => l.includes("ProxyConfig proxy?;")); - assert.ok(proxyLine && !proxyLine.includes("Special Agent Note"), "Internal link should have no agent note"); - }); - - test("should render record field with default value (RecordWithDefault from ballerinax/custom.integration)", () => { - const result = renderLibrary(allLibraries, "ballerinax/custom.integration"); - assert.ok(result.includes("type RecordWithDefault record {")); - assert.ok( - result.includes("int timeout? = 60;"), - "Should render field with optional + default" - ); - assert.ok( - result.includes("int retryCount?;"), - "Should render optional field without default" - ); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §2: EnumTypeDefinition - // ---------------------------------------------------------------- - suite("§2 EnumTypeDefinition", () => { - test("should render enum with members, skip member descriptions (HttpVersion from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Defines the supported HTTP protocols.")); - assert.ok(result.includes("enum HttpVersion {")); - assert.ok(result.includes("HTTP_2_0")); - assert.ok(result.includes("HTTP_1_1")); - assert.ok(result.includes("HTTP_1_0")); - // Member descriptions should be skipped - assert.ok(!result.includes("Represents HTTP/2.0 protocol"), "Should skip enum member descriptions"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §3: UnionTypeDefinition - // ---------------------------------------------------------------- - suite("§3 UnionTypeDefinition", () => { - test("should render union with members (Compression from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - // Multi-line description - assert.ok(result.includes("# Options to compress using gzip or deflate.")); - assert.ok(result.includes("# AUTO: When service behaves as a HTTP gateway...")); - assert.ok(result.includes("type Compression COMPRESSION_AUTO|COMPRESSION_ALWAYS|COMPRESSION_NEVER;")); - }); - - test("should render union without members as bare type declaration (StatusCode from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Represents an HTTP status code type.")); - assert.ok(result.includes("type StatusCode;")); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §4: ConstantTypeDefinition - // ---------------------------------------------------------------- - suite("§4 ConstantTypeDefinition", () => { - test("should render string constant with quoted value (AUTH_HEADER from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Represents the Authorization header name.")); - assert.ok(result.includes('const string AUTH_HEADER = "Authorization";')); - }); - - test("should render numeric constant without quotes (DEFAULT_PORT from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Default HTTP listener port.")); - assert.ok(result.includes("const int DEFAULT_PORT = 9090;")); - // Should NOT have quotes around numeric value - assert.ok(!result.includes('"9090"'), "Numeric constant should not be quoted"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §5: ClassTypeDefinition - // ---------------------------------------------------------------- - suite("§5 ClassTypeDefinition", () => { - test("should render class with description and empty body (PersistentCookieHandler from ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Provides persistence for cookies.")); - assert.ok(result.includes("class PersistentCookieHandler {")); - // Should NOT be `client class` - assert.ok(!result.includes("client class PersistentCookieHandler"), "Regular class should not be client class"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §6: Client — Constructor - // ---------------------------------------------------------------- - suite("§6 Client Constructor", () => { - test("should render constructor with internal links only (salesforce)", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok(result.includes("client class Client {")); - assert.ok( - result.includes("function init(ConnectionConfig config) returns error?;"), - "Constructor should use function init(...), no remote keyword, no description" - ); - }); - - test("should render constructor with external links and defaults (postgresql)", () => { - const result = renderLibrary(allLibraries, "ballerinax/postgresql"); - // Constructor with many params, defaults, and external link - assert.ok( - result.includes('function init(string host = "localhost", string|() username = "postgres", string|() password = (), string|() database = (), int port = 5432, Options|() options = (), sql:ConnectionPool|() connectionPool = ()) returns ballerina/sql:1.16.0:Error?;'), - "Should render constructor with all params, defaults, external prefix" - ); - assert.ok( - result.includes("// Special Agent Note: ConnectionPool FROM ballerina/sql package"), - "Constructor should have Special Agent Note for external param" - ); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §7: Client — Remote Function - // ---------------------------------------------------------------- - suite("§7 Client Remote Function", () => { - test("should render remote function without external links (salesforce query)", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok(result.includes(" # Executes the specified SOQL query.")); - assert.ok( - result.includes("remote function query(string soql, record {|anydata...;|} returnType = record {|anydata...;|}) returns stream|error;"), - "Should render remote function with default param" - ); - }); - - test("should render remote function with external links on param and return (postgresql queryRow)", () => { - const result = renderLibrary(allLibraries, "ballerinax/postgresql"); - assert.ok(result.includes(" # Executes the query, which is expected to return at most one row of the result.")); - assert.ok(result.includes(" # If the query does not return any results, an `sql:NoRowsError` is returned.")); - assert.ok( - result.includes("remote function queryRow(sql:ParameterizedQuery sqlQuery, anydata returnType = anydata) returns returnType|sql:Error;"), - "Should prefix external types in both param and return" - ); - assert.ok( - result.includes("// Special Agent Note: ParameterizedQuery, Error FROM ballerina/sql package"), - "Should collect external links from both params and return in one note" - ); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §8: Client — Resource Function - // ---------------------------------------------------------------- - suite("§8 Client Resource Function", () => { - test("should render resource function with path segments and path-param exclusion (docusign post envelopes)", () => { - const result = renderLibrary(allLibraries, "ballerinax/docusign.dsesign"); - assert.ok(result.includes(" # Creates an envelope.")); - // Path: accounts/[string accountId]/envelopes - assert.ok( - result.includes("resource function post accounts/[string accountId]/envelopes("), - "Should render path with static segments and path parameter brackets" - ); - // accountId should NOT appear in parenthesized params (it's in the path) - const resourceLine = result.split("\n").find((l) => l.includes("resource function post accounts")); - assert.ok(resourceLine, "Resource function line should exist"); - const paramsSection = resourceLine!.substring(resourceLine!.indexOf("(")); - assert.ok(!paramsSection.includes("string accountId"), "Path param should be excluded from parenthesized params"); - // Non-path params should be present - assert.ok(paramsSection.includes("EnvelopeDefinition payload")); - assert.ok(paramsSection.includes("string|() cdse_mode = ()")); - assert.ok(paramsSection.includes("string|() change_routing_order = ()")); - // Return type - assert.ok(paramsSection.includes("returns EnvelopeSummary|error;")); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §9: Client (full composition) - // ---------------------------------------------------------------- - suite("§9 Client full composition", () => { - test("should render client class with constructor + remote functions (salesforce)", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok(result.includes("# Ballerina Salesforce connector provides the capability to access Salesforce REST API.")); - assert.ok(result.includes("client class Client {")); - assert.ok(result.includes("function init(ConnectionConfig config) returns error?;")); - assert.ok(result.includes("remote function query(")); - assert.ok(result.includes("}")); - }); - - test("should render client class with constructor + resource functions (docusign)", () => { - const result = renderLibrary(allLibraries, "ballerinax/docusign.dsesign"); - assert.ok(result.includes("client class Client {")); - assert.ok(result.includes("function init(ConnectionConfig config) returns error?;")); - assert.ok(result.includes("resource function post accounts/[string accountId]/envelopes(")); - }); - - test("should render client class with constructor + remote functions with external links (postgresql)", () => { - const result = renderLibrary(allLibraries, "ballerinax/postgresql"); - assert.ok(result.includes("# Represents a PostgreSQL database client.")); - assert.ok(result.includes("client class Client {")); - assert.ok(result.includes("function init(")); - assert.ok(result.includes("remote function queryRow(")); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §10: Standalone Functions (library-level) - // ---------------------------------------------------------------- - suite("§10 Standalone Functions", () => { - test("should render standalone function with # + param and # + return docs (io fileWriteBytes)", () => { - const result = renderLibrary(allLibraries, "ballerina/io"); - assert.ok(result.includes("# Write a set of bytes to a file.")); - assert.ok(result.includes("# + path - The path of the file")); - assert.ok(result.includes("# + content - Byte content to write")); - assert.ok(result.includes("# + option - To indicate whether to overwrite or append the given content")); - assert.ok(result.includes("# + return - An `io:Error` or else `()`")); - assert.ok( - result.includes("function fileWriteBytes(string path, byte[] content, FileWriteOption option = OVERWRITE) returns Error|();"), - "Should render function with params and default" - ); - }); - - test("should render standalone function without param descriptions (http authenticateResource)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("# Uses for declarative auth design.")); - assert.ok( - result.includes("function authenticateResource(Service serviceRef, string methodName, string[] resourcePath) returns ();"), - "Should render function with no param docs when descriptions are empty" - ); - // Should NOT have # + param lines for params with empty descriptions - const funcLines = result.split("\n"); - const authFuncIdx = funcLines.findIndex((l) => l.includes("function authenticateResource(")); - // The line before should be the description, not a # + param line - assert.ok( - funcLines[authFuncIdx - 1].includes("# Uses for declarative auth design."), - "No # + param lines for empty descriptions" - ); - }); - - test("should render standalone function with multi-package external links (custom.integration process)", () => { - const result = renderLibrary(allLibraries, "ballerinax/custom.integration"); - assert.ok( - result.includes("function process(http:Request req, kafka:Message msg) returns error?;"), - "Should prefix types from different packages" - ); - assert.ok( - result.includes("// Special Agent Note: Request FROM ballerina/http package, Message FROM ballerinax/kafka package"), - "Should group by package in Special Agent Note with comma separation" - ); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §11: Service — GenericService - // ---------------------------------------------------------------- - suite("§11 GenericService", () => { - test("should render generic service with listener signature and instructions passthrough (ballerina/http)", () => { - const result = renderLibrary(allLibraries, "ballerina/http"); - assert.ok(result.includes("// --- Service (generic) ---")); - assert.ok(result.includes("// Listener: Listener(int port)")); - assert.ok(result.includes("// Instructions:")); - // Instructions passed through verbatim - assert.ok(result.includes("# Service writing instructions")); - assert.ok(result.includes("- HTTP Service always requires a http listener to be attached to it.")); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc §12: Service — FixedService - // ---------------------------------------------------------------- - suite("§12 FixedService", () => { - test("should render fixed service with listener and remote methods (salesforce)", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok( - result.includes("service on new salesforce:Listener(salesforce:ListenerConfig listenerConfig"), - "Should render service on new Listener(...)" - ); - // Method names extracted from description backticks - assert.ok(result.includes(" # The `onCreate` method is triggered when a new record create event is received from Salesforce.")); - assert.ok(result.includes("remote function onCreate(salesforce:EventData payload) returns error?;")); - assert.ok(result.includes("remote function onUpdate(salesforce:EventData payload) returns error?;")); - assert.ok(result.includes("remote function onDelete(salesforce:EventData payload) returns error?;")); - }); - - test("should mark optional methods with // optional comment", () => { - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - // onCreate and onUpdate are optional: false - const onCreateLine = result.split("\n").find((l) => l.includes("remote function onCreate(")); - assert.ok(onCreateLine && !onCreateLine.includes("// optional"), "Required method should not have // optional"); - // onDelete is optional: true - const onDeleteLine = result.split("\n").find((l) => l.includes("remote function onDelete(")); - assert.ok(onDeleteLine && onDeleteLine.includes("// optional"), "Optional method should have // optional comment"); - }); - }); - - // ---------------------------------------------------------------- - // Design Doc: External Type References — Dual Approach - // ---------------------------------------------------------------- - suite("External Type References — Dual Approach", () => { - test("Strategy 1: should apply module-qualified prefix to external type names", () => { - // salesforce ConnectionConfig auth field - const result = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok(result.includes("http:BearerTokenConfig"), "Should prefix with http:"); - assert.ok(result.includes("http:OAuth2RefreshTokenGrantConfig"), "Should prefix with http:"); - // Non-external types left unprefixed - assert.ok(result.includes("|OAuth2PasswordGrantConfig|"), "Non-linked types should stay unprefixed"); - }); - - test("Strategy 2: should emit Special Agent Note only for external links", () => { - // CacheConfig has only internal links → no note - const httpResult = renderLibrary(allLibraries, "ballerina/http"); - const policyLine = httpResult.split("\n").find((l) => l.includes("CachingPolicy policy?;")); - assert.ok(policyLine && !policyLine.includes("Special Agent Note"), "Internal-only field should have no agent note"); - - // ConnectionConfig auth has external links → note - const sfResult = renderLibrary(allLibraries, "ballerinax/salesforce"); - assert.ok(sfResult.includes("// Special Agent Note: BearerTokenConfig, OAuth2RefreshTokenGrantConfig FROM ballerina/http package")); - }); - - test("should handle multi-package external links on a single function line", () => { - const result = renderLibrary(allLibraries, "ballerinax/custom.integration"); - assert.ok( - result.includes("// Special Agent Note: Request FROM ballerina/http package, Message FROM ballerinax/kafka package"), - "Multi-package note should separate packages with comma" - ); - }); - - test("should collect external links from both params and return type on function", () => { - const result = renderLibrary(allLibraries, "ballerinax/postgresql"); - // queryRow has ParameterizedQuery in param and Error in return, both from ballerina/sql - assert.ok( - result.includes("// Special Agent Note: ParameterizedQuery, Error FROM ballerina/sql package"), - "Should collect from both param and return in one note" - ); - }); - }); -}); diff --git a/workspaces/ballerina/ballerina-rpc-client/src/BallerinaRpcClient.ts b/workspaces/ballerina/ballerina-rpc-client/src/BallerinaRpcClient.ts index 1816364b88..04f5d57c77 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/BallerinaRpcClient.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/BallerinaRpcClient.ts @@ -67,9 +67,7 @@ import { traceAnimationChanged, TraceAnimationEvent, webToolToggle, - WebToolToggle, - runningServicesChanged, - RunningServiceInfo + WebToolToggle } from "@wso2/ballerina-core"; import { LangClientRpcClient } from "./rpc-clients/lang-client/rpc-client"; import { LibraryBrowserRpcClient } from "./rpc-clients/library-browser/rpc-client"; @@ -108,7 +106,6 @@ export class BallerinaRpcClient { private _agentChat: AgentChatRpcClient; private _platformExt: PlatformExtRpcClient; private _identifierUpdatedCallbacks = new Set<(response: ProjectStructureArtifactResponse[]) => void>(); - private _runningServicesChangedCallbacks = new Set<(services: RunningServiceInfo[]) => void>(); constructor() { this.messenger = new Messenger(vscode); @@ -135,9 +132,6 @@ export class BallerinaRpcClient { this.messenger.onNotification(onIdentifierUpdated, (response: ProjectStructureArtifactResponse[]) => { this._identifierUpdatedCallbacks.forEach((callback) => callback(response)); }); - this.messenger.onNotification(runningServicesChanged, (services: RunningServiceInfo[]) => { - this._runningServicesChangedCallbacks.forEach((callback) => callback(services)); - }); } getAIAgentRpcClient(): AiAgentRpcClient { @@ -328,11 +322,4 @@ export class BallerinaRpcClient { onTraceAnimationChanged(callback: (event: TraceAnimationEvent) => void) { this.messenger.onNotification(traceAnimationChanged, callback); } - - onRunningServicesChanged(callback: (services: RunningServiceInfo[]) => void): () => void { - this._runningServicesChangedCallbacks.add(callback); - return () => { - this._runningServicesChangedCallbacks.delete(callback); - }; - } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-agent/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-agent/rpc-client.ts index 9e42b40803..de79ee28a4 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-agent/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-agent/rpc-client.ts @@ -53,7 +53,6 @@ import { updateAIAgentTools, updateMCPToolKit, getPackageVersion, - fixMissingImports, AIGetPackageVersionResponse, AIGetPackageVersionRequest } from "@wso2/ballerina-core"; @@ -103,10 +102,6 @@ export class AiAgentRpcClient implements AIAgentAPI { return this._messenger.sendRequest(genTool, HOST_EXTENSION, params); } - fixMissingImports(): Promise { - return this._messenger.sendRequest(fixMissingImports, HOST_EXTENSION); - } - getPackageVersion(params: AIGetPackageVersionRequest): Promise { return this._messenger.sendRequest(getPackageVersion, HOST_EXTENSION, params); } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts index 407e3bf165..55321a8583 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts @@ -52,12 +52,8 @@ import { UpdateChatMessageRequest, UsageResponse, WebToolApprovalRequest, - ClarifyAnswerRequest, - ClarifyCancelRequest, approveWebTool, declineWebTool, - submitClarifyAnswer, - cancelClarify, abortAIGeneration, acceptChanges, addFilesToProject, @@ -111,10 +107,6 @@ import { CompactConversationRequest, CompactConversationResponse, getShowContextUsage, - getRunningServices, - stopRunningService, - RunningServiceInfo, - StopRunningServiceRequest, } from "@wso2/ballerina-core"; import { HOST_EXTENSION } from "vscode-messenger-common"; import { Messenger } from "vscode-messenger-webview"; @@ -339,20 +331,4 @@ export class AiPanelRpcClient implements AIPanelAPI { promptForLogin(): void { return this._messenger.sendNotification(promptForLogin, HOST_EXTENSION); } - - submitClarifyAnswer(params: ClarifyAnswerRequest): Promise { - return this._messenger.sendRequest(submitClarifyAnswer, HOST_EXTENSION, params); - } - - cancelClarify(params: ClarifyCancelRequest): Promise { - return this._messenger.sendRequest(cancelClarify, HOST_EXTENSION, params); - } - - getRunningServices(): Promise { - return this._messenger.sendRequest(getRunningServices, HOST_EXTENSION); - } - - stopRunningService(params: StopRunningServiceRequest): Promise { - return this._messenger.sendRequest(stopRunningService, HOST_EXTENSION, params); - } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/bi-diagram/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/bi-diagram/rpc-client.ts index 5e9fee854a..6d79dbe3a6 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/bi-diagram/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/bi-diagram/rpc-client.ts @@ -20,7 +20,6 @@ import { AIChatRequest, AddFieldRequest, - InlineAgentChatRequest, AddFunctionRequest, AddImportItemResponse, AddProjectToWorkspaceRequest, @@ -114,7 +113,6 @@ import { ServiceClassModelResponse, ServiceClassSourceRequest, SignatureHelpRequest, - SuggestedProjectDefaultsResponse, SignatureHelpResponse, SourceEditResponse, UpdateConfigVariableRequestV2, @@ -129,10 +127,6 @@ import { UpdatedArtifactsResponse, ValidateProjectFormRequest, ValidateProjectFormResponse, - UpdateProjectTitleRequest, - UpdatePackageTitleRequest, - updateProjectTitle, - updatePackageTitle, VerifyTypeDeleteRequest, VerifyTypeDeleteResponse, VisibleTypesRequest, @@ -183,7 +177,6 @@ import { getFormDiagnostics, getFunctionNames, getFunctionNode, - getSuggestedProjectDefaults, getModuleNodes, getNodeTemplate, getOpenApiGeneratedModules, @@ -207,8 +200,6 @@ import { handleReadmeContent, openAIChat, openConfigToml, - startInlineAgentChat, - cleanupAgentChatServices, openReadme, removeBreakpointFromSource, renameIdentifier, @@ -305,10 +296,6 @@ export class BiDiagramRpcClient implements BIDiagramAPI { return this._messenger.sendRequest(validateProjectPath, HOST_EXTENSION, params); } - getSuggestedProjectDefaults(params: { isInProject: boolean }): Promise { - return this._messenger.sendRequest(getSuggestedProjectDefaults, HOST_EXTENSION, params); - } - deleteProject(params: DeleteProjectRequest): void { return this._messenger.sendNotification(deleteProject, HOST_EXTENSION, params); } @@ -397,14 +384,6 @@ export class BiDiagramRpcClient implements BIDiagramAPI { return this._messenger.sendNotification(openAIChat, HOST_EXTENSION, params); } - startInlineAgentChat(params: InlineAgentChatRequest): void { - return this._messenger.sendNotification(startInlineAgentChat, HOST_EXTENSION, params); - } - - cleanupAgentChatServices(): Promise { - return this._messenger.sendRequest(cleanupAgentChatServices, HOST_EXTENSION); - } - getSignatureHelp(params: SignatureHelpRequest): Promise { return this._messenger.sendRequest(getSignatureHelp, HOST_EXTENSION, params); } @@ -584,12 +563,4 @@ export class BiDiagramRpcClient implements BIDiagramAPI { getExpressionTokens(params: ExpressionTokensRequest): Promise { return this._messenger.sendRequest(getExpressionTokens, HOST_EXTENSION, params); } - - updateProjectTitle(params: UpdateProjectTitleRequest): Promise { - return this._messenger.sendRequest(updateProjectTitle, HOST_EXTENSION, params); - } - - updatePackageTitle(params: UpdatePackageTitleRequest): Promise { - return this._messenger.sendRequest(updatePackageTitle, HOST_EXTENSION, params); - } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/common/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/common/rpc-client.ts index 5df5883abc..274ff3c41a 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/common/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/common/rpc-client.ts @@ -49,7 +49,6 @@ import { getWorkspaceFiles, getWorkspaceRoot, getWorkspaceType, - getPreferredTryItOption, goToSource, hasCentralPATConfigured, isNPSupported, @@ -58,7 +57,6 @@ import { runBackgroundTerminalCommand, selectFileOrDirPath, selectFileOrFolderPath, - setPreferredTryItOption, showErrorMessage, SetWebviewCacheRequestParam, SetWebviewCache, @@ -175,12 +173,4 @@ export class CommonRpcClient implements CommonRPCAPI { hasCentralPATConfigured(): Promise { return this._messenger.sendRequest(hasCentralPATConfigured, HOST_EXTENSION); } - - getPreferredTryItOption(): Promise { - return this._messenger.sendRequest(getPreferredTryItOption, HOST_EXTENSION); - } - - setPreferredTryItOption(option: string): Promise { - return this._messenger.sendRequest(setPreferredTryItOption, HOST_EXTENSION, option); - } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/migrate-integration/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/migrate-integration/rpc-client.ts index 92dde0d83d..e2894c7c5a 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/migrate-integration/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/migrate-integration/rpc-client.ts @@ -40,23 +40,6 @@ import { import { HOST_EXTENSION } from "vscode-messenger-common"; import { Messenger } from "vscode-messenger-webview"; -// Defined locally to avoid depending on a rebuilt @wso2/ballerina-core -const _getActiveMigrationSession = { method: "migrate-integration/getActiveMigrationSession" } as const; -const _markEnhancementComplete = { method: "migrate-integration/markEnhancementComplete" } as const; -const _startMigrationEnhancement = { method: "migrate-integration/startMigrationEnhancement" } as const; -const _wizardEnhancementReady = { method: "migrate-integration/wizardEnhancementReady" } as const; -const _openMigratedProject = { method: "migrate-integration/openMigratedProject" } as const; -const _abortMigrationAgent = { method: "migrate-integration/abortMigrationAgent" } as const; -const _seedMigrationHistory = { method: "migrate-integration/seedMigrationHistory" } as const; -const _getMigrationHistoryMessages = { method: "migrate-integration/getMigrationHistoryMessages" } as const; - -/** Local mirror until @wso2/ballerina-core is rebuilt. */ -export interface ActiveMigrationSession { - isActive: boolean; - aiFeatureUsed: boolean; - fullyEnhanced: boolean; -} - export class MigrateIntegrationRpcClient implements MigrateIntegrationAPI { private _messenger: Messenger; @@ -95,55 +78,4 @@ export class MigrateIntegrationRpcClient implements MigrateIntegrationAPI { migrateProject(params: MigrateRequest): void { return this._messenger.sendNotification(migrateProject, HOST_EXTENSION, params); } - - getActiveMigrationSession(): Promise { - return this._messenger.sendRequest(_getActiveMigrationSession as any, HOST_EXTENSION); - } - - markEnhancementComplete(): Promise { - return this._messenger.sendRequest(_markEnhancementComplete as any, HOST_EXTENSION); - } - - startMigrationEnhancement(): Promise { - return this._messenger.sendRequest(_startMigrationEnhancement as any, HOST_EXTENSION); - } - - /** - * Tells the extension backend that the wizard AI enhancement view is - * visible and ready to receive streaming events. Triggers the - * wizard-level migration agent. - */ - wizardEnhancementReady(): Promise { - return this._messenger.sendRequest(_wizardEnhancementReady as any, HOST_EXTENSION); - } - - /** - * Opens the migrated project in VS Code. - * Called after wizard-level AI enhancement completes or the user skips it. - */ - openMigratedProject(): Promise { - return this._messenger.sendRequest(_openMigratedProject as any, HOST_EXTENSION); - } - - /** - * Aborts the currently running migration AI agent. - */ - abortMigrationAgent(): Promise { - return this._messenger.sendRequest(_abortMigrationAgent as any, HOST_EXTENSION); - } - - /** - * Seeds saved migration conversation history into the AI chat state. - * Returns true if history was found and seeded, false otherwise. - */ - seedMigrationHistory(): Promise { - return this._messenger.sendRequest(_seedMigrationHistory as any, HOST_EXTENSION); - } - - /** - * Retrieves the persisted migration conversation history messages. - */ - getMigrationHistoryMessages(): Promise> { - return this._messenger.sendRequest(_getMigrationHistoryMessages as any, HOST_EXTENSION); - } } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx index 09c0f73d0a..f37fb88d3a 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx @@ -411,7 +411,7 @@ export interface FormProps { formDiagnostics?: { message: string; severity: "ERROR" | "WARNING" | "INFO" }[]; formDiagnosticsAction?: React.ReactNode; preserveOrder?: boolean; - handleSelectedTypeChange?: (type: string | CompletionItem) => void; + handleSelectedTypeChange?: (type: string | CompletionItem) => void | Promise; scopeFieldAddon?: React.ReactNode; onChange?: (fieldKey: string, value: any, allValues: FormValues) => void; injectedComponents?: { @@ -426,7 +426,6 @@ export interface FormProps { openFormTypeEditor?: (open: boolean, newType?: string, editingField?: FormField) => void; derivedFields?: FieldDerivation[]; // Configuration for auto-deriving field values from other fields updateImports?: (key: string, imports: Imports) => void; - defaultExpandAdvanced?: boolean; } export const Form = forwardRef((props: FormProps, _ref) => { @@ -497,7 +496,7 @@ export const Form = forwardRef((props: FormProps, _ref) => { rpcClient.getBIDiagramRpcClient().formDirtyDidChange({ filePath: fileName, isDirty }); }, [isDirty, fileName, rpcClient]); - const [showAdvancedOptions, setShowAdvancedOptions] = useState(props.defaultExpandAdvanced ?? false); + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [activeFormField, setActiveFormField] = useState(undefined); const [diagnosticsInfo, setDiagnosticsInfo] = useState(undefined); const [isMarkdownExpanded, setIsMarkdownExpanded] = useState(false); @@ -550,21 +549,15 @@ export const Form = forwardRef((props: FormProps, _ref) => { defaultValues[field.key] = formValues[field.key] ?? []; } - if (getPrimaryInputType(field.types)?.fieldType === "TYPE") { + if (field.key === "type") { // Handle the case where the type is changed via 'Add Type' const existingType = formValues[field.key]; const newType = field.value; - if (existingType === "") { - // User has explicitly cleared the type field; preserve the empty value - defaultValues[field.key] = ""; - } else if (existingType !== newType) { + if (existingType !== newType) { setValue(field.key, newType); getVisualiableFields(); } - else if (newType === undefined) { - defaultValues[field.key] = ""; - } } // Handle choice fields and their properties @@ -588,9 +581,7 @@ export const Form = forwardRef((props: FormProps, _ref) => { } } - const rawDiag = (field.diagnostics as any); - const diagArray = Array.isArray(rawDiag) ? rawDiag : (rawDiag?.diagnostics ?? []); - diagnosticsMap.push({ key: field.key, diagnostics: diagArray }); + diagnosticsMap.push({ key: field.key, diagnostics: [] }); } // Handle the case where the name is updated dynamically (e.g., from a sibling field's onValueChange like headerName) @@ -672,12 +663,10 @@ export const Form = forwardRef((props: FormProps, _ref) => { openSubPanel(updatedSubPanel); }; - const handleOnTypeChange = () => { - getVisualiableFields(); - }; - const handleNewTypeSelected = (type: string | CompletionItem) => { - handleSelectedTypeChange && handleSelectedTypeChange(type); + Promise.resolve(handleSelectedTypeChange?.(type)).catch((error) => { + console.error("Error in handleSelectedTypeChange", error); + }); } const getVisualiableFields = () => { @@ -780,8 +769,8 @@ export const Form = forwardRef((props: FormProps, _ref) => { // has advance fields const hasAdvanceFields = formFields.some((field) => field.advanced && field.enabled && !field.hidden) || advancedChoiceFields.length > 0; const variableField = formFields.find((field) => field.key === "variable"); - const typeField = formFields.find((field) => getPrimaryInputType(field.types)?.fieldType === "TYPE"); - const expressionField = formFields.find((field) => getPrimaryInputType(field.types)?.fieldType === "EXPRESSION"); + const typeField = formFields.find((field) => field.key === "type"); + const expressionField = formFields.find((field) => field.key === "expression"); const targetTypeField = formFields.find((field) => field.codedata?.kind === "PARAM_FOR_TYPE_INFER"); const hasParameters = hasRequiredParameters(formFields, selectedNode) || hasOptionalParameters(formFields); @@ -818,7 +807,7 @@ export const Form = forwardRef((props: FormProps, _ref) => { // Find the first editable field const firstEditableFieldIndex = formFields.findIndex( - (field) => field.editable !== false + (field) => field.editable !== false && (field.value == null || field.value === '') ); const isValid = useMemo(() => { @@ -832,7 +821,7 @@ export const Form = forwardRef((props: FormProps, _ref) => { continue; } - let diagnostics: Diagnostic[] = Array.isArray(diagnosticsInfoItem.diagnostics) ? diagnosticsInfoItem.diagnostics : []; + let diagnostics: Diagnostic[] = diagnosticsInfoItem.diagnostics || []; if (diagnostics.length === 0) { // Only clear errors that were set by the expression diagnostics system, // not errors set by other validators (e.g., PathEditor) @@ -1121,7 +1110,6 @@ export const Form = forwardRef((props: FormProps, _ref) => { autoFocus={firstEditableFieldIndex === formFields.indexOf(updatedField) && !hideSaveButton} recordTypeFields={recordTypeFields} onIdentifierEditingStateChange={handleIdentifierEditingStateChange} - handleOnTypeChange={handleOnTypeChange} setSubComponentEnabled={setIsSubComponentEnabled} handleNewTypeSelected={handleNewTypeSelected} onBlur={handleOnBlur} @@ -1222,7 +1210,6 @@ export const Form = forwardRef((props: FormProps, _ref) => { handleOnFieldFocus={handleOnFieldFocus} recordTypeFields={recordTypeFields} onIdentifierEditingStateChange={handleIdentifierEditingStateChange} - handleOnTypeChange={handleOnTypeChange} onBlur={handleOnBlur} /> @@ -1262,7 +1249,6 @@ export const Form = forwardRef((props: FormProps, _ref) => { handleOnFieldFocus={handleOnFieldFocus} recordTypeFields={recordTypeFields} onIdentifierEditingStateChange={handleIdentifierEditingStateChange} - handleOnTypeChange={handleOnTypeChange} onBlur={handleOnBlur} handleFormValidation={handleFormValidation} /> @@ -1292,7 +1278,6 @@ export const Form = forwardRef((props: FormProps, _ref) => { ((open: boolean, newType?: string | NodeProperties) => handleOpenRecordEditor(open, typeField, newType)) } handleOnFieldFocus={handleOnFieldFocus} - handleOnTypeChange={handleOnTypeChange} recordTypeFields={recordTypeFields} onIdentifierEditingStateChange={handleIdentifierEditingStateChange} handleNewTypeSelected={handleNewTypeSelected} @@ -1308,7 +1293,6 @@ export const Form = forwardRef((props: FormProps, _ref) => { recordTypeFields={recordTypeFields} onIdentifierEditingStateChange={handleIdentifierEditingStateChange} handleNewTypeSelected={handleNewTypeSelected} - handleOnTypeChange={handleOnTypeChange} onBlur={handleOnBlur} handleFormValidation={handleFormValidation} /> @@ -1410,6 +1394,3 @@ export const Form = forwardRef((props: FormProps, _ref) => { }); export default Form; - -export const FormRow = S.Row; -export const FormButtonContainer = S.ButtonContainer; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts b/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts index c699e3e58b..bc6c000d73 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts @@ -46,7 +46,7 @@ export type FormField = { placeholder?: string; defaultValue?: string; documentation: string; - value: string | any; + value: string | any[]; advanceProps?: FormField[]; diagnostics?: DiagnosticMessage[]; items?: string[]; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/Form/utils.ts b/workspaces/ballerina/ballerina-side-panel/src/components/Form/utils.ts index 56236d2794..b7796e90b3 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/Form/utils.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/Form/utils.ts @@ -16,7 +16,7 @@ * under the License. */ -import { NodeKind, getPrimaryInputType } from "@wso2/ballerina-core"; +import { NodeKind } from "@wso2/ballerina-core"; import { FormField, FormImports } from "../.."; // This function allows us to format strings by adding indentation as tabs to the lines @@ -51,7 +51,7 @@ export function updateFormFieldWithImports(formField: FormField, fieldImports: F } export function isPrioritizedField(field: FormField): boolean { - return field.key === "variable" || getPrimaryInputType(field.types)?.fieldType === "TYPE" || field.codedata?.kind === "PARAM_FOR_TYPE_INFER"; + return field.key === "variable" || field.key === "type" || field.codedata?.kind === "PARAM_FOR_TYPE_INFER"; } export function hasRequiredParameters(formFields: FormField[], selectedNode?: NodeKind): boolean { @@ -76,7 +76,7 @@ export function hasOptionalParameters(formFields: FormField[]): boolean { export function hasReturnType(formFields: FormField[]): boolean { return formFields.some(field => - field.key === "variable" || getPrimaryInputType(field.types)?.fieldType === "TYPE" || field.codedata?.kind === "PARAM_FOR_TYPE_INFER" + field.key === "variable" || field.key === "type" || field.codedata?.kind === "PARAM_FOR_TYPE_INFER" ); } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx index f51d1dc873..7ae2481c6b 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx @@ -25,7 +25,7 @@ import { Codicon, ErrorBanner, LinkButton, RequiredFormInput, ThemeColors } from import { FormField, FormValues } from '../Form/types'; import { Controller } from 'react-hook-form'; import { useFormContext } from '../../context'; -import { Imports, NodeKind, getPrimaryInputType } from '@wso2/ballerina-core'; +import { Imports, NodeKind } from '@wso2/ballerina-core'; import { useRpcContext } from '@wso2/ballerina-rpc-client'; import { FieldFactory } from '../editors/FieldFactory'; import { buildRequiredRule, getFieldKeyForAdvanceProp } from '../editors/utils'; @@ -335,7 +335,7 @@ export function ParamManager(props: ParamManagerProps) { field.editable = param.identifierEditable; field.lineRange = param.identifierRange; } - if (getPrimaryInputType(field.types)?.fieldType === "TYPE" && field.type === "ACTION_TYPE" && param.formValues['isGraphqlId'] !== undefined) { + if (field.key === "type" && field.type === "ACTION_TYPE" && param.formValues['isGraphqlId'] !== undefined) { field.isGraphqlId = param.formValues['isGraphqlId']; } } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ChoiceForm.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ChoiceForm.tsx index 9cb5355d39..199d9406e9 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ChoiceForm.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ChoiceForm.tsx @@ -21,7 +21,6 @@ import React, { useEffect, useState } from "react"; import { Codicon, Dropdown, LinkButton, LocationSelector, RadioButtonGroup, ThemeColors } from "@wso2/ui-toolkit"; import { FormField } from "../Form/types"; -import { FormRow, FormButtonContainer } from "../Form"; import { capitalize, getValueForDropdown } from "./utils"; import { useFormContext } from "../../context"; import styled from "@emotion/styled"; @@ -51,6 +50,11 @@ const FormSection = styled.div` width: 100%; `; +const ButtonContainer = styled.div` + display: flex; + justify-content: flex-start; +`; + export function ChoiceForm(props: ChoiceFormProps) { const { field, recordTypeFields } = props; const { form } = useFormContext(); @@ -92,7 +96,7 @@ export function ChoiceForm(props: ChoiceFormProps) { }; setPropertyValues(property.properties); } - }, [selectedOption, field.choices]); + }, [selectedOption]); const convertConfig = (model: PropertyModel): FormField[] => { const formFields: FormField[] = []; @@ -201,22 +205,19 @@ export function ChoiceForm(props: ChoiceFormProps) { /> ))} {advancedFields.length > 0 && ( - - Advanced Configurations - - setShowAdvancedOptions(!showAdvancedOptions)} - sx={{ fontSize: 12, padding: 8, color: ThemeColors.PRIMARY, gap: 4 }} - > - - {showAdvancedOptions ? "Collapse" : "Expand"} - - - + + setShowAdvancedOptions(!showAdvancedOptions)} + sx={{ fontSize: 12, padding: 8, color: ThemeColors.PRIMARY, gap: 4 }} + > + + {showAdvancedOptions ? "Collapse" : "Expand"} + + )} {showAdvancedOptions && advancedFields.map((dfield) => ( = { - MODEL_PROVIDER: "bi-ai-model", - VECTOR_STORE: "bi-db", - EMBEDDING_PROVIDER: "bi-doc", - DATA_LOADER: "bi-data-table", - CHUNKER: "bi-cut", - SHORT_TERM_MEMORY_STORE: "bi-memory", -}; - -// Modules that always use node-type icons, skipping the icon URL fallback -const GENERIC_ICON_MODULES = new Set(["ai.devant", "ai"]); - -export function getConnectionIcon(codedata: CodeData, iconUrl?: string): React.ReactElement { - const iconSx = { width: ICON_SIZE, height: ICON_SIZE, fontSize: ICON_SIZE }; - - // Check AI module icon map first (e.g. OpenAI, Anthropic, etc.) +export function getConnectionIcon(codedata: CodeData): React.ReactElement { + // Check AI module icon map first if (codedata.module) { const icon = getAIModuleIcon(codedata.module, ICON_SIZE); if (icon) return icon; } - // WSO2 "ai" module: use bi-wso2 for model providers and embedding providers + // Handle WSO2 AI module if (codedata.module === "ai") { - if (codedata.node === "MODEL_PROVIDER" || codedata.node === "EMBEDDING_PROVIDER") { - return ; + if (codedata.node === "VECTOR_STORE") { + return ; } + return ; } - // Icon URL from metadata (fetched from Central) — skip for modules that prefer generic icons - if (iconUrl && !(codedata.module && GENERIC_ICON_MODULES.has(codedata.module))) { - return ; + // Fallback by node type + switch (codedata?.node) { + case "MODEL_PROVIDER": + return ; + case "VECTOR_STORE": + return ; + case "EMBEDDING_PROVIDER": + return ; + case "DATA_LOADER": + return ; + case "CHUNKER": + return ; + case "MEMORY_STORE": + return ; + default: + return ; } - - // Icon by node type - const nodeIcon = codedata.node && NODE_ICON_MAP[codedata.node]; - if (nodeIcon) return ; - - return ; } // --- Select Item type --- @@ -70,7 +64,6 @@ export interface ConnectionSelectItem { label: string; value: string; codedata?: CodeData; - iconUrl?: string; } // --- Styled Components --- @@ -182,7 +175,6 @@ interface ConnectionIconSelectProps { emptyMessage?: string; disabled?: boolean; required?: boolean; - loading?: boolean; onChange: (value: string) => void; } @@ -195,7 +187,6 @@ export const ConnectionIconSelect: React.FC = ({ emptyMessage = "No items available. Create one below.", disabled = false, required = false, - loading = false, onChange, }) => { const [open, setOpen] = useState(false); @@ -280,27 +271,22 @@ export const ConnectionIconSelect: React.FC = ({ role="combobox" aria-expanded={open} aria-haspopup="listbox" - tabIndex={disabled || isEmpty || loading ? -1 : 0} - disabled={disabled || isEmpty || loading} - onClick={() => !disabled && !isEmpty && !loading && setOpen(!open)} + tabIndex={disabled || isEmpty ? -1 : 0} + disabled={disabled || isEmpty} + onClick={() => !disabled && !isEmpty && setOpen(!open)} onKeyDown={handleKeyDown} > - {loading ? ( - <> - - Loading... - - ) : selectedItem ? ( + {selectedItem ? ( <> - {selectedItem.codedata && getConnectionIcon(selectedItem.codedata, selectedItem.iconUrl)} + {selectedItem.codedata && getConnectionIcon(selectedItem.codedata)} {selectedItem.label} ) : ( {isEmpty ? emptyMessage : placeholder} )} - {!loading && !isEmpty && } + {!isEmpty && } {open && items.length > 0 && ( @@ -314,7 +300,7 @@ export const ConnectionIconSelect: React.FC = ({ onClick={() => handleSelect(item.value)} onKeyDown={(e: React.KeyboardEvent) => handleOptionKeyDown(e, index, item.value)} > - {item.codedata && getConnectionIcon(item.codedata, item.iconUrl)} + {item.codedata && getConnectionIcon(item.codedata)} {item.label} ))} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/DropdownChoiceForm.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/DropdownChoiceForm.tsx index 03b9ee35b5..48438e6eff 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/DropdownChoiceForm.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/DropdownChoiceForm.tsx @@ -18,8 +18,7 @@ import React, { useEffect, useState } from "react"; -import { Codicon, Dropdown, LinkButton, LocationSelector, RadioButtonGroup, ThemeColors } from "@wso2/ui-toolkit"; -import { FormRow, FormButtonContainer } from "../Form"; +import { Dropdown, LocationSelector, RadioButtonGroup } from "@wso2/ui-toolkit"; import { FormField } from "../Form/types"; import { buildRequiredRule, capitalize, getValueForDropdown } from "./utils"; @@ -58,7 +57,6 @@ export function DropdownChoiceForm(props: DropdownChoiceFormProps) { const [selectedOption, setSelectedOption] = useState(initialOption); const [dynamicFields, setDynamicFields] = useState([]); - const [showGroupSections, setShowGroupSections] = useState<{ [key: string]: boolean }>({}); // Update dynamic fields when selection changes useEffect(() => { @@ -68,7 +66,6 @@ export function DropdownChoiceForm(props: DropdownChoiceFormProps) { setDynamicFields([]); } setValue(field.key, selectedOption); - setShowGroupSections({}); }, [selectedOption]); return ( @@ -94,50 +91,17 @@ export function DropdownChoiceForm(props: DropdownChoiceFormProps) { /> - {dynamicFields - .filter(dfield => dfield.type !== "GROUP_SECTION" && !dfield.advanced && !dfield.optional) - .map((dfield, index) => ( - - )) - } - {dynamicFields - .filter(dfield => dfield.type === "GROUP_SECTION") - .map(groupField => { - const collapsedFields = groupField.advanceProps || []; - if (collapsedFields.length === 0) return null; - const isExpanded = showGroupSections[groupField.key] || false; + {dynamicFields.map((dfield, index) => { + if (!dfield.advanced && !dfield.optional) { return ( - - - {groupField.label} - - setShowGroupSections(prev => ({ - ...prev, - [groupField.key]: !isExpanded - }))} - sx={{ fontSize: 12, padding: 8, color: ThemeColors.PRIMARY, gap: 4 }} - > - - {isExpanded ? "Collapse" : "Expand"} - - - - {isExpanded && collapsedFields.map(childField => ( - - ))} - + ); - }) - } + } + })} ); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/EditorFactory.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/EditorFactory.tsx index d4eba55412..9115a3bf1f 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/EditorFactory.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/EditorFactory.tsx @@ -51,8 +51,8 @@ import { FormMapEditorWrapper } from "./FormMapEditorNewWrapper"; import { InputMode } from "./MultiModeExpressionEditor/ChipExpressionEditor/types"; import { ArgManagerEditor } from "../ParamManager/ArgManager"; import { DependentTypeEditor } from "./DependentTypeEditor"; +import { FormSectionGroup } from "./FormSectionGroup"; import { FieldFactory } from "./FieldFactory"; -import { GroupSectionEditor } from "./GroupSectionEditor"; export interface FormFieldEditorProps { field: FormField; @@ -116,7 +116,30 @@ export const EditorFactory = (props: FormFieldEditorProps) => { if (!field.enabled || field.hidden) { return <>; } else if (fieldInputType.fieldType === "GROUP_SECTION") { - return ; + return ( + + {field.advanceProps?.map((childField) => ( + + ))} + + ); } else if (fieldInputType.fieldType === "RECORD_FIELD_SELECTOR" && field.codedata?.kind === "PARAM_FOR_TYPE_INFER") { return ; } else if (fieldInputType.fieldType === "SLIDER") { @@ -160,7 +183,7 @@ export const EditorFactory = (props: FormFieldEditorProps) => { handleNewTypeSelected={handleNewTypeSelected} /> ); - } else if (!field.items && fieldInputType.fieldType === "TYPE" && field.editable) { + } else if (!field.items && (field.key === "type" || field.type === "TYPE") && field.editable) { return ( = ({ const popupBoxRef = useRef(null); const textAreaRef = useRef(null); - // Reset state when dialog opens + // Reset state and focus when dialog opens useEffect(() => { if (isOpen) { setCustomInstructions(""); + // Focus textarea so user can start typing immediately + setTimeout(() => textAreaRef.current?.focus(), 50); } }, [isOpen]); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/controls/RefinementBar.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/controls/RefinementBar.tsx index 8cbc0e954e..9b5a9a5d25 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/controls/RefinementBar.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/controls/RefinementBar.tsx @@ -31,7 +31,6 @@ interface RefinementBarProps { onVersionNavigate: (index: number) => void; showDiff: boolean; onToggleDiff: () => void; - isGeneration?: boolean; } const BarContainer = styled.div` @@ -174,7 +173,6 @@ export const RefinementBar: React.FC = ({ onVersionNavigate, showDiff, onToggleDiff, - isGeneration = false, }) => { const [refineText, setRefineText] = useState(""); @@ -198,7 +196,7 @@ export const RefinementBar: React.FC = ({ setRefineText(e.target.value)} onKeyDown={handleKeyDown} @@ -234,7 +232,7 @@ export const RefinementBar: React.FC = ({ - {currentVersionIndex === 0 ? (isGeneration ? "Generated" : "Original") : `Version ${currentVersionIndex}`} / {versionCount - 1} + {currentVersionIndex === 0 ? "Original" : `Version ${currentVersionIndex}`} / {versionCount - 1} onVersionNavigate(currentVersionIndex + 1)} @@ -244,7 +242,7 @@ export const RefinementBar: React.FC = ({ ) : ( - {isGeneration ? "Generated Version" : "Original Version"} + Original Version )} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/PromptMode.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/PromptMode.tsx index aef6bbb315..450620fbe3 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/PromptMode.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/PromptMode.tsx @@ -19,6 +19,7 @@ import React, { useState, useRef, useEffect } from "react"; import { EditorView as CodeMirrorView } from "@codemirror/view"; import { EditorView as ProseMirrorView } from "prosemirror-view"; +import { EditorState as ProseMirrorState } from "prosemirror-state"; import { EditorModeExpressionProps } from "./types"; import { ChipExpressionEditorComponent } from "../../MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor"; import { RichTextTemplateEditor, customMarkdownParser, customMarkdownSerializer } from "../../MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor"; @@ -153,10 +154,13 @@ export const PromptMode: React.FC = ({ } else if (proseMirrorView) { const doc = customMarkdownParser.parse(prompt); if (doc) { - const tr = proseMirrorView.state.tr; - (tr as any).replaceWith(0, proseMirrorView.state.doc.content.size, doc.content); - tr.setMeta('addToHistory', false); - proseMirrorView.dispatch(tr); + // Create new editor state with the new document + const newState = ProseMirrorState.create({ + doc: doc, + schema: proseMirrorView.state.schema, + plugins: proseMirrorView.state.plugins + }); + proseMirrorView.updateState(newState); } } }; @@ -233,7 +237,7 @@ export const PromptMode: React.FC = ({ }; const handleRefine = async (instructions: string) => { - const currentEnhanced = showDiff ? enhancedPromptRef.current : getCurrentPrompt(); + const currentEnhanced = getCurrentPrompt(); setEnhancementState({ mode: 'enhancing' }); try { @@ -282,18 +286,13 @@ export const PromptMode: React.FC = ({ const handleVersionNavigate = (index: number) => { if (index < 0 || index >= versionHistoryRef.current.length) return; const prompt = versionHistoryRef.current[index]; - // Only dispatch to the editor when it's mounted (diff view unmounts it) - if (!showDiff) { - applyPrompt(prompt); - } + applyPrompt(prompt); enhancedPromptRef.current = prompt; setCurrentVersionIndex(index); - const cursorPos = !showDiff && isSourceView && codeMirrorView + const cursorPos = isSourceView && codeMirrorView ? codeMirrorView.state.selection.main.head - : !showDiff && proseMirrorView - ? proseMirrorView.state.selection.head - : 0; + : proseMirrorView?.state.selection.head || 0; onChange(prompt, cursorPos); }; @@ -375,7 +374,7 @@ export const PromptMode: React.FC = ({ ) : ( @@ -441,7 +440,6 @@ export const PromptMode: React.FC = ({ onVersionNavigate={handleVersionNavigate} showDiff={showDiff} onToggleDiff={() => setShowDiff(!showDiff)} - isGeneration={isGenerationRef.current} /> )} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FieldFactory.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FieldFactory.tsx index 7dd32570d7..3dfdf1cfaa 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FieldFactory.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FieldFactory.tsx @@ -20,7 +20,7 @@ import React, { useRef } from "react"; import { useEffect, useState, useMemo, useCallback } from "react"; import styled from '@emotion/styled'; import { EditorFactory, FormField, InputMode, useFormContext, Provider as FormContextProvider, FormValues } from "../.."; -import { Imports, InputType, ExpressionProperty, getPrimaryInputType } from "@wso2/ballerina-core"; +import { Imports, InputType, ExpressionProperty } from "@wso2/ballerina-core"; import { NodeKind, NodeProperties, RecordTypeField, SubPanel, SubPanelView } from "@wso2/ballerina-core"; import { CompletionItem } from "@wso2/ui-toolkit"; import { getInputModeFromTypes } from "./MultiModeExpressionEditor/ChipExpressionEditor/utils"; @@ -56,8 +56,6 @@ type FieldFactoryProps = { export const FieldFactory = (props: FieldFactoryProps) => { const [renderingEditors, setRenderingEditors] = useState(null); const [inputMode, setInputMode] = useState(InputMode.EXP); - const currentFieldKeyRef = useRef(null); - const currentInputModeRef = useRef(InputMode.EXP); const formContext = useFormContext(); const { expressionEditor } = formContext; @@ -158,16 +156,6 @@ export const FieldFactory = (props: FieldFactoryProps) => { return props.field.types[props.field.types.length - 1]; } - const checkAndReturnCompatibleInputMode = (selectedInputType: InputType): InputMode => { - if ( - (getPrimaryInputType(props.field.types)?.fieldType !== "REPEATABLE_MAP") && - (getPrimaryInputType(props.field.types)?.fieldType !== "REPEATABLE_LIST") - ) return getInputModeFromTypes(selectedInputType); - if (selectedInputType.fieldType !== "EXPRESSION") return getInputModeFromTypes(selectedInputType); - if (!props.field.value || typeof props.field.value === "string") return getInputModeFromTypes(selectedInputType); - return getInputModeFromTypes(getPrimaryInputType(props.field.types)); - } - useEffect(() => { if (!props.field.types || props.field.types.length === 0) { throw new Error("Field types are not defined"); @@ -178,29 +166,9 @@ export const FieldFactory = (props: FieldFactoryProps) => { : [props.field.types[0], props.field.types[props.field.types.length - 1]]; setRenderingEditors(newRenderingTypes); - const isNewField = currentFieldKeyRef.current !== props.field.key; - currentFieldKeyRef.current = props.field.key; - const selectedInputType = getInitialSelectedInputType(); - let initialInputMode: InputMode; - if (isNewField) { - initialInputMode = checkAndReturnCompatibleInputMode(selectedInputType) || InputMode.EXP; - currentInputModeRef.current = initialInputMode; - setInputMode(initialInputMode); - } else { - // Preserve the user's current mode selection when the same field is updated, - // but reset if the current mode is no longer available in the new types. - const isCurrentModeAvailable = newRenderingTypes.some( - t => getInputModeFromTypes(t) === currentInputModeRef.current - ); - if (!isCurrentModeAvailable) { - initialInputMode = checkAndReturnCompatibleInputMode(selectedInputType) || InputMode.EXP; - currentInputModeRef.current = initialInputMode; - setInputMode(initialInputMode); - } else { - initialInputMode = currentInputModeRef.current; - } - } + const initialInputMode = getInputModeFromTypes(selectedInputType) || InputMode.EXP; + setInputMode(initialInputMode); updateFieldTypesSelection(initialInputMode); }, [props.field, props.recordTypeFields]); @@ -221,7 +189,6 @@ export const FieldFactory = (props: FieldFactoryProps) => { }; const handleModeChange = useCallback((mode: InputMode) => { - currentInputModeRef.current = mode; setInputMode(mode); updateFieldTypesSelection(mode); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormArrayEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormArrayEditor.tsx index 2b67391b69..3c07ea508c 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormArrayEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormArrayEditor.tsx @@ -22,7 +22,7 @@ import { Form, FormField, FormFieldEditorProps, FormValues, S, useFormContext, u import { Codicon } from "@wso2/ui-toolkit/lib/components/Codicon/Codicon"; import { ScrollableList, ScrollableListRef } from "@wso2/ui-toolkit/lib/components/ScrollableList/ScrollableList"; import ModeSwitcher from "../ModeSwitcher"; -import { getArraySubFormFieldFromTypes, stringToRawArrayElements, buildStringArray, getRecordTypeFields, mapDiagnosticsServerityToFormSeverity, getPropertyFromFormField } from "./utils"; +import { getArraySubFormFieldFromTypes, stringToRawArrayElements, buildStringArray, getRecordTypeFields, mapDiagnosticsServerityToFormSeverity } from "./utils"; export const FormArrayEditor = (props: FormFieldEditorProps & { onChange: (value: any) => void; @@ -72,16 +72,6 @@ export const FormArrayEditor = (props: FormFieldEditorProps & { const handleSetDiagnosticsInfoChange = (diagnostics: FormDiagnostics) => { const existingDiagnostics = elementDiagnosticsRef.current.filter(d => d.key !== diagnostics.key); elementDiagnosticsRef.current = [...existingDiagnostics, diagnostics]; - setRepeatableFields(prev => prev.map(field => { - if (field.key !== diagnostics.key) return field; - return { - ...field, - diagnostics: diagnostics.diagnostics.map(diag => ({ - message: diag.message, - severity: mapDiagnosticsServerityToFormSeverity(diag.severity) - })) - }; - })); } const handleFormDiagnosticsChange = async (showDiagnostics: boolean, expression: string, key: string, property: Property, setDiagnosticsInfo: (diagnostics: FormDiagnostics) => void, shouldUpdateNode?: boolean, variableType?: string) => { @@ -98,17 +88,6 @@ export const FormArrayEditor = (props: FormFieldEditorProps & { variableType); }; - const handleElementFormValidation = async (data: FormValues, dirtyFields?: any): Promise => { - const key = Object.keys(data)[0]; - if (!key) return true; - const value = data[key]; - const valueField = repeatableFields.find(field => field.key === key); - if (!valueField) return true; - await handleFormDiagnosticsChange(true, value, key, getPropertyFromFormField(valueField), () => { }, false, (props.field.types[0] as any).name); - - return true; - } - const applyDiagnosticsToField = (field: FormField): FormField => { const diagnostics = elementDiagnosticsRef.current.find(diag => diag.key === field.key); if (!diagnostics) return field; @@ -254,7 +233,6 @@ export const FormArrayEditor = (props: FormFieldEditorProps & { onChange={(fieldKey: string, value: any, allValues: FormValues) => { handleFormOnChange(fieldKey, value, allValues, formField.key); }} - onFormValidation={handleElementFormValidation} expressionEditor={{ ...expressionEditor, onCompletionItemSelect: expressionEditor?.onCompletionItemSelect, diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormMapEditorNew.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormMapEditorNew.tsx index f308a4eb50..00064c419d 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormMapEditorNew.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/FormMapEditorNew.tsx @@ -17,13 +17,12 @@ */ import React, { useEffect, useRef, useState } from "react"; -import styled from "@emotion/styled"; -import { FormDiagnostics, InputType, Property } from "@wso2/ballerina-core"; +import { InputType } from "@wso2/ballerina-core"; import { Form, FormValues, S, useFormContext, useModeSwitcherContext, FormField, FormFieldEditorProps } from "../.."; import { Codicon } from "@wso2/ui-toolkit/lib/components/Codicon/Codicon"; import { ScrollableList, ScrollableListRef } from "@wso2/ui-toolkit/lib/components/ScrollableList/ScrollableList"; import ModeSwitcher from "../ModeSwitcher"; -import { getMapSubFormFieldFromTypes, buildStringMap, stringToRawObjectEntries, getRecordTypeFields, mapDiagnosticsServerityToFormSeverity, getPropertyFromFormField } from "./utils"; +import { getMapSubFormFieldFromTypes, buildStringMap, stringToRawObjectEntries, getRecordTypeFields } from "./utils"; export const FormMapEditorNew = (props: FormFieldEditorProps & { onChange: (value: any) => void; @@ -31,44 +30,11 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { }) => { const [repeatableFields, setRepeatableFields] = useState([]); const scrollableListRef = useRef(null); - const elementDiagnosticsRef = useRef([]); + const isInternalUpdate = useRef(false); const { expressionEditor } = useFormContext(); const modeSwitcherContext = useModeSwitcherContext(); - const WarningBanner = styled.div` - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - font-size: 11px; - color: var(--vscode-editorWarning-foreground, #cca700); - background: var(--vscode-inputValidation-warningBackground); - margin-top: 6px; - margin-bottom: 6px; - margin-left: 6px; - margin-right: 6px; - box-sizing: border-box; - border-radius: 3px; - `; - - const dedupeFields = (fields: FormField[][]) => { - const seen = new Set(); - const result: FormField[][] = []; - fields.forEach((f) => { - const keyVal = f[0].value as string; - if (!keyVal) { - result.push(f); - return; - } - if (!seen.has(keyVal)) { - seen.add(keyVal); - result.push(f); - } - }); - return result; - }; - const processToOutputFormat = (fields: FormField[][]): Record => { const output: Record = {}; fields.forEach((field) => { @@ -102,60 +68,12 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { return fields; } - const makeDiagnosticsKey = (diagnostics?: any[]) => { - if (!Array.isArray(diagnostics) || diagnostics.length === 0) return ""; - return diagnostics.map(d => `${d.message}|${d.severity}`).join("||"); - }; - - const handleSetDiagnosticsInfoChange = (diagnostics: FormDiagnostics) => { - const existingDiagnostics = elementDiagnosticsRef.current.filter(d => d.key !== diagnostics.key); - elementDiagnosticsRef.current = [...existingDiagnostics, diagnostics]; - setRepeatableFields(prev => prev.map(fieldPair => { - const valueField = fieldPair[1]; - if (valueField.key !== diagnostics.key) return fieldPair; - return [ - fieldPair[0], - { - ...valueField, - diagnostics: diagnostics.diagnostics.map(diag => ({ - message: diag.message, - severity: mapDiagnosticsServerityToFormSeverity(diag.severity) - })) - } - ]; - })); - }; - - const handleFormDiagnosticsChange = async (showDiagnostics: boolean, expression: string, key: string, property: Property, setDiagnosticsInfo: (diagnostics: FormDiagnostics) => void, shouldUpdateNode?: boolean, variableType?: string) => { - return expressionEditor?.getExpressionFormDiagnostics?.( - showDiagnostics, - expression, - key, - property, - (diagnostics: FormDiagnostics) => { - handleSetDiagnosticsInfoChange(diagnostics); - setDiagnosticsInfo(diagnostics); - }, - shouldUpdateNode, - variableType - ); - }; - - const handleElementFormValidation = async (data: FormValues, _dirtyFields?: any): Promise => { - const valueFieldKey = Object.keys(data).find(k => k.startsWith('mp-val-')); - if (!valueFieldKey) return true; - const value = data[valueFieldKey]; - const valueField = repeatableFields.flat().find(field => field.key === valueFieldKey); - if (!valueField) return true; - handleFormDiagnosticsChange(true, value, valueFieldKey, getPropertyFromFormField(valueField), () => { }, false, (props.field.types[0] as any).name); - return true; - }; - const handleAddNewItem = () => { const key = crypto.randomUUID(); if (!(props.field.types[0] as any).template) return; const newField = getMapSubFormFieldFromTypes(key, (props.field.types[0] as any).template.types as InputType[]) setRepeatableFields(prev => [...prev, newField]); + isInternalUpdate.current = true; // Wait for the dom update setTimeout(() => { scrollableListRef.current?.scrollToBottom(); @@ -174,28 +92,26 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { return formFields; }); setRepeatableFields(newRepeatableFields); + isInternalUpdate.current = true; props.onChange(processToOutputFormat(newRepeatableFields)); } const handleModeSwitchValueChange = () => { - const deduped = dedupeFields(repeatableFields); - if (deduped.length !== repeatableFields.length) { - setRepeatableFields(deduped); - } - const stringValue = buildStringMap(deduped); + const stringValue = buildStringMap(repeatableFields); props.onChange(stringValue); } const handleDeleteItem = (keyToDelete: string) => { const newRepeatableFields = repeatableFields.filter((formField) => formField[0].key !== keyToDelete); setRepeatableFields(newRepeatableFields); + isInternalUpdate.current = true; props.onChange(processToOutputFormat(newRepeatableFields)); }; useEffect(() => { if (!props.value) return; - if (typeof props.value !== 'string' && - JSON.stringify(props.value) === JSON.stringify(processToOutputFormat(repeatableFields))) { + if (isInternalUpdate.current) { + isInternalUpdate.current = false; return; } let processedInputValue: string | FormField[][] = ""; @@ -204,17 +120,6 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { } else { processedInputValue = processToInputFormat(props.value); } - // Build diagnostics lookup from props.field.value (populated by setDiagnosticsToFields) - const diagnosticsMap: Record = {}; - if (props.field.value && typeof props.field.value === 'object' && !Array.isArray(props.field.value)) { - Object.entries(props.field.value as Record).forEach(([entryKey, entryVal]) => { - if (entryVal?.diagnostics) { - // diagnostics may be a Diagnostic object { hasDiagnostics, diagnostics: [] } or already a flat array - const diags = entryVal.diagnostics; - diagnosticsMap[entryKey] = Array.isArray(diags) ? diags : (diags?.diagnostics ?? []); - } - }); - } let newValue = buildStringMap(processedInputValue); const initialValues = stringToRawObjectEntries(newValue); const initialFields = initialValues.map((val) => { @@ -222,59 +127,11 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { const fields = getMapSubFormFieldFromTypes(key, (props.field.types[0] as any).template.types as InputType[]); fields[0].value = val.key; fields[1].value = val.value; - if (diagnosticsMap[val.key]) { - fields[1].diagnostics = diagnosticsMap[val.key]; - } return fields; }); setRepeatableFields(initialFields); }, [props.value, props.field.types]); - // When the server responds with updated diagnostics (e.g. after flowDesignService/diagnostics), - // props.field.value is updated asynchronously after the MAP editor has already rendered. - // This effect applies the new per-entry diagnostics to existing repeatableFields without - // recreating the whole list (which would discard in-progress edits). - useEffect(() => { - if (!props.field.value || typeof props.field.value !== 'object' || Array.isArray(props.field.value)) return; - - const diagnosticsMap: Record = {}; - Object.entries(props.field.value as Record).forEach(([entryKey, entryVal]) => { - if (entryVal?.diagnostics) { - const diags = entryVal.diagnostics; - diagnosticsMap[entryKey] = Array.isArray(diags) ? diags : (diags?.diagnostics ?? []); - } - }); - - setRepeatableFields(prev => { - if (prev.length === 0) return prev; - return prev.map(fieldPair => { - const keyVal = fieldPair[0].value as string; - if (!keyVal || diagnosticsMap[keyVal] === undefined) return fieldPair; - // Don't overwrite diagnostics that have already been locally managed - // (e.g. cleared or updated via an inner value field mode change). - const isLocallyManaged = elementDiagnosticsRef.current.some(d => d.key === fieldPair[1].key); - if (isLocallyManaged) return fieldPair; - return [ - fieldPair[0], - { ...fieldPair[1], diagnostics: diagnosticsMap[keyVal] } - ]; - }); - }); - }, [props.field.value]); - - const duplicateEntryKeys = new Set(); - const seenKeyValues = new Set(); - repeatableFields.forEach((formField) => { - const keyVal = formField[0].value as string; - if (keyVal) { - if (seenKeyValues.has(keyVal)) { - duplicateEntryKeys.add(formField[0].key); - } else { - seenKeyValues.add(keyVal); - } - } - }); - return ( @@ -310,61 +167,41 @@ export const FormMapEditorNew = (props: FormFieldEditorProps & { itemCount={repeatableFields.length} maxVisibleItems={2} > - {repeatableFields.map((formField) => { - const keyVal = formField[0].value as string; - const isDuplicate = duplicateEntryKeys.has(formField[0].key); - return ( - -
- handleDeleteItem(formField[0].key)} - /> -
-
{ - handleFormOnChange(fieldKey, value, allValues, formField[0].key); - }} - onFormValidation={handleElementFormValidation} - expressionEditor={{ - ...expressionEditor, - onCompletionItemSelect: expressionEditor?.onCompletionItemSelect, - getHelperPane: expressionEditor?.getHelperPane, - types: expressionEditor?.types, - referenceTypes: expressionEditor?.referenceTypes, - retrieveVisibleTypes: expressionEditor?.retrieveVisibleTypes, - getTypeHelper: expressionEditor?.getTypeHelper, - helperPaneHeight: expressionEditor?.helperPaneHeight, - getExpressionFormDiagnostics: handleFormDiagnosticsChange, - }} - submitText={'Save'} - nestedForm={true} - preserveOrder={true} + { + repeatableFields.map((formField) => ( + +
+ handleDeleteItem(formField[0].key)} /> - {isDuplicate && ( - - - Duplicate key "{keyVal}" — this entry will be ignored - - )} - - ); - })} +
+ { + handleFormOnChange(fieldKey, value, allValues, formField[0].key); + }} + expressionEditor={{ + ...expressionEditor, + onCompletionItemSelect: expressionEditor?.onCompletionItemSelect, + getHelperPane: expressionEditor?.getHelperPane, + types: expressionEditor?.types, + referenceTypes: expressionEditor?.referenceTypes, + retrieveVisibleTypes: expressionEditor?.retrieveVisibleTypes, + getTypeHelper: expressionEditor?.getTypeHelper, + helperPaneHeight: expressionEditor?.helperPaneHeight + }} + submitText={'Save'} + nestedForm={true} + preserveOrder={true} + /> +
+ + ))} child.type !== "GROUP_SECTION") || []; - const nestedGroupChildren = field.advanceProps?.filter(child => child.type === "GROUP_SECTION") || []; - const collapsedFields = nestedGroupChildren.flatMap(group => group.advanceProps || []); - const nestedGroupLabel = nestedGroupChildren[0]?.label; - - return ( - - {regularChildren.map((childField) => ( - - ))} - {collapsedFields.length > 0 && ( - - {nestedGroupLabel} - - {!showAdvancedOptions && ( - setShowAdvancedOptions(true)} - sx={{ fontSize: 12, padding: 8, color: ThemeColors.PRIMARY, gap: 4 }} - > - - Expand - - )} - {showAdvancedOptions && ( - setShowAdvancedOptions(false)} - sx={{ fontSize: 12, padding: 8, color: ThemeColors.PRIMARY, gap: 4 }} - > - - Collapse - - )} - - - )} - {showAdvancedOptions && collapsedFields.map((childField) => ( - - ))} - - ); -} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx index f10f2ce802..704e86cd08 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx @@ -92,9 +92,6 @@ export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfi if (value === '') { return value; } - if (value.trim().startsWith('"') && value.trim().endsWith('"')) { - return `${prefix}${value.trim().slice(1, -1)}${suffix}`; - } if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { return value; } @@ -105,10 +102,7 @@ export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfi if (!expValue) return true; const suffix = this.getSerializationSuffix(); const prefix = this.getSerializationPrefix(); - return ( - (expValue.trim().startsWith(prefix) && expValue.trim().endsWith(suffix)) || - (expValue.trim().startsWith('"') && expValue.trim().endsWith('"')) - ) + return (expValue.trim().startsWith(prefix) && expValue.trim().endsWith(suffix)) } } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ConnectionSelectEditor/ConnectionSelectEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ConnectionSelectEditor/ConnectionSelectEditor.tsx index dc0b0da718..19b26a91d2 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ConnectionSelectEditor/ConnectionSelectEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ConnectionSelectEditor/ConnectionSelectEditor.tsx @@ -18,10 +18,10 @@ import React, { useEffect, useState } from "react"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; -import { CodeData } from "@wso2/ballerina-core"; import { FormField } from "../../../Form/types"; import { ConnectionIconSelect, ConnectionSelectItem } from "../../ConnectionIconSelect"; import { useFormContext } from "../../../../context"; +import { LinkButton } from "@wso2/ui-toolkit/lib/components/LinkButton/LinkButton"; interface ConnectionSelectEditorProps { value: string; @@ -29,37 +29,12 @@ interface ConnectionSelectEditorProps { onChange: (value: string, cursorPosition: number) => void; } -// Cache icon URLs by module name across remounts to avoid icon flicker -const iconUrlCache = new Map(); -// Cache fetched items by searchNodesKind across remounts to avoid redundant API calls -const itemsCache = new Map(); - -function enrichWithCachedIcons(items: ConnectionSelectItem[]): ConnectionSelectItem[] { - return items.map(item => { - const module = item.codedata?.module; - const cachedUrl = module ? iconUrlCache.get(module) : undefined; - return cachedUrl && !item.iconUrl ? { ...item, iconUrl: cachedUrl } : item; - }); -} - -function ensureValueInItems( - items: ConnectionSelectItem[], - value: string, - searchNodesKind?: string, -): ConnectionSelectItem[] { - if (!value || items.some(item => item.value === value)) { - return items; - } - return [ - ...items, - { - id: value, - label: value, - value, - codedata: searchNodesKind ? { node: searchNodesKind } as CodeData : undefined, - }, - ]; -} +const actionButtonStyles = { + padding: "4px 6px", + margin: 0, + marginTop: "6px", + fontSize: "13px", +}; export const ConnectionSelectEditor: React.FC = ({ value, field, onChange }) => { const { rpcClient } = useRpcContext(); @@ -67,20 +42,10 @@ export const ConnectionSelectEditor: React.FC = ({ const searchNodesKind = field.codedata?.searchNodesKind; const initialItems: ConnectionSelectItem[] = field.codedata?.initialItems ?? []; - const staticItems: ConnectionSelectItem[] = field.codedata?.staticItems ?? []; - const cachedItems = searchNodesKind ? itemsCache.get(searchNodesKind) : undefined; - const resolvedItems = [...staticItems, ...(cachedItems ?? enrichWithCachedIcons(initialItems))]; - const [selectItems, setSelectItems] = useState( - ensureValueInItems(resolvedItems, value, searchNodesKind) - ); - const [loading, setLoading] = useState(!!searchNodesKind && !cachedItems); + const [selectItems, setSelectItems] = useState(initialItems); - const fetchItems = () => { + useEffect(() => { if (!searchNodesKind) return; - // Show loading only if we have no cached items to display - if (!itemsCache.has(searchNodesKind)) { - setLoading(true); - } rpcClient.getBIDiagramRpcClient().searchNodes({ filePath: fileName, position: targetLineRange.startLine, @@ -89,42 +54,16 @@ export const ConnectionSelectEditor: React.FC = ({ const nodes = response?.output ?? []; const items: ConnectionSelectItem[] = nodes .filter(node => node.properties?.variable?.value) - .map(node => { - const iconUrl = node.metadata?.icon; - const module = node.codedata?.module; - if (iconUrl && module) { - iconUrlCache.set(module, iconUrl); - } - return { - id: String(node.properties.variable.value), - label: node.properties.variable.value as string, - value: String(node.properties.variable.value), - codedata: node.codedata, - iconUrl, - }; - }); - itemsCache.set(searchNodesKind, items); - setSelectItems([...staticItems, ...items]); - }).finally(() => { - setLoading(false); + .map(node => ({ + id: String(node.properties.variable.value), + label: node.properties.variable.value as string, + value: String(node.properties.variable.value), + codedata: node.codedata, + })); + setSelectItems(items); }); - }; - - useEffect(() => { - fetchItems(); }, [searchNodesKind, fileName]); - // When value changes to something not in the current items (e.g. after creating - // a new connection via an overlay), inject a placeholder and re-fetch - useEffect(() => { - if (!value || selectItems.some(item => item.value === value)) return; - setSelectItems(prev => ensureValueInItems(prev, value, searchNodesKind)); - if (searchNodesKind) { - itemsCache.delete(searchNodesKind); - } - fetchItems(); - }, [value]); - return ( <> = ({ value={value} required={!field.optional} disabled={!field.editable} - loading={loading} onChange={(val) => onChange(val, val?.length)} /> diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx index c6fe9cab75..af2cf783cd 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx @@ -485,10 +485,6 @@ export const RichTextTemplateEditor: React.FC = ({ const serialized = customMarkdownSerializer.serialize(view.state.doc); const newEditorValue = sanitizeText(configuration.deserializeValue(serialized)); onChange?.(newEditorValue, cursorPosition); - - // Explicitly trigger token fetch to convert inserted text to chips - pendingTokenFetchRef.current = true; - fetchAndUpdateTokens(view); }; const fetchAndUpdateTokens = async (editorView: EditorView) => { diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/plugins/chipPlugin.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/plugins/chipPlugin.ts index 00edcaf059..7cce9d62ae 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/plugins/chipPlugin.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/plugins/chipPlugin.ts @@ -204,10 +204,10 @@ function createChipElement( return span; } -function findDocPosition(doc: any, textOffset: number, bias: 'start' | 'end' = 'end'): number { +function findDocPosition(doc: any, textOffset: number): number { // Clamp offset to valid range if (textOffset <= 0) return 0; - if (textOffset > doc.textContent.length) return doc.content.size; + if (textOffset >= doc.textContent.length) return doc.content.size; let charCount = 0; let docPos = 0; @@ -219,13 +219,7 @@ function findDocPosition(doc: any, textOffset: number, bias: 'start' | 'end' = ' if (node.isText) { const textLength = node.text.length; - // 'start' bias: prefer next text node at paragraph boundaries - // 'end' bias: stay in current text node - const found = bias === 'start' - ? charCount + textLength > textOffset - : charCount + textLength >= textOffset; - - if (found) { + if (charCount + textLength >= textOffset) { // This text node contains our target offset docPos = pos + (textOffset - charCount); return false; @@ -291,8 +285,8 @@ function replaceTextWithChips( diagnostic: null }); - const startDocPos = findDocPosition(tr.doc, compound.start, 'start'); - const endDocPos = findDocPosition(tr.doc, compound.end, 'end'); + const startDocPos = findDocPosition(tr.doc, compound.start); + const endDocPos = findDocPosition(tr.doc, compound.end); replacements.push({ from: startDocPos, to: endDocPos, node: chipNode }); } @@ -329,8 +323,8 @@ function replaceTextWithChips( diagnostic: null }); - const startDocPos = findDocPosition(tr.doc, token.start, 'start'); - const endDocPos = findDocPosition(tr.doc, token.end, 'end'); + const startDocPos = findDocPosition(tr.doc, token.start); + const endDocPos = findDocPosition(tr.doc, token.end); replacements.push({ from: startDocPos, to: endDocPos, node: chipNode }); } @@ -378,11 +372,6 @@ export function createChipPlugin( } } - // Clear stale chip data on user edits so chips get re-created at correct positions - if ((tr as any).docChanged && tr.getMeta('addToHistory') !== false) { - return { tokenUpdate: null, lastProcessedTokens: null }; - } - return value; } }, diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx index 93710830fc..e94addbfe3 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx @@ -46,7 +46,7 @@ export const TextModeEditor: React.FC = (pro isExpandedVersion={false} completions={props.completions} onChange={props.onChange} - value={props.value} + value={props.value != null ? (props.configuration?.deserializeValue(getValueForTextModeEditor(props.value)) ?? getValueForTextModeEditor(props.value)) : undefined} sanitizedExpression={props.sanitizedExpression} rawExpression={props.rawExpression} fileName={props.fileName} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ReadonlyField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ReadonlyField.tsx index 49782411ae..5b8313de27 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ReadonlyField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ReadonlyField.tsx @@ -28,6 +28,7 @@ interface ReadonlyFieldProps { const Container = styled.div` width: 100%; + cursor: not-allowed; `; const Label = styled.div` @@ -59,7 +60,6 @@ const InputContainer = styled.div` min-width: var(--input-min-width); margin-top: 10px; overflow: hidden; - cursor: not-allowed; `; const ExpressionRibbon = styled.div` diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/TypeEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/TypeEditor.tsx index 82bb552161..babbc64b2d 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/TypeEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/TypeEditor.tsx @@ -156,6 +156,8 @@ export function TypeEditor(props: TypeEditorProps) { // Trigger actions on blur await onBlur?.(); setShowDefaultCompletion(undefined); + // Clean up memory + cursorPositionRef.current = undefined; // Trigger the on Blur from parent await props.onBlur?.(); }; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/utils.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/utils.ts index 7e3f9f2163..10736de5b3 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/utils.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/utils.ts @@ -179,7 +179,7 @@ export const getMapSubFormFieldFromTypes = (formId: string, types: InputType[]): editable: true, documentation: "", value: "", - types: types.map(type => ({ ...type })), + types: types, enabled: true } ] diff --git a/workspaces/ballerina/ballerina-side-panel/src/index.ts b/workspaces/ballerina/ballerina-side-panel/src/index.ts index b883b1ea9e..b55e26ca41 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/index.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/index.ts @@ -25,7 +25,6 @@ export * from "./components/editors"; export * from "./components/GroupList"; export * from "./components/ParamManager/ParamManager"; export * from "./components/CardList"; -export * from "./components/Skeletons"; export * from "./context"; export * from "./utils/path-validations"; diff --git a/workspaces/ballerina/ballerina-visualizer/package.json b/workspaces/ballerina/ballerina-visualizer/package.json index 419250e953..8aadf6d3eb 100644 --- a/workspaces/ballerina/ballerina-visualizer/package.json +++ b/workspaces/ballerina/ballerina-visualizer/package.json @@ -14,7 +14,7 @@ "build": "webpack --config webpack.config.js --mode=production && pnpm run postbuild", "copy:assets": "copyfiles -u 1 \"src/**/*.scss\" \"src/**/*.svg\" \"src/**/*.css\" \"src/resources/assets/font/*.*\" lib/", "deploy": "npm publish", - "postbuild": "node ./scripts/clean-jslibs.js && copyfiles -u 1 -V build/*.js build/fonts/* build/images/* -e build/*.txt ../ballerina-extension/resources/jslibs" + "postbuild": "find ../ballerina-extension/resources/jslibs -maxdepth 1 -type f -name '[0-9]*.js' -delete && copyfiles -u 1 -V build/*.js build/fonts/* build/images/* -e build/*.txt ../ballerina-extension/resources/jslibs" }, "keywords": [], "dependencies": { diff --git a/workspaces/ballerina/ballerina-visualizer/scripts/clean-jslibs.js b/workspaces/ballerina/ballerina-visualizer/scripts/clean-jslibs.js deleted file mode 100644 index 24a655f268..0000000000 --- a/workspaces/ballerina/ballerina-visualizer/scripts/clean-jslibs.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -const fs = require('fs').promises; -const path = require('path'); - -async function main() { - const targetDir = path.resolve(__dirname, '..', '..', 'ballerina-extension', 'resources', 'jslibs'); - - try { - await fs.access(targetDir); - } catch (e) { - if (e.code === 'ENOENT') { - console.log(`Target jslibs directory not found (${targetDir}), skipping cleanup.`); - return; - } - console.error(`Cannot access jslibs directory (${targetDir}):`, e); - process.exitCode = 1; - return; - } - - try { - const entries = await fs.readdir(targetDir, { withFileTypes: true }); - const deletes = entries - .filter((d) => d.isFile() && /^.+\.[0-9a-f]{8}\.js$/.test(d.name)) - .map((d) => fs.unlink(path.join(targetDir, d.name))); - - if (deletes.length === 0) { - console.log('No versioned JS files to remove.'); - return; - } - - await Promise.all(deletes); - console.log(`Removed ${deletes.length} versioned JS file(s) from ${targetDir}`); - } catch (err) { - console.error('Error cleaning jslibs directory:', err); - process.exitCode = 1; - } -} - -main(); diff --git a/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx b/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx index 79762b3ab2..9c4d631272 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx @@ -70,16 +70,7 @@ export const useDataMapperModel = ( isError, refetch } = useQuery({ - queryKey: [ - 'getDMModel', - filePath, - codedata?.sourceCode, - codedata?.lineRange?.startLine, - codedata?.lineRange?.endLine, - viewId, - viewState?.subMappingName, - position - ], + queryKey: ['getDMModel', codedata, viewId], queryFn: getDMModel, networkMode: 'always' }); diff --git a/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx b/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx index dd595b3198..b62225e9d9 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx @@ -20,13 +20,11 @@ import React, { Suspense, useEffect } from "react"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { AIMachineStateValue, MachineStateValue } from "@wso2/ballerina-core"; import styled from '@emotion/styled'; - import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; import { Global, css } from '@emotion/react'; import { DownloadIcon } from "./components/DownloadIcon"; import { WebviewErrorBoundary } from "./components/WebviewErrorBoundary"; import { ThemeColors } from "@wso2/ui-toolkit"; -import { LoadingRing } from "./components/Loader"; const MainPanel = React.lazy(() => import("./MainPanel")); const AIPanel = React.lazy(() => import("./views/AIPanel/AIPanel")); @@ -39,9 +37,6 @@ const EvaluationHistory = React.lazy(() => const EvaluationReport = React.lazy(() => import("./views/EvaluationReport/EvaluationReport").then((module) => ({ default: module.EvaluationReport })) ); -const MigrationPanel = React.lazy(() => - import("./views/MigrationPanel/MigrationPanel").then((module) => ({ default: module.MigrationPanel })) -); const ProgressRing = styled(VSCodeProgressRing)` height: 36px; @@ -104,9 +99,8 @@ const MODES = { AI: "ai", RUNTIME_SERVICES: "runtime-services", AGENT_CHAT: "agent-chat", - MIGRATION: "migration", EVALUATION_HISTORY: "evaluation-history", - EVALUATION_REPORT: "evaluation-report", + EVALUATION_REPORT: "evaluation-report" }; export function Visualizer({ mode }: { mode: string }) { @@ -146,11 +140,9 @@ export function Visualizer({ mode }: { mode: string }) { case MODES.RUNTIME_SERVICES: return case MODES.AI: - return }> + return case MODES.AGENT_CHAT: return - case MODES.MIGRATION: - return case MODES.EVALUATION_HISTORY: return case MODES.EVALUATION_REPORT: diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/ConnectionConfig.tsx b/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/ConnectionConfig.tsx index 348ca8ee07..16eb531bd1 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/ConnectionConfig.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/ConnectionConfig.tsx @@ -17,10 +17,10 @@ */ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { FlowNode, LineRange, NodeKind, NodeProperties, getPrimaryInputType } from "@wso2/ballerina-core"; +import { FlowNode, LineRange, NodeKind, NodeProperties } from "@wso2/ballerina-core"; import { FormField, FormImports, FormValues } from "@wso2/ballerina-side-panel"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; -import { ArtifactForm } from "../../views/BI/Forms/ArtifactForm"; +import { FormGeneratorNew } from "../../views/BI/Forms/FormGeneratorNew"; import { RelativeLoader } from "../RelativeLoader"; import { InfoBox } from "../InfoBox"; import { ConnectionConfigProps } from "./types"; @@ -101,7 +101,7 @@ export function ConnectionConfig(props: ConnectionConfigProps): JSX.Element { const { variable, ...restProperties } = connectionNode.properties; const fields = convertNodePropertiesToFormFields(restProperties as NodeProperties); fields.forEach(field => { - if (getPrimaryInputType(field.types)?.fieldType === "TYPE") { + if (field.key === "type") { field.hidden = true; } }); @@ -213,7 +213,7 @@ export function ConnectionConfig(props: ConnectionConfigProps): JSX.Element { )} {!loading && selectedConnectionFields?.length > 0 && ( <> - ([]); - const [recordTypeFields, setRecordTypeFields] = useState([]); const [loading, setLoading] = useState(false); const [savingForm, setSavingForm] = useState(false); @@ -74,27 +73,6 @@ export function ConnectionCreator(props: ConnectionCreatorProps): JSX.Element { if (nodeFormTemplate && nodeFormTemplate.properties) { const fields = convertConfig(nodeFormTemplate.properties); setConnectionFields(fields); - - const rtFields: RecordTypeField[] = Object.entries(nodeFormTemplate.properties) - .filter( - ([_, property]) => - getPrimaryInputType(property?.types)?.typeMembers && - getPrimaryInputType(property?.types)?.typeMembers.some((member: PropertyTypeMemberInfo) => member.kind === "RECORD_TYPE") - ) - .map(([key, property]) => ({ - key, - property: { - ...property, - metadata: { - label: property.metadata?.label || key, - description: property.metadata?.description || "", - }, - }, - recordTypeMembers: getPrimaryInputType(property.types)?.typeMembers.filter( - (member: PropertyTypeMemberInfo) => member.kind === "RECORD_TYPE" - ) || [], - })); - setRecordTypeFields(rtFields); } setLoading(false); }; @@ -117,7 +95,7 @@ export function ConnectionCreator(props: ConnectionCreatorProps): JSX.Element { updateNodeWithConnectionVariable(connectionKind, selectedNode, nodeTemplate?.properties?.variable?.value as string); // Update the line range for the selected node if it was updated updateNodeLineRange(selectedNode, response.artifacts); - onSave?.(selectedNode, response.artifacts); + onSave?.(selectedNode); } catch (error) { console.error(`>>> Error creating ${connectionKind}`, error); } @@ -132,7 +110,7 @@ export function ConnectionCreator(props: ConnectionCreatorProps): JSX.Element { )} {!loading && connectionFields?.length > 0 && ( <> - ({ query: "", - searchKind: "SHORT_TERM_MEMORY_STORE" + searchKind: "MEMORY_STORE" }) } }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/types.ts b/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/types.ts index f3b149f74b..4d38b4bc74 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/types.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/components/ConnectionSelector/types.ts @@ -16,9 +16,9 @@ * under the License. */ -import { FlowNode, InputType, ProjectStructureArtifactResponse, SearchKind } from "@wso2/ballerina-core"; +import { FlowNode, InputType, SearchKind } from "@wso2/ballerina-core"; -export type ConnectionKind = 'MODEL_PROVIDER' | 'VECTOR_STORE' | 'EMBEDDING_PROVIDER' | 'CHUNKER' | 'SHORT_TERM_MEMORY_STORE'; +export type ConnectionKind = 'MODEL_PROVIDER' | 'VECTOR_STORE' | 'EMBEDDING_PROVIDER' | 'CHUNKER' | 'MEMORY_STORE'; export interface ConnectionKindConfig { displayName: string; @@ -62,5 +62,5 @@ export interface ConnectionCreatorProps { connectionKind: ConnectionKind; selectedNode?: FlowNode; nodeFormTemplate?: FlowNode; - onSave?: (connectionNode: FlowNode, artifacts?: ProjectStructureArtifactResponse[]) => void; + onSave?: (connectionNode: FlowNode) => void; } diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/EditableTitle/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/components/EditableTitle/index.tsx deleted file mode 100644 index c803d1c326..0000000000 --- a/workspaces/ballerina/ballerina-visualizer/src/components/EditableTitle/index.tsx +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import React, { ReactNode, useCallback, useRef, useState } from "react"; -import styled from "@emotion/styled"; -import { Codicon } from "@wso2/ui-toolkit"; - -const EditableTitleWrapper = styled.div` - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - outline: none; - - &:is(:hover, :focus-visible) .edit-title-icon-wrapper { - opacity: 1; - max-width: 28px; - } -`; - -const EditTitleIconWrapper = styled.div` - opacity: 0; - max-width: 0; - overflow: hidden; - flex-shrink: 0; - transition: opacity 0.2s, max-width 0.2s; - display: flex; - align-items: center; -`; - -const TitleInput = styled.input<{ $hasError?: boolean }>` - font-weight: bold; - font-size: 1.5rem; - margin-bottom: 0; - margin-top: 0; - background: transparent; - border: none; - border-bottom: 2px solid ${({ $hasError }: { $hasError?: boolean }) => $hasError - ? 'var(--vscode-inputValidation-errorBorder)' - : 'var(--vscode-focusBorder)'}; - color: var(--vscode-foreground); - outline: none; - padding: 0; - font-family: inherit; - min-width: 120px; - width: auto; - @media (min-width: 768px) { - font-size: 1.875rem; - } -`; - -const TitleValidationMessage = styled.div` - display: flex; - align-items: center; - gap: 4px; - font-size: 0.72rem; - color: var(--vscode-inputValidation-errorForeground, var(--vscode-errorForeground)); - background: var(--vscode-inputValidation-errorBackground, transparent); - border: 1px solid var(--vscode-inputValidation-errorBorder); - border-radius: 2px; - padding: 2px 6px; - position: absolute; - top: calc(100% + 4px); - left: 0; - white-space: nowrap; - z-index: 10; -`; - -interface EditableTitleProps { - /** The current title value (used to seed the input and detect no-op edits). */ - title: string; - /** Called with the new title when the user commits. Throw to keep edit mode open. */ - onCommit: (newTitle: string) => Promise; - /** Returns an error message for the given value, or "" if valid. */ - validate?: (value: string) => string; - /** Optional style overrides for the input (e.g. to match a specific font size). */ - inputStyle?: React.CSSProperties; - /** The title element rendered in non-editing state (e.g. ). */ - children: ReactNode; -} - -export function EditableTitle({ title, onCommit, validate, inputStyle, children }: EditableTitleProps) { - const [isEditing, setIsEditing] = useState(false); - const [inputValue, setInputValue] = useState(""); - const [error, setError] = useState(""); - const inputRef = useRef(null); - const wrapperRef = useRef(null); - - const restoreFocus = () => setTimeout(() => { wrapperRef.current?.focus(); }, 0); - - const startEditing = useCallback(() => { - setInputValue(title); - setError(""); - setIsEditing(true); - setTimeout(() => { inputRef.current?.select(); }, 0); - }, [title]); - - const commitEdit = useCallback(async () => { - const trimmed = inputValue.trim(); - const currentError = validate ? validate(trimmed) : ""; - if (!trimmed || currentError) { - setError(""); - setIsEditing(false); - restoreFocus(); - return; - } - if (trimmed === title) { - setIsEditing(false); - restoreFocus(); - return; - } - try { - await onCommit(trimmed); - setIsEditing(false); - restoreFocus(); - } catch { - // Keep edit mode open so the user can correct or cancel. - } - }, [inputValue, title, onCommit, validate]); - - const handleKeyDown = useCallback((e: React.KeyboardEvent) => { - if (e.key === "Enter") { - e.preventDefault(); - e.currentTarget.blur(); - } else if (e.key === "Escape") { - setError(""); - setIsEditing(false); - restoreFocus(); - } - }, []); - - return ( -
- {isEditing ? ( - <> - { - setInputValue(e.target.value); - setError(validate ? validate(e.target.value) : ""); - }} - onKeyDown={handleKeyDown} - onBlur={commitEdit} - autoFocus - /> - {error && ( - - - {error} - - )} - - ) : ( - { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - startEditing(); - } - }} - title="Click to edit name" - aria-label="Edit name" - > - {children} - - - - - )} -
- ); -} diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/EntryPointTypeCreator/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/components/EntryPointTypeCreator/index.tsx index f5a7c59898..dcf847a13e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/EntryPointTypeCreator/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/components/EntryPointTypeCreator/index.tsx @@ -23,36 +23,35 @@ import styled from "@emotion/styled"; import DynamicModal from "../../components/Modal"; import { EditorContext, StackItem } from "@wso2/type-editor"; -import { BreadcrumbContainer, BreadcrumbItem, BreadcrumbSeparator } from "../../views/BI/Forms/FlowNodeForm"; import { FormTypeEditor } from "../../views/BI/TypeEditor"; export const Title = styled.div` color: ${ThemeColors.ON_SURFACE}; `; -// const BreadcrumbContainer = styled.div` -// display: flex; -// align-items: center; -// gap: 8px; -// padding: 8px 20px; -// background: ${ThemeColors.SURFACE_CONTAINER}; -// border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -// `; - -// const BreadcrumbItem = styled.span` -// color: ${ThemeColors.ON_SURFACE_VARIANT}; -// font-size: var(--vscode-font-size); +const BreadcrumbContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: ${ThemeColors.SURFACE_CONTAINER}; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const BreadcrumbItem = styled.span` + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-size: var(--vscode-font-size); -// &:last-child { -// color: ${ThemeColors.ON_SURFACE}; -// font-weight: 500; -// } -// `; - -// const BreadcrumbSeparator = styled.span` -// color: ${ThemeColors.ON_SURFACE_VARIANT}; -// font-size: var(--vscode-font-size); -// `; + &:last-child { + color: ${ThemeColors.ON_SURFACE}; + font-weight: 500; + } +`; + +const BreadcrumbSeparator = styled.span` + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-size: var(--vscode-font-size); +`; interface EntryPointTypeCreatorProps { isOpen: boolean; diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/RelativeLoader/styles.ts b/workspaces/ballerina/ballerina-visualizer/src/components/RelativeLoader/styles.ts index 19827383cd..2a07f000a5 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/RelativeLoader/styles.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/components/RelativeLoader/styles.ts @@ -20,7 +20,6 @@ import styled from "@emotion/styled"; export const LoaderContainer = styled.div` display: flex; - flex-direction: column; justify-content: center; align-items: center; height: 100%; diff --git a/workspaces/ballerina/ballerina-visualizer/src/components/TitleBar/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/components/TitleBar/index.tsx index d16bd4b417..a1cee00075 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/components/TitleBar/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/components/TitleBar/index.tsx @@ -16,14 +16,13 @@ * under the License. */ -import React, { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useEffect, useState } from "react"; import styled from "@emotion/styled"; import { Icon } from "@wso2/ui-toolkit"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { BetaSVG } from "../../views/Connectors/Marketplace/BetaSVG"; import { UndoRedoGroup } from "../UndoRedoGroup"; import { MACHINE_VIEW } from "@wso2/ballerina-core"; -import { EditableTitle } from "../EditableTitle"; const TitleBarContainer = styled.div` display: flex; @@ -109,20 +108,10 @@ interface TitleBarProps { hideUndoRedo?: boolean; onBack?: () => void; // Override back functionality isBetaFeature?: boolean; - onTitleEdit?: (newTitle: string) => Promise; - validateTitle?: (value: string) => string; } -// Input style overrides to match the TitleBar's Title styled component (20px, 600 weight). -const TITLE_BAR_INPUT_STYLE: React.CSSProperties = { - fontSize: '20px', - fontWeight: '600', - whiteSpace: 'nowrap', - margin: 0, -}; - export function TitleBar(props: TitleBarProps) { - const { title, subtitle, subtitleElement, actions, hideBack, hideUndoRedo, onBack, isBetaFeature, onTitleEdit, validateTitle } = props; + const { title, subtitle, subtitleElement, actions, hideBack, hideUndoRedo, onBack, isBetaFeature } = props; const { rpcClient } = useRpcContext(); const [isDiagramView, setIsDiagramView] = useState(false); @@ -154,18 +143,7 @@ export function TitleBar(props: TitleBarProps) { )} - {onTitleEdit ? ( - - {title} - - ) : ( - {title} - )} + {title} {subtitle && {subtitle}} {subtitleElement && subtitleElement} diff --git a/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx b/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx index c8d9d0712e..7af0b6146d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx @@ -222,7 +222,6 @@ export function convertFunctionCategoriesToSidePanelCategories( functionType: FUNCTION_TYPE ): PanelCategory[] { const panelCategories = normalizeFunctionSearchCategories(categories) - .filter((category) => category.metadata.label !== "Agent Tools") .map((category) => convertDiagramCategoryToSidePanelCategory(category, functionType)) .filter((category) => category !== undefined); const functionCategory = findCurrentIntegrationCategory(panelCategories); @@ -238,14 +237,12 @@ export function convertModelProviderCategoriesToSidePanelCategories(categories: category.items?.forEach((item) => { if ((item as PanelNode).metadata?.codedata) { const codedata = (item as PanelNode).metadata.codedata; - const iconUrl = (item as PanelNode)?.metadata?.metadata?.icon; const iconType = codedata?.module == "ai" ? codedata.object : codedata?.module; - item.icon = ; + item.icon = ; } else if (((item as PanelCategory).items.at(0) as PanelNode)?.metadata?.codedata) { const codedata = ((item as PanelCategory).items.at(0) as PanelNode)?.metadata.codedata; - const iconUrl = ((item as PanelCategory).items.at(0) as PanelNode)?.metadata?.metadata?.icon; const iconType = codedata?.module == "ai" ? codedata.object : codedata?.module; - item.icon = ; + item.icon = ; } }); }); @@ -253,8 +250,8 @@ export function convertModelProviderCategoriesToSidePanelCategories(categories: } export function convertVectorStoreCategoriesToSidePanelCategories(categories: Category[]): PanelCategory[] { - return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata, iconUrl) => { - return ; + return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata) => { + return ; }); } @@ -263,29 +260,27 @@ export function convertEmbeddingProviderCategoriesToSidePanelCategories(categori } export function convertKnowledgeBaseCategoriesToSidePanelCategories(categories: Category[]): PanelCategory[] { - return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata, iconUrl) => { + return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata) => { if ((codedata?.module as string).includes("azure")) { - return ; + return ; } - return ; + return }); } export function convertCategoriesToSidePanelCategoriesWithIcon( categories: Category[], - iconFactory: (codedata: any, iconUrl?: string) => React.ReactElement + iconFactory: (codedata: any) => React.ReactElement ): PanelCategory[] { const panelCategories = categories.map((category) => convertDiagramCategoryToSidePanelCategory(category)); panelCategories.forEach((category) => { category.items?.forEach((item) => { if ((item as PanelNode).metadata?.codedata) { const codedata = (item as PanelNode).metadata.codedata; - const iconUrl = (item as PanelNode)?.metadata?.metadata?.icon; - item.icon = iconFactory(codedata, iconUrl); + item.icon = iconFactory(codedata); } else if (((item as PanelCategory).items.at(0) as PanelNode)?.metadata?.codedata) { const codedata = ((item as PanelCategory).items.at(0) as PanelNode)?.metadata.codedata; - const iconUrl = ((item as PanelCategory).items.at(0) as PanelNode)?.metadata?.metadata?.icon; - item.icon = iconFactory(codedata, iconUrl); + item.icon = iconFactory(codedata); } }); }); @@ -293,23 +288,21 @@ export function convertCategoriesToSidePanelCategoriesWithIcon( } export function convertDataLoaderCategoriesToSidePanelCategories(categories: Category[]): PanelCategory[] { - return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata, iconUrl) => { - if (iconUrl && codedata?.module !== "ai" && codedata?.module !== "ai.devant") return ; - return ; - }); + return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata) => ( + + )); } export function convertChunkerCategoriesToSidePanelCategories(categories: Category[]): PanelCategory[] { - return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata, iconUrl) => { - if (iconUrl && codedata?.module !== "ai" && codedata?.module !== "ai.devant") return ; - return ; - }); + return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata) => ( + + )); } export function convertMemoryStoreCategoriesToSidePanelCategories(categories: Category[]): PanelCategory[] { - return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata, iconUrl) => { - return ; - }); + return convertCategoriesToSidePanelCategoriesWithIcon(categories, (codedata) => ( + + )); } import { @@ -368,10 +361,8 @@ export function getContainerTitle(view: SidePanelView, activeNode: FlowNode, cli return `Select ${getConnectionDisplayName(connectionKind)}`; case SidePanelView.CONNECTION_CREATE: return `Create ${getConnectionDisplayName(connectionKind)}`; - case SidePanelView.ERROR: + case SidePanelView.CONNECTOR_ERROR: return "Error"; - case SidePanelView.LOADING: - return ""; case SidePanelView.AGENT_MEMORY_MANAGER: return "Configure Memory"; case SidePanelView.AGENT_TOOL: diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/LoginPanel/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/LoginPanel/index.tsx index 39aca6b28f..2dfcee6512 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/LoginPanel/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/LoginPanel/index.tsx @@ -39,11 +39,8 @@ const TopSpacer = styled.div` `; const BottomSpacer = styled.div` - height: 32px; -`; - -const EndSpacer = styled.div` flex-grow: 1; + min-height: 48px; `; const HeaderContent = styled.div` @@ -62,6 +59,7 @@ const FooterContent = styled.div` width: 100%; max-width: 360px; align-self: center; + margin-bottom: 60px; `; const Title = styled.h2` @@ -225,7 +223,6 @@ const LoginPanel: React.FC = () => { Enter your AWS Bedrock credentials Enter your Google Vertex AI credentials - ); }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/ClarifyFooter.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/ClarifyFooter.tsx deleted file mode 100644 index 89cb0f019b..0000000000 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/ClarifyFooter.tsx +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import React, { useState } from "react"; -import styled from "@emotion/styled"; -import { ClarifyQuestion } from "@wso2/ballerina-core"; -import { FooterContainer } from "./index"; -import { ActionButton } from "../../AgentStreamView/styles"; -import { FooterBox, FooterDivider, FooterTextInputRow, FooterInput } from "./styles"; - -// ── Clarify-specific styled components ──────────────────────────────────────── - -const TabRow = styled.div` - display: flex; - gap: 0; - border-bottom: 1px solid var(--vscode-panel-border); - margin-bottom: 4px; -`; - -const Tab = styled.button<{ active: boolean }>` - background: transparent; - border: none; - border-bottom: 2px solid ${({ active }: { active: boolean }) => active ? "var(--vscode-focusBorder)" : "transparent"}; - color: ${({ active }: { active: boolean }) => active ? "var(--vscode-editor-foreground)" : "var(--vscode-descriptionForeground)"}; - font-weight: ${({ active }: { active: boolean }) => active ? 600 : 400}; - font-family: var(--vscode-font-family); - font-size: 12px; - padding: 6px 12px; - cursor: pointer; - margin-bottom: -1px; - white-space: nowrap; - - &:hover:not([data-active="true"]) { - color: var(--vscode-editor-foreground); - } -`; - -const QuestionHeader = styled.div` - display: flex; - align-items: baseline; - flex-wrap: wrap; - gap: 6px; - margin-bottom: 8px; -`; - -const QuestionText = styled.span` - font-size: 13px; - font-weight: 500; - line-height: 1.4; - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); -`; - -const TypeBadge = styled.span` - font-size: 10px; - color: var(--vscode-descriptionForeground); - font-family: var(--vscode-font-family); - opacity: 0.5; - white-space: nowrap; -`; - -const OptionsList = styled.div` - display: flex; - flex-direction: column; - gap: 4px; -`; - -const OptionButton = styled.button<{ selected: boolean }>` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0; - width: 100%; - background: ${({ selected }: { selected: boolean }) => - selected ? "var(--vscode-list-inactiveSelectionBackground, var(--vscode-list-hoverBackground))" : "transparent"}; - border: 1px solid ${({ selected }: { selected: boolean }) => - selected ? "var(--vscode-focusBorder, var(--vscode-input-border))" : "transparent"}; - cursor: pointer; - padding: 7px 10px; - border-radius: 4px; - text-align: left; - - &:hover:not(:disabled) { - background: var(--vscode-list-hoverBackground); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } -`; - -const OptionRow = styled.div` - display: flex; - align-items: center; - gap: 8px; - width: 100%; -`; - -const OptionLabel = styled.span<{ selected: boolean }>` - font-size: 13px; - font-family: var(--vscode-font-family); - font-weight: ${({ selected }: { selected: boolean }) => selected ? 500 : 400}; - color: ${({ selected }: { selected: boolean }) => - selected ? "var(--vscode-editor-foreground)" : "var(--vscode-foreground)"}; -`; - -const OtherInputRow = styled(FooterTextInputRow)` - margin-top: 4px; - margin-left: 18px; - width: calc(100% - 18px); -`; - -// ── Types ───────────────────────────────────────────────────────────────────── - -interface ClarifyFooterProps { - questions: ClarifyQuestion[]; - requestId: string; - rpcClient: any; -} - -// ── Component ───────────────────────────────────────────────────────────────── - -const ClarifyFooter: React.FC = ({ questions, requestId, rpcClient }) => { - const [page, setPage] = useState(0); - const [selections, setSelections] = useState>( - () => Object.fromEntries(questions.map((_, i): [number, string[]] => [i, []])) - ); - const [otherEnabled, setOtherEnabled] = useState>( - () => Object.fromEntries(questions.map((_, i): [number, boolean] => [i, false])) - ); - const [customTexts, setCustomTexts] = useState>( - () => Object.fromEntries(questions.map((_, i): [number, string] => [i, ""])) - ); - const [isSubmitting, setIsSubmitting] = useState(false); - - const q = questions[page]; - const isMulti = q?.selectionType === "multiple"; - - const canProceed = (i: number) => - (selections[i]?.length ?? 0) > 0 || - (otherEnabled[i] && (customTexts[i]?.trim().length ?? 0) > 0); - - const canSubmit = questions.every((_, i) => canProceed(i)); - - const handleSelectOption = (value: string) => { - if (isMulti) { - setSelections(prev => { - const current = prev[page] ?? []; - return { - ...prev, - [page]: current.includes(value) - ? current.filter(v => v !== value) - : [...current, value], - }; - }); - } else { - setSelections(prev => ({ ...prev, [page]: [value] })); - setOtherEnabled(prev => ({ ...prev, [page]: false })); - setCustomTexts(prev => ({ ...prev, [page]: "" })); - if (page < questions.length - 1) { - setTimeout(() => setPage(p => p + 1), 300); - } - } - }; - - const handleToggleOther = () => { - const nowEnabled = !otherEnabled[page]; - setOtherEnabled(prev => ({ ...prev, [page]: nowEnabled })); - if (nowEnabled) { - if (!isMulti) { - setSelections(prev => ({ ...prev, [page]: [] })); - } - } else { - setCustomTexts(prev => ({ ...prev, [page]: "" })); - } - }; - - const handleSubmit = async () => { - if (!canSubmit || isSubmitting) return; - setIsSubmitting(true); - const answers = questions.map((qu, i) => { - const selected = selections[i] ?? []; - const custom = customTexts[i]?.trim() ?? ""; - const allAnswers = otherEnabled[i] && custom ? [...selected, custom] : [...selected]; - return { question: qu.question, answers: allAnswers }; - }); - try { - await rpcClient?.getAiPanelRpcClient().submitClarifyAnswer({ requestId, answers }); - } catch (e) { - console.error("[ClarifyFooter] submit error:", e); - } finally { - setIsSubmitting(false); - } - }; - - if (!q) return null; - - const isOtherActive = otherEnabled[page]; - - return ( - - - {questions.length > 1 && ( - - {questions.map((qu, i) => ( - setPage(i)} - disabled={isSubmitting} - > - {qu.tabLabel} - - ))} - - )} - - - {q.question} - {isMulti ? "multiple answer" : "single answer"} - - - - {q.options.map(opt => { - const isSelected = (selections[page] ?? []).includes(opt.value); - return ( - handleSelectOption(opt.value)} - disabled={isSubmitting} - > - - - {opt.label} - - - ); - })} - - - - Other - - - {isOtherActive && ( - - setCustomTexts(prev => ({ ...prev, [page]: e.target.value }))} - /> - - )} - - - - - - {isSubmitting - ? <> Submitting... - : <> Submit - } - - - - ); -}; - -export default ClarifyFooter; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx index 1bf423ca6c..97ba9c831a 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx @@ -20,10 +20,9 @@ import React, { useState, useEffect } from "react"; import styled from "@emotion/styled"; import { keyframes } from "@emotion/css"; import AIChatInput, { AIChatInputRef, TagOptions } from "../../AIChatInput"; -import { RunningServicesPanel } from "../../AIChatInput/RunningServicesChip"; import { Input } from "../../AIChatInput/utils/inputUtils"; import { AIPanelPrompt, Attachment, TemplateId, CodeContext } from "@wso2/ballerina-core"; -import { commandTemplates, suggestedCommandTemplates as defaultSuggestedCommandTemplates } from "../../../commandTemplates/data/commandTemplates.const"; +import { commandTemplates, suggestedCommandTemplates } from "../../../commandTemplates/data/commandTemplates.const"; import { AttachmentOptions } from "../../AIChatInput/hooks/useAttachments"; import { getTemplateTextById } from "../../../commandTemplates/utils/utils"; import CodeContextCard from "../../CodeContextCard"; @@ -150,7 +149,6 @@ type FooterProps = { aiChatInputRef: React.RefObject; tagOptions: TagOptions; attachmentOptions: AttachmentOptions; - suggestedCommandTemplates?: AIPanelPrompt[]; inputPlaceholder: string; onSend: (content: { input: Input[]; attachments: Attachment[]; metadata?: Record }) => Promise; onStop: () => void; @@ -166,15 +164,13 @@ type FooterProps = { isWebToolsEnabled?: boolean; onToggleWebSearch?: () => void; disabled?: boolean; - contextUsage?: { inputTokens: number; percentage: number; breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; files: number; messages: number; toolResults: number } } | null; - runningServicesPanel?: RunningServicesPanel; + contextUsage?: { inputTokens: number; percentage: number; breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; messages: number; toolResults: number } } | null; }; const Footer: React.FC = ({ aiChatInputRef, tagOptions, attachmentOptions, - suggestedCommandTemplates, inputPlaceholder, onSend, onStop, @@ -191,9 +187,7 @@ const Footer: React.FC = ({ onToggleWebSearch, disabled, contextUsage, - runningServicesPanel, }) => { - const footerSuggestedCommandTemplates = suggestedCommandTemplates ?? defaultSuggestedCommandTemplates; const [animatedText, setAnimatedText] = useState("Generating."); useEffect(() => { @@ -221,7 +215,7 @@ const Footer: React.FC = ({ {showSuggestedCommands && ( - {footerSuggestedCommandTemplates.map((item, index) => renderPrompt(item, index, aiChatInputRef))} + {suggestedCommandTemplates.map((item, index) => renderPrompt(item, index, aiChatInputRef))} )} {codeContext && onRemoveCodeContext && ( @@ -254,7 +248,6 @@ const Footer: React.FC = ({ onToggleWebSearch={onToggleWebSearch} disabled={disabled} contextUsage={contextUsage} - runningServicesPanel={runningServicesPanel} /> AI-generated content may contain mistakes. Always review changes. diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/compaction/ContextUsageWidget/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/compaction/ContextUsageWidget/index.tsx index c6e722ada2..8706697099 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/compaction/ContextUsageWidget/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/compaction/ContextUsageWidget/index.tsx @@ -19,6 +19,7 @@ import React, { useRef, useState } from "react"; import styled from "@emotion/styled"; +const PRE_TURN_THRESHOLD = 178_808; const MAX_CONTEXT_WINDOW = 200_000; const TOOLTIP_SHOW_MS = 300; const TOOLTIP_HIDE_MS = 200; @@ -27,7 +28,6 @@ interface UsageBreakdown { systemInstructions: number; toolDefinitions: number; reservedOutput: number; - files: number; messages: number; toolResults: number; } @@ -247,10 +247,6 @@ const ContextUsageWidget: React.FC = ({ percentage, inp User Context - - Project Files - {toPct(breakdown.files)} - Messages {toPct(breakdown.messages)} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx index cef11f920f..6d8f5a8eb0 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx @@ -37,7 +37,6 @@ import { ApprovalOverlayState, WebToolToggle, LoginMethod, - RunningServiceInfo, } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -77,7 +76,6 @@ import AttachmentBox, { AttachmentsContainer } from "../AttachmentBox"; import Footer from "./Footer"; import { AgentMode } from "../AIChatInput/ModeToggle"; import CommonApprovalFooter from "./Footer/CommonApprovalFooter"; -import ClarifyFooter from "./Footer/ClarifyFooter"; import { useFooterLogic } from "./Footer/useFooterLogic"; import { SettingsPanel } from "../../SettingsPanel"; import WelcomeMessage from "./Welcome"; @@ -86,8 +84,6 @@ import { getOnboardingOpens, incrementOnboardingOpens, convertToUIMessages, isCo import FeedbackBar from "./../FeedbackBar"; import { useFeedback } from "./utils/useFeedback"; import { SegmentType, splitContent } from "./segment"; -import { MigrationContextCard } from "../MigrationContextCard"; -import { ActiveMigrationSession } from "@wso2/ballerina-rpc-client"; import { ReviewBar } from "../ReviewBar"; const NO_DRIFT_FOUND = "No drift identified between the code and the documentation."; @@ -110,26 +106,6 @@ const MessageBody = styled.div<{ isUserMessage: boolean }>(({ isUserMessage }: { overflowWrap: "anywhere", })); -const CompactionNoticeContainer = styled.div` - display: flex; - align-items: center; - gap: 8px; - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 4px; - padding: 6px 12px; - margin: 6px 0; - font-size: 12px; - color: var(--vscode-descriptionForeground); -`; - -const CompactionNotice: React.FC<{ children: React.ReactNode }> = ({ children }) => ( - - - {children} - -); - // ── Agent stream serialization ──────────────────────────────────────────────── function serializeStream(entries: StreamEntry[], existingContent: string): string { @@ -203,12 +179,7 @@ const AIChat: React.FC = () => { const [isAutoApproveEnabled, setIsAutoApproveEnabled] = useState(false); const [isWebToolsEnabled, setIsWebToolsEnabled] = useState(false); const userWebSearchPreferenceRef = useRef(false); - const [agentMode, _setAgentMode] = useState(AgentMode.Edit); - const agentModeRef = useRef(AgentMode.Edit); - const setAgentMode = (mode: AgentMode) => { - _setAgentMode(mode); - agentModeRef.current = mode; - }; + const [agentMode, setAgentMode] = useState(AgentMode.Edit); const [availableCheckpointIds, setAvailableCheckpointIds] = useState>(new Set()); const [hasActiveReview, setHasActiveReview] = useState(false); @@ -223,11 +194,7 @@ const AIChat: React.FC = () => { const [currentFileArray, setCurrentFileArray] = useState([]); const [codeContext, setCodeContext] = useState(undefined); - const [footerSuggestedCommandTemplates, setFooterSuggestedCommandTemplates] = useState(undefined); - const [footerInputPlaceholder, setFooterInputPlaceholder] = useState("Describe your integration..."); - const [migrationSession, setMigrationSession] = useState(null); - const [isMigrationEnhancementRunning, setIsMigrationEnhancementRunning] = useState(false); const [usage, setUsage] = useState<{ remainingUsagePercentage: number; resetsIn: number } | null>(null); const [isUsageExceeded, setIsUsageExceeded] = useState(false); const [loginMethod, setLoginMethod] = useState(null); @@ -235,16 +202,13 @@ const AIChat: React.FC = () => { const [contextUsage, setContextUsage] = useState<{ inputTokens: number; percentage: number; - breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; files: number; messages: number; toolResults: number }; + breakdown?: { systemInstructions: number; toolDefinitions: number; reservedOutput: number; messages: number; toolResults: number }; } | null>(null); const [showContextUsage, setShowContextUsage] = useState(false); - const [runningServices, setRunningServices] = useState([]); - //TODO: Need a better way of storing data related to last generation to be in the repair state. const currentDiagnosticsRef = useRef([]); const codeContextRef = useRef(undefined); - const hiddenContextRef = useRef(undefined); const functionsRef = useRef([]); const lastAttatchmentsRef = useRef([]); const aiChatInputRef = useRef(null); @@ -288,14 +252,10 @@ const AIChat: React.FC = () => { : undefined; updateCodeContext(codeCtx); - hiddenContextRef.current = defaultPrompt.hiddenContext; // Handle plan mode for text-type prompts if (defaultPrompt.type === 'text') { - const textPrompt = defaultPrompt; setAgentMode(defaultPrompt.planMode ? AgentMode.Plan : AgentMode.Edit); - setFooterSuggestedCommandTemplates(textPrompt.suggestedCommandTemplates); - setFooterInputPlaceholder(textPrompt.inputPlaceholder ?? "Describe your integration..."); if (defaultPrompt.autoSubmit && defaultPrompt.text.trim().length > 0) { void handleSend({ @@ -304,9 +264,6 @@ const AIChat: React.FC = () => { }); return; } - } else { - setFooterSuggestedCommandTemplates(undefined); - setFooterInputPlaceholder("Describe your integration..."); } aiChatInputRef.current?.setInputContent(defaultPrompt); @@ -329,7 +286,7 @@ const AIChat: React.FC = () => { useEffect(function updateOnboardingState() { incrementOnboardingOpens(); }, []); - + /* REFACTORED CODE END [2] */ const formatResetsIn = (seconds: number): string => { const days = Math.floor(seconds / 86400); @@ -450,43 +407,6 @@ const AIChat: React.FC = () => { }); }, [rpcClient]); - // Initial fetch covers services started before the webview opened; - // the subscription keeps the list live for the rest of the session. - useEffect(() => { - let cancelled = false; - rpcClient.getAiPanelRpcClient().getRunningServices().then((services) => { - if (!cancelled) { - setRunningServices(services); - } - }).catch(() => { /* manager not initialized yet */ }); - const disposeListener = rpcClient.onRunningServicesChanged((services: RunningServiceInfo[]) => { - setRunningServices(services); - }); - return () => { - cancelled = true; - disposeListener(); - }; - }, [rpcClient]); - - const handleStopRunningService = async (taskId: string) => { - try { - await rpcClient.getAiPanelRpcClient().stopRunningService({ taskId }); - } catch (err) { - console.error("Failed to stop running service", err); - } - }; - - const handleStopAllRunningServices = async () => { - const active = runningServices.filter((s) => !s.exited); - await Promise.all( - active.map((s) => - rpcClient.getAiPanelRpcClient().stopRunningService({ taskId: s.taskId }).catch((err) => { - console.error("Failed to stop running service", err); - }) - ) - ); - }; - /** * Effect: Load initial chat history from aiChatMachine context */ @@ -507,26 +427,6 @@ const AIChat: React.FC = () => { loadHistory(); }, [rpcClient]); - /** - * Effect: Check for an active migration session that needs AI enhancement. - * Shows a context card so the user can resume or start enhancement. - */ - useEffect(function checkMigrationSession() { - const fetchSession = async () => { - try { - const session = await rpcClient.getMigrateIntegrationRpcClient().getActiveMigrationSession(); - if (session && !session.fullyEnhanced) { - setMigrationSession(session); - } else { - setMigrationSession(null); - } - } catch { - // Migration RPC may not be available – ignore - } - }; - fetchSession(); - }, [rpcClient]); - rpcClient?.onCheckpointCaptured(async (payload: { messageId: string; checkpointId: string }) => { setMessages((prevMessages) => { const updatedMessages = [...prevMessages]; @@ -659,21 +559,13 @@ const AIChat: React.FC = () => { const targetIndex = ensureAssistantMessage(msgs); const last = msgs[targetIndex]; const entries = parseStream(last.content); - const planItem: StreamItem = { - kind: "plan", requestId: response.requestId, tasks: response.tasks, message: response.message, - ...(response.autoApproved ? { approvalStatus: "approved" as const } : {}) - }; + const planItem: StreamItem = { kind: "plan", requestId: response.requestId, tasks: response.tasks, message: response.message }; const updated = appendToLastEntry(entries, planItem); msgs[targetIndex] = { ...last, content: serializeStream(updated, last.content) }; return msgs; }); } - // Skip approval UI when auto-approved from backend (scaffold mode) - if (response.autoApproved) { - return; - } - if (isAutoApproveEnabled && response.approvalType === "completion") { await rpcClient.getAiPanelRpcClient().approveTask({ requestId: response.requestId }); return; @@ -763,31 +655,6 @@ const AIChat: React.FC = () => { return msgs; }); - } else if (type === "clarify_event") { - const clarifyNotification = response as any; - const clarifyData = { - requestId: clarifyNotification.requestId, - stage: clarifyNotification.stage, - questions: clarifyNotification.questions, - answers: clarifyNotification.answers, - }; - setMessages(prevMessages => { - const msgs = [...prevMessages]; - const targetIndex = ensureAssistantMessage(msgs); - const last = msgs[targetIndex]; - const entries = parseStream(last.content); - let found = false; - let updated = entries.map(entry => { - const idx = entry.items.findIndex(item => item.kind === "ask" && (item.data as any)?.requestId === clarifyData.requestId); - if (idx === -1) return entry; - found = true; - return { ...entry, items: entry.items.map((item, i) => i === idx ? { kind: "ask" as const, data: clarifyData } : item) }; - }); - if (!found) updated = appendToLastEntry(entries, { kind: "ask", data: clarifyData }); - msgs[targetIndex] = { ...last, content: serializeStream(updated, last.content) }; - return msgs; - }); - } else if (type === "diagnostics") { currentDiagnosticsRef.current = response.diagnostics; @@ -829,21 +696,12 @@ const AIChat: React.FC = () => { } else if (type === "compaction_start") { setIsCompacting(true); - } else if (type === "compaction_end") { + } else if (type === "compaction_end" || type === "compaction_failed") { setIsCompacting(false); - - } else if (type === "compaction_disabled") { - setIsCompacting(false); - setMessages(prevMessages => { - const msgs = [...prevMessages]; - const targetIndex = ensureAssistantMessage(msgs); - const last = msgs[targetIndex]; - msgs[targetIndex] = { - ...last, - content: last.content + "\nYour project is large — automatic context compaction is disabled. You may hit the context limit on long sessions. Start a new thread if that happens." - }; - return msgs; - }); + // Compaction wipes pre-compaction generations — refresh so restore buttons disappear + rpcClient.getAiPanelRpcClient().getCheckpoints() + .then(cps => setAvailableCheckpointIds(new Set(cps.map(cp => cp.id)))) + .catch(() => {}); } else if (type === "usage_metrics") { const inputTokens = (response as any).usage?.inputTokens ?? 0; @@ -864,9 +722,7 @@ const AIChat: React.FC = () => { setIsCompacting(false); setIsCodeLoading(false); setIsLoading(false); - setIsMigrationEnhancementRunning(false); fetchUsage(); - setAgentMode(AgentMode.Edit); } else if (type === "abort") { console.log("Received abort signal"); @@ -885,16 +741,6 @@ const AIChat: React.FC = () => { setIsCompacting(false); setIsCodeLoading(false); setIsLoading(false); - if (isMigrationEnhancementRunning) { - setIsMigrationEnhancementRunning(false); - // Re-fetch session so the Resume card appears - try { - const updatedSession = await rpcClient.getMigrateIntegrationRpcClient().getActiveMigrationSession(); - setMigrationSession(updatedSession); - } catch (e) { - console.error('[AIChat] Failed to refresh migration session after abort:', e); - } - } } else if (type === "save_chat") { console.log("Received save_chat signal"); @@ -1036,62 +882,6 @@ const AIChat: React.FC = () => { } } - /** - * Handles the user clicking "Resume/Start AI Enhancement" from the migration - * context card. Seeds any saved conversation history, then submits the - * auto-fix prompt via the default prompt mechanism. - */ - async function handleContinueMigrationEnhancement() { - try { - // Prime the chat UI so streaming events have a message to append to - setMigrationSession(null); - setIsLoading(true); - - // aiFeatureUsed=true && fullyEnhanced=false means enhancement was previously - // started (at the wizard or via AI Chat) but never finished — this is a resume. - const isResume = migrationSession?.aiFeatureUsed === true; - - if (isResume) { - // Load any persisted history into the chat UI before resuming - const historyMessages = await rpcClient.getMigrateIntegrationRpcClient().getMigrationHistoryMessages(); - if (historyMessages.length > 0) { - const historyUiMessages = historyMessages.map((m) => ({ - role: m.role === "user" ? "User" : "Copilot", - content: m.content, - type: m.role === "user" ? "user_message" : "assistant_message", - })); - setMessages((prev) => [...prev, ...historyUiMessages]); - } - } - - // Push the user trigger and empty assistant placeholder - setMessages((prevMessages) => [ - ...prevMessages, - { role: "User", content: isResume ? "Continue AI Enhancement" : "Start AI Enhancement", type: "user_message" }, - { role: "Copilot", content: "", type: "assistant_message" }, - ]); - - setIsMigrationEnhancementRunning(true); - - // Seed saved conversation history into AI chat state - await rpcClient.getMigrateIntegrationRpcClient().seedMigrationHistory(); - // Prepare the pipeline (updates toml, sets _wizardProjectRoot, marks session active, flags AI Chat routing) - await rpcClient.getMigrateIntegrationRpcClient().startMigrationEnhancement(); - // Actually start the wizard-level streaming pipeline — events now routed to AI Chat - await rpcClient.getMigrateIntegrationRpcClient().wizardEnhancementReady(); - } catch (error) { - console.error('[AIChat] Failed to continue migration enhancement:', error); - setIsMigrationEnhancementRunning(false); - setIsLoading(false); - } - } - - /** - * Handles the user clicking "Auto-fix" from the "none" banner. - * Calls the backend to update the toml and start the pipeline, which will - * push a new default prompt – the `onPromptUpdated` listener picks it up and - * triggers the auto-submit effect. - */ async function handleSend(content: { input: Input[]; attachments: Attachment[]; metadata?: Record }) { setCurrentGeneratingPromptIndex(otherMessages.length); setIsPromptExecutedInCurrentWindow(true); @@ -1451,23 +1241,13 @@ const AIChat: React.FC = () => { })); const currentCodeContext = codeContextRef.current; - const currentHiddenContext = hiddenContextRef.current; - hiddenContextRef.current = undefined; - console.log("Submitting agent prompt:", { useCase, agentMode: agentModeRef.current, codeContext: currentCodeContext, operationType, fileAttatchments }); + console.log("Submitting agent prompt:", { useCase, agentMode, codeContext: currentCodeContext, operationType, fileAttatchments }); rpcClient.getAiPanelRpcClient().generateAgent({ - usecase: useCase, hiddenContext: currentHiddenContext, isPlanMode: agentModeRef.current === AgentMode.Plan, codeContext: currentCodeContext, operationType, fileAttachmentContents: fileAttatchments, webSearchEnabled: isWebToolsEnabled + usecase: useCase, isPlanMode: agentMode === AgentMode.Plan, codeContext: currentCodeContext, operationType, fileAttachmentContents: fileAttatchments, webSearchEnabled: isWebToolsEnabled }) } async function handleStop() { - if (isMigrationEnhancementRunning) { - // Stop the migration agent - partial state and VS Code notification are - // handled by the orchestrator's abort catch block. - await rpcClient.getMigrateIntegrationRpcClient().abortMigrationAgent(); - setIsLoading(false); - setIsCodeLoading(false); - return; - } // Call RPC with empty params (defaults to current workspace + 'default' thread) rpcClient.getAiPanelRpcClient().abortAIGeneration({}); @@ -1753,11 +1533,11 @@ const AIChat: React.FC = () => { )} + {expanded && ( @@ -388,17 +449,29 @@ const HTTPTestScenarioDetail: React.FC = ({ loading - Sending Requests... + Running test scenario... ); } if (!output) return null; + const showSummary = output.entries.length > 1; const hasNoEntries = output.entries.length === 0; return ( <> + {showSummary && Summary} + {showSummary && ( + + + Total: {output.summary.totalEntries} + Passed: {output.summary.passedEntries} + Failed: {output.summary.failedEntries} + + + )} + {hasNoEntries ? (
@@ -435,39 +508,20 @@ interface TryItCardProps { } const TryItCard: React.FC = ({ input, output, rpcClient }) => { - const [isEditing, setIsEditing] = useState(false); - if (!input?.hurlScript && !output?.hurlScript) return null; const hurlScript = input?.hurlScript ?? output?.hurlScript; const scenario = input?.scenario ?? output?.scenario; const handleEdit = async () => { - if (!hurlScript || !rpcClient) { - return; - } - - setIsEditing(true); - try { - const commonRpcClient = rpcClient.getCommonRpcClient(); - - await commonRpcClient?.executeCommand?.({ - commands: ["workbench.action.focusFirstEditorGroup"] - }); - - await commonRpcClient?.executeCommand?.({ + await rpcClient?.getCommonRpcClient?.()?.executeCommand?.({ commands: [ HURL_IMPORT_VSCODE_COMMAND, hurlScript, - { viewColumn: "active", - fileName: scenario - } ] }); } catch (e) { // eslint-disable-next-line no-console console.error("Failed to invoke edit command", e); - } finally { - setIsEditing(false); } }; @@ -483,33 +537,22 @@ const TryItCard: React.FC = ({ input, output, rpcClient }) => { return ( - + HTTP Request{multipleEntries && `s`} - - + {scenario && {scenario}} + + + + - + - - - {scenario && {scenario}} - {content} + {content} ); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AgentStreamView/types.ts b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AgentStreamView/types.ts index 61fe117d73..d288cb1a26 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AgentStreamView/types.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AgentStreamView/types.ts @@ -23,7 +23,6 @@ export type StreamItem = | { kind: "plan"; requestId: string; tasks: any[]; message?: string; approvalStatus?: "approved" | "revised"; approvalComment?: string } | { kind: "config"; data: Record } | { kind: "connector"; data: Record } - | { kind: "ask"; data: Record } | { kind: "try_it"; toolCallId?: string; input?: any; output?: any } | { kind: "component"; id?: string; componentType: string; data: Record }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationContextCard.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationContextCard.tsx deleted file mode 100644 index ec3cab0cb2..0000000000 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationContextCard.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -import React from "react"; -import styled from "@emotion/styled"; -import { ActiveMigrationSession } from "@wso2/ballerina-rpc-client"; - -// ────────────────────────────────────────────────────────────────────────────── -// Styled Components -// ────────────────────────────────────────────────────────────────────────────── - -const Card = styled.div` - display: flex; - flex-direction: column; - gap: 10px; - padding: 14px 16px; - margin: 8px 16px; - border: 1px solid var(--vscode-panel-border); - border-radius: 6px; - background-color: var(--vscode-editorWidget-background); - font-size: 13px; -`; - -const CardHeader = styled.div` - display: flex; - align-items: center; - gap: 8px; - font-weight: 600; - color: var(--vscode-editor-foreground); -`; - -const CardBody = styled.div` - color: var(--vscode-descriptionForeground); - line-height: 1.5; -`; - -const ActionButton = styled.button` - display: inline-flex; - align-items: center; - gap: 4px; - padding: 5px 12px; - border-radius: 3px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - border: 1px solid var(--vscode-button-background); - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - - &:hover { - opacity: 0.85; - } -`; - -// ────────────────────────────────────────────────────────────────────────────── -// Props -// ────────────────────────────────────────────────────────────────────────────── - -interface MigrationContextCardProps { - session: ActiveMigrationSession; - onContinueEnhancement: () => void; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Component -// ────────────────────────────────────────────────────────────────────────────── - -/** - * Renders a context card inside AI Chat when the current project is a migrated - * project with pending or partially-completed AI enhancement. - */ -export function MigrationContextCard({ session, onContinueEnhancement }: MigrationContextCardProps) { - // Only show when enhancement is needed - if (session.fullyEnhanced) { - return null; - } - - const aiEnabled = session.aiFeatureUsed; - - return ( - - - - {"Migration AI Enhancement Available"} - - - {aiEnabled - ? "AI enhancement was previously started for this project. Continue to restore conversation context and pick up where you left off." - : "This project was migrated without AI enhancement. You can run AI enhancement now to fix build errors, resolve TODOs, and refine tests."} - -
- - - {aiEnabled ? "Continue AI Enhancement" : "Start AI Enhancement"} - -
-
- ); -} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationEnhancementBanner/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationEnhancementBanner/index.tsx deleted file mode 100644 index f5d46bad9b..0000000000 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/MigrationEnhancementBanner/index.tsx +++ /dev/null @@ -1,354 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://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. - */ - -import styled from "@emotion/styled"; -import { Codicon } from "@wso2/ui-toolkit"; -import React, { useMemo } from "react"; - -// ────────────────────────────────────────────────────────────────────────────── -// Types -// ────────────────────────────────────────────────────────────────────────────── - -export type StageState = "pending" | "active" | "done"; - -export interface StageInfo { - label: string; - state: StageState; -} - -export interface MigrationEnhancementBannerProps { - aiFeatureUsed: boolean; - isActive: boolean; - fullyEnhanced: boolean; - /** All AI conversation messages – used to derive per-stage status. */ - messages: Array<{ role: string; content: string }>; - onDismiss: () => void; - /** Called when user picks "Auto-fix" from the skip banner to kick off the pipeline. */ - onStartEnhancement?: () => void; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Stage detection -// ────────────────────────────────────────────────────────────────────────────── - -/** - * Derives the status of each of the pipeline stages by scanning the - * accumulated message content for known tool-call patterns. - * - * Stage heuristics (all based on `` / `` tags): - * - Stage 1: `getCompilationErrors` tool call seen → active; result present → done - * - Stage 2: inferred – active once stage 1 is done, done once stage 4 starts - * - Stage 3: inferred – active when file edits target `tests/`, done once stage 4 starts - * - Stage 4: `runTests` tool call seen → active; result present → done - */ -function deriveStages(messages: Array<{ role: string; content: string }>): StageInfo[] { - const allContent = messages.map((m) => m.content).join("\n"); - - // Stage 1: compilation errors check - const s1Called = - allContent.includes('tool="getCompilationErrors"') || - allContent.includes("tool='getCompilationErrors'"); - const s1ResultMatch = allContent.match(/]*tool="getCompilationErrors"/); - const s1Done = - s1Called && - s1ResultMatch !== null && - (allContent.includes("No errors found") || - allContent.includes("no errors") || - allContent.includes("0 errors") || - allContent.includes("no compilation errors")); - - // Stage 4: test runner - const s4Called = - allContent.includes('tool="runTests"') || - allContent.includes("tool='runTests'") || - allContent.includes('tool="run_tests"'); - const s4ResultMatch = allContent.match(/]*tool="runTests"/); - const s4Done = s4Called && s4ResultMatch !== null; - - // Stage 3 hint: file edits targeting tests/ directory - const s3Hint = - /tool=["'](?:file_write|file_edit|writeFile|editFile)["'][^>]*>(?:[^<]|<(?!\/toolcall))*tests\//.test( - allContent - ); - - // Stage 2: active once stage 1 is done; done once stage 4 starts (sequential assumption) - const s2Active = s1Done && !s4Called; - const s2Done = s4Called; - - // Stage 3: active when tests/ file edits detected after stage 1; done once stage 4 starts - const s3Active = s3Hint && !s4Called; - const s3Done = s4Called; - - const stageState = (active: boolean, done: boolean): StageState => - done ? "done" : active ? "active" : "pending"; - - return [ - { label: "Fix build errors", state: stageState(s1Called && !s1Done, s1Done) }, - { label: "Resolve TODO items", state: stageState(s2Active, s2Done) }, - { label: "Refine tests", state: stageState(s3Active, s3Done) }, - { label: "Run & fix tests", state: stageState(s4Called && !s4Done, s4Done) }, - ]; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Styled components -// ────────────────────────────────────────────────────────────────────────────── - -const BannerContainer = styled.div` - display: flex; - flex-direction: column; - gap: 6px; - padding: 8px 12px; - border-bottom: 1px solid var(--vscode-panel-border); - background-color: var(--vscode-textCodeBlock-background); - font-family: var(--vscode-font-family); - font-size: 11px; - color: var(--vscode-editor-foreground); - flex-shrink: 0; -`; - -const BannerHeader = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -`; - -const BannerTitle = styled.span` - display: flex; - align-items: center; - gap: 5px; - font-weight: 600; - font-size: 11px; - opacity: 0.85; -`; - -const ModeBadge = styled.span` - display: inline-block; - padding: 1px 6px; - border-radius: 10px; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.3px; - background-color: rgba(52, 163, 84, 0.2); - color: var(--vscode-gitDecoration-addedResourceForeground); - border: 1px solid var(--vscode-gitDecoration-addedResourceForeground); -`; - -const StartButton = styled.button<{ variant: 'primary' | 'secondary' }>` - display: inline-flex; - align-items: center; - gap: 4px; - padding: 3px 10px; - border-radius: 3px; - font-size: 11px; - font-weight: 600; - cursor: pointer; - border: 1px solid - ${(props: { variant: 'primary' | 'secondary' }) => - props.variant === 'primary' - ? 'var(--vscode-gitDecoration-addedResourceForeground)' - : 'var(--vscode-textLink-foreground)'}; - background-color: ${(props: { variant: 'primary' | 'secondary' }) => - props.variant === 'primary' ? 'rgba(52, 163, 84, 0.15)' : 'rgba(0, 120, 212, 0.1)'}; - color: ${(props: { variant: 'primary' | 'secondary' }) => - props.variant === 'primary' - ? 'var(--vscode-gitDecoration-addedResourceForeground)' - : 'var(--vscode-textLink-foreground)'}; - - &:hover { - opacity: 0.85; - } -`; - -const DismissButton = styled.button` - display: flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 2px; - cursor: pointer; - color: var(--vscode-descriptionForeground); - border-radius: 2px; - line-height: 1; - - &:hover { - color: var(--vscode-editor-foreground); - background-color: var(--vscode-toolbar-hoverBackground); - } -`; - -const StageList = styled.div` - display: flex; - align-items: center; - gap: 0; - flex-wrap: wrap; -`; - -const StageItem = styled.div<{ state: StageState; isLast: boolean }>` - display: flex; - align-items: center; - gap: 4px; - padding: 2px 0; - - ${(props: { state: StageState; isLast: boolean }) => - !props.isLast && - ` - &::after { - content: "›"; - margin: 0 6px; - color: var(--vscode-descriptionForeground); - font-size: 12px; - } - `} - - color: ${(props: { state: StageState; isLast: boolean }) => { - switch (props.state) { - case "done": - return "var(--vscode-gitDecoration-addedResourceForeground)"; - case "active": - return "var(--vscode-editor-foreground)"; - default: - return "var(--vscode-descriptionForeground)"; - } - }}; -`; - -const StageLabel = styled.span<{ state: StageState }>` - font-size: 11px; - font-weight: ${(props: { state: StageState }) => (props.state === "active" ? "600" : "normal")}; - opacity: ${(props: { state: StageState }) => (props.state === "pending" ? "0.55" : "1")}; -`; - -// ────────────────────────────────────────────────────────────────────────────── -// Stage icon helper -// ────────────────────────────────────────────────────────────────────────────── - -function StageIcon({ state }: { state: StageState }) { - if (state === "done") { - return ( - - - - ); - } - if (state === "active") { - return ( - - ); - } - return ( - - ); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Main component -// ────────────────────────────────────────────────────────────────────────────── - -export function MigrationEnhancementBanner({ - aiFeatureUsed, - isActive, - fullyEnhanced, - messages, - onDismiss, - onStartEnhancement, -}: MigrationEnhancementBannerProps) { - const stages = useMemo(() => deriveStages(messages), [messages]); - - // Banner is hidden once enhancement is completed - if (fullyEnhanced) { - return null; - } - - // ── "Skip" banner: user did not enable AI at wizard, offer to start later ── - if (!aiFeatureUsed) { - return ( - - - - - AI Migration Enhancement available - - - - - -
- Start AI enhancement pipeline: - onStartEnhancement?.()} - title="Automatically fix build errors, resolve TODOs, and run tests" - > - - Auto-fix - - -
-
- ); - } - - // ── Active / pending enhancement banner ─────────────────────────────── - return ( - - - - - Migration Enhancement - Auto-fix - {isActive && ( - - )} - - - - - - - {stages.map((stage, i) => ( - - - {stage.label} - - ))} - - - ); -} - -export default MigrationEnhancementBanner; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/TryItScenariosSegment/types.ts b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/TryItScenariosSegment/types.ts index 738ee9c162..0675b3dce8 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/TryItScenariosSegment/types.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/TryItScenariosSegment/types.ts @@ -69,6 +69,11 @@ export type HurlToolOutput = { output: { status: string; durationMs: number; + summary: { + totalEntries: number; + passedEntries: number; + failedEntries: number; + }; entries: Array<{ name: string; method?: string; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/styles.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/styles.tsx index 0f6ba632e1..608c022817 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/styles.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/styles.tsx @@ -44,7 +44,6 @@ export const Header = styled.header({ export const HeaderButtons = styled.div({ display: "flex", justifyContent: "flex-end", - gap: "4px", marginRight: "10px", }); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AgentChatPanel/Components/ChatInterface.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AgentChatPanel/Components/ChatInterface.tsx index c6052578b3..d884990afc 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AgentChatPanel/Components/ChatInterface.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AgentChatPanel/Components/ChatInterface.tsx @@ -83,7 +83,6 @@ export const ChatWrapper = styled.div` flex-direction: column; height: 100vh; width: 100%; - container-type: inline-size; `; export const ChatContainer = styled.div` @@ -106,15 +105,15 @@ export const Messages = styled.div` padding: 8px 20px; height: 100%; - @container (min-width: 1000px) { + @media (min-width: 1000px) { padding: 8px 10%; } - @container (min-width: 1600px) { + @media (min-width: 1600px) { padding: 8px 15%; } - @container (min-width: 2000px) { + @media (min-width: 2000px) { padding: 8px 20%; } `; @@ -343,7 +342,7 @@ const AgentSelectorName = styled.span` text-overflow: ellipsis; min-width: 0; - @media (max-width: 400px) { + @media (max-width: 500px) { display: none; } `; @@ -422,8 +421,7 @@ const ModalContent = styled.div<{ maxWidth: string }>(({ maxWidth }: { maxWidth: border: `1px solid ${ThemeColors.OUTLINE_VARIANT}`, borderRadius: '4px', boxShadow: '0 4px 24px rgba(0, 0, 0, 0.4)', - maxWidth: maxWidth, - margin: '0 16px', + width: maxWidth, textAlign: 'center' })); @@ -468,7 +466,7 @@ interface ClearChatWarningPopupProps { const ClearChatWarningPopup: React.FC = ({ isOpen, onContinue, onCancel }) => { return ( - +

Are you sure you want to clear the chat? This will remove all messages and cannot be undone.

)} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx index 7edb1b0f44..3f8e371409 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx @@ -120,8 +120,6 @@ type NodePromptLaunchOptions = { planMode?: boolean; }; -const SIDE_PANEL_DEFAULT_ERROR_MESSAGE = "Error while performing the action."; - export function BIFlowDiagram(props: BIFlowDiagramProps) { const { projectPath, breakpointState, syntaxTree, onUpdate, onReady, onSave } = props; const { rpcClient } = useRpcContext(); @@ -137,7 +135,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { const [showProgressIndicator, setShowProgressIndicator] = useState(false); const [showProgressSpinner, setShowProgressSpinner] = useState(false); const [progressMessage, setProgressMessage] = useState(LOADING_MESSAGE); - const [progressTitle, setProgressTitle] = useState(""); const [subPanel, setSubPanel] = useState({ view: SubPanelView.UNDEFINED }); const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); const [breakpointInfo, setBreakpointInfo] = useState(); @@ -148,7 +145,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { const [projectOrg, setProjectOrg] = useState(""); const [entrypointContext, setEntrypointContext] = useState<{ serviceName?: string; functionName?: string }>(); const [isUserAuthenticated, setIsUserAuthenticated] = useState(false); - const [errorMessage, setErrorMessage] = useState(undefined); + const [connectorErrorMessage, setConnectorErrorMessage] = useState(undefined); // Navigation stack for back navigation const [navigationStack, setNavigationStack] = useState([]); @@ -918,10 +915,9 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewVectorKnowledgeBase.current = false; isCreatingNewDataLoader.current = false; isCreatingNewChunker.current = false; - setErrorMessage(undefined); - isCreatingAgent.current = false; - setShowProgressIndicator(false); - setShowProgressSpinner(false); + isCreatingNewWorkflow.current = false; + setConnectorErrorMessage(undefined); + isCreatingNewActivity.current = false; clearNavigationStack(); }; @@ -952,9 +948,8 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { const fetchNodesAndAISuggestions = ( parent: FlowNode | Branch, target: LineRange, - fetchAiSuggestions = false, - updateFlowModel = true, - isOnAddNode = false + fetchAiSuggestions = false, // By default, we fetch available nodes without fetching AI suggestions. + updateFlowModel = true ) => { if (!parent || !target) { console.error(">>> No parent or target found"); @@ -971,8 +966,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { const modelWithDraft = addDraftNode(parent, target); setModel(modelWithDraft); } - setShowSidePanel(true); - isOnAddNode && setSidePanelView(SidePanelView.LOADING); rpcClient .getBIDiagramRpcClient() .getAvailableNodes(getNodeRequest) @@ -980,8 +973,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { console.log(">>> Available nodes", response); if (!response.categories) { console.error(">>> Error getting available nodes", response); - setErrorMessage(SIDE_PANEL_DEFAULT_ERROR_MESSAGE); - setSidePanelView(SidePanelView.ERROR); return; } @@ -993,6 +984,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { let finalCategories = convertedCategories; initialCategoriesRef.current = convertedCategories; + setShowSidePanel(true); setSidePanelView(SidePanelView.NODE_LIST); setCategories(convertedCategories); }) @@ -1035,7 +1027,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { // handle add new node topNodeRef.current = parent; changeTargetRange(target) - fetchNodesAndAISuggestions(parent, target, undefined, undefined, true); + fetchNodesAndAISuggestions(parent, target); }; const handleOnAddNodePrompt = ( @@ -1212,15 +1204,9 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { } }, [rpcClient, model?.fileName]); - const handleRetryNodeFetch = () => { - if (topNodeRef.current && targetRef.current) { - fetchNodesAndAISuggestions(topNodeRef.current, targetRef.current, false, false, true); - } - }; - const showConnectorError = () => { - setErrorMessage(SIDE_PANEL_DEFAULT_ERROR_MESSAGE); - setSidePanelView(SidePanelView.ERROR); + setConnectorErrorMessage("An unexpected error occurred while fetching connection information."); + setSidePanelView(SidePanelView.CONNECTOR_ERROR); setShowSidePanel(true); } @@ -1736,7 +1722,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { }); break; - default: + default: // default node selectedClientName.current = category; setShowProgressIndicator(true); @@ -2084,8 +2070,8 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { setShowSidePanel(true); }) .catch(() => { - setErrorMessage(SIDE_PANEL_DEFAULT_ERROR_MESSAGE); - setSidePanelView(SidePanelView.ERROR); + setConnectorErrorMessage("An unexpected error occurred while fetching connection information."); + setSidePanelView(SidePanelView.CONNECTOR_ERROR); setShowSidePanel(true); }) .finally(() => { @@ -2372,7 +2358,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingAgent.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("AI Agent"); // Push current state to navigation stack pushToNavigationStack(sidePanelView, categories, selectedNodeRef.current, selectedClientName.current); @@ -2390,7 +2375,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { }, }) .then((response) => { - if (!isCreatingAgent.current) return; selectedNodeRef.current = response.flowNode; nodeTemplateRef.current = response.flowNode; showEditForm.current = false; @@ -2407,7 +2391,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewModelProvider.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Model Providers"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2423,7 +2406,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "MODEL_PROVIDER", }) .then((response) => { - if (!isCreatingNewModelProvider.current) return; setCategories(convertModelProviderCategoriesToSidePanelCategories(response.categories as Category[])); setSidePanelView(SidePanelView.MODEL_PROVIDERS); setShowSidePanel(true); @@ -2439,7 +2421,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewVectorStore.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Vector Stores"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2455,7 +2436,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "VECTOR_STORE", }) .then((response) => { - if (!isCreatingNewVectorStore.current) return; setCategories(convertVectorStoreCategoriesToSidePanelCategories(response.categories as Category[])); setSidePanelView(SidePanelView.VECTOR_STORES); setShowSidePanel(true); @@ -2471,7 +2451,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewEmbeddingProvider.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Embedding Providers"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2487,7 +2466,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "EMBEDDING_PROVIDER", }) .then((response) => { - if (!isCreatingNewEmbeddingProvider.current) return; setCategories( convertEmbeddingProviderCategoriesToSidePanelCategories(response.categories as Category[]) ); @@ -2505,7 +2483,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewVectorKnowledgeBase.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Knowledge Bases"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2521,7 +2498,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "KNOWLEDGE_BASE", }) .then((response) => { - if (!isCreatingNewVectorKnowledgeBase.current) return; setCategories(convertKnowledgeBaseCategoriesToSidePanelCategories(response.categories as Category[])); setSidePanelView(SidePanelView.KNOWLEDGE_BASES); setShowSidePanel(true); @@ -2537,7 +2513,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewDataLoader.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Data Loaders"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2553,7 +2528,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "DATA_LOADER", }) .then((response) => { - if (!isCreatingNewDataLoader.current) return; setCategories(convertDataLoaderCategoriesToSidePanelCategories(response.categories as Category[])); setSidePanelView(SidePanelView.DATA_LOADERS); setShowSidePanel(true); @@ -2569,7 +2543,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { isCreatingNewChunker.current = true; setShowProgressIndicator(true); setShowProgressSpinner(true); - setProgressTitle("Chunkers"); const messageTimeout = setupProgressMessageTimeout(); // Push current state to navigation stack @@ -2585,7 +2558,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { searchKind: "CHUNKER", }) .then((response) => { - if (!isCreatingNewChunker.current) return; setCategories(convertChunkerCategoriesToSidePanelCategories(response.categories as Category[])); setSidePanelView(SidePanelView.CHUNKERS); setShowSidePanel(true); @@ -2685,19 +2657,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { // TODO: implement the edit agent logic }; - const handleOnChatWithAgent = (agentCallNode: FlowNode) => { - const agentVarName = agentCallNode.properties?.connection?.value as string; - if (!agentVarName || !model?.fileName) { - console.error('Cannot start inline agent chat: missing agent variable name or file path'); - return; - } - rpcClient.getBIDiagramRpcClient().startInlineAgentChat({ - agentVarName, - filePath: model.fileName, - agentNode: agentCallNode, - }); - }; - // AI Agent callback handlers const handleOnEditAgentModel = async (agentCallNode: FlowNode) => { const agentNode = await findAgentNodeFromAgentCallNode(agentCallNode, rpcClient); @@ -2803,7 +2762,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { console.error("Error deleting memory manager:", error); alert("Failed to remove memory manager. Please try again."); } finally { - resetNodeSelectionStates(); setShowProgressIndicator(false); debouncedGetFlowModel(); } @@ -3029,7 +2987,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { console.error("Error deleting tool:", error); alert(`Failed to remove tool "${tool.name}". Please try again.`); } finally { - resetNodeSelectionStates(); + selectedNodeRef.current = undefined; setShowProgressIndicator(false); debouncedGetFlowModel(); } @@ -3116,14 +3074,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { }; const flowModel = originalModel && suggestedModel ? suggestedModel : model; - - // Hide "Chat" button on agent nodes when already inside a chat agent flow diagram - const isChatAgentFlow = (() => { - const eventStartNode = flowModel?.nodes.find((node) => node.codedata.node === "EVENT_START"); - const meta = eventStartNode?.metadata?.data as { kind?: string; label?: string } | undefined; - return meta?.kind === "AI Chat Agent" && meta?.label === "chat"; - })(); - const memoizedDiagramProps = useMemo( () => ({ model: flowModel, @@ -3153,7 +3103,6 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { goToTool: handleOnGoToTool, onSelectMemoryManager: handleOnSelectMemoryManager, onDeleteMemoryManager: handleOnDeleteMemoryManager, - onChatWithAgent: isChatAgentFlow ? undefined : handleOnChatWithAgent, }, suggestions: { fetching: fetchingAiSuggestions, @@ -3224,11 +3173,10 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { setSidePanelView={setSidePanelView} showProgressSpinner={showProgressSpinner} progressMessage={progressMessage} - progressTitle={progressTitle} // Regular callbacks onClose={handleOnCloseSidePanel} onSaveAndRefresh={closeSidePanelAndFetchUpdatedFlowModel} - onBack={sidePanelView === SidePanelView.ERROR ? handleRetryNodeFetch : handleOnFormBack} + onBack={handleOnFormBack} onSelectNode={handleOnSelectNode} // Add node callbacks onAddConnection={handleOnAddConnection} @@ -3274,7 +3222,7 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { onSelectConnectorPopup={handleOnSelectConnectorConfiguration} selectedMcpToolkitName={selectedMcpToolkitName} onNavigateToPanel={handleOnNavigateToPanel} - errorMessage={errorMessage} + errorMessage={connectorErrorMessage} // Devant specific callbacks onImportDevantConn={handleClickImportDevantConn} onLinkDevantProject={(platformExtState?.isExtInstalled && !platformExtState?.selectedContext?.project) ? onLinkDevantProject : undefined} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/index.tsx index 400378278c..b34601d48c 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/index.tsx @@ -159,16 +159,6 @@ export function BIFocusFlowDiagram(props: BIFocusFlowDiagramProps) { if (node?.functionDefinition) { const flowNode = getFlowNodeForNaturalFunction(node.functionDefinition); - // Enrich model provider metadata with icon URL from connections - const modelProviderValue = flowNode.properties?.modelProvider?.value as string; - if (modelProviderValue && Array.isArray(model.flowModel.connections)) { - const matchingConnection = model.flowModel.connections.find( - (c: any) => c?.properties?.variable?.value === modelProviderValue - ); - if (matchingConnection?.metadata?.icon && flowNode.properties?.modelProvider?.metadata?.data) { - (flowNode.properties.modelProvider.metadata.data as any).iconUrl = matchingConnection.metadata.icon; - } - } model.flowModel.nodes.push(flowNode); setModel(model.flowModel); const eventStartNode = model.flowModel.nodes.find( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/utils.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/utils.tsx index a648f3ad02..2ac84c8b6b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/utils.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FocusFlowDiagram/utils.tsx @@ -127,8 +127,10 @@ export function createPromptHelperPane(params: CreatePromptHelperPaneParams): JS filteredCompletions: filteredCompletions, isInModal: false, types: [{ballerinaType: "ai:Prompt", fieldType: "RAW_TEMPLATE", selected: false}], + forcedValueTypeConstraint: valueTypeConstraint as string || "ai:Prompt", handleRetrieveCompletions: async (value: string, property: Property, offset: number, triggerCharacter?: string) => await debouncedRetrieveCompletions(value, property, offset, triggerCharacter), + handleValueTypeConstChange: () => { }, inputMode: inputMode || InputMode.PROMPT }); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/DeclareVariableForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/DeclareVariableForm/index.tsx index cc924f0392..f984a21fa8 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/DeclareVariableForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/DeclareVariableForm/index.tsx @@ -33,7 +33,9 @@ export const VariableForm = (props: FormProps) => { }, [props.formFields]); const handleOnTypeChange = (type: string | CompletionItem) => { - handleSelectedTypeChange?.(type); + Promise.resolve(handleSelectedTypeChange?.(type)).catch((error) => { + console.error("Error in handleSelectedTypeChange", error); + }); }; return ( <> diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx similarity index 87% rename from workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx rename to workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx index a2b13be121..35b7f8b7da 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx @@ -66,7 +66,6 @@ import { FormExpressionEditorRef, HelperPaneHeight, Icon, - LabelInfo, ThemeColors, Tooltip, } from "@wso2/ui-toolkit"; @@ -102,7 +101,7 @@ import { getTypeHelper } from "../../TypeHelper"; import { EXPRESSION_EXTRACTION_REGEX, TypeHelperContext } from "../../../../constants"; import MatchForm from "../MatchForm"; import { FormSubmitOptions } from "../../FlowDiagram"; -import { AI_PROMPT_TYPE, getHelperPaneNew } from "../../HelperPaneNew"; +import { getHelperPaneNew } from "../../HelperPaneNew"; import { ConfigureRecordPage } from "../../HelperPaneNew/Views/RecordConfigModal"; import { VariableForm } from "../DeclareVariableForm"; import KnowledgeBaseForm from "../KnowledgeBaseForm"; @@ -115,31 +114,15 @@ import { SidePanelView } from "../../FlowDiagram/PanelManager"; import { ConnectionKind } from "../../../../components/ConnectionSelector"; import { getFilteredTypesByKind } from "../../TypeEditor/utils"; import { useModalStack } from "../../../../Context"; -import { getArraySubFormFieldFromTypes, stringToRawArrayElements, stringToRawObjectEntries } from "@wso2/ballerina-side-panel/lib/components/editors/utils"; +import { getArraySubFormFieldFromTypes, stringToRawArrayElements } from "@wso2/ballerina-side-panel/lib/components/editors/utils"; -interface FlowNodeTypeEditorState { +interface TypeEditorState { isOpen: boolean; fieldKey?: string; // Optional, to store the key of the field being edited newTypeValue?: string; } -export interface ResolvedType { - name: string; - value: string; - sortText?: string; - codeData?: CodeData; - labelDetails?: LabelInfo; -} - -export interface ResolvedType { - name: string; - value: string; - sortText?: string; - codeData?: CodeData; - labelDetails?: LabelInfo; -} - -interface FlowNodeFormProps { +interface FormProps { fileName: string; node: FlowNode; nodeFormTemplate?: FlowNode; // used in edit forms @@ -178,39 +161,8 @@ interface FlowNodeFormProps { derivedFields?: FieldDerivation[]; // Configuration for auto-deriving field values from other fields devantExpressionEditor?: ExpressionEditorDevantProps; customValidator?: (fieldKey: string, value: any, allValues: FormValues) => string | undefined; // Custom validation function for form fields - defaultExpandAdvanced?: boolean; } -type RepeatableMapEntry = { - key: string; - value: string; -}; - -const getRepeatableMapEntriesFromValue = (value: unknown): RepeatableMapEntry[] => { - if (typeof value === "string") { - return stringToRawObjectEntries(value); - } - - if (value && typeof value === "object" && !Array.isArray(value)) { - return Object.entries(value as Record).map(([entryKey, entryValue]) => ({ - key: entryKey, - value: typeof entryValue === "object" && entryValue !== null - ? String((entryValue as any).value ?? "") - : String(entryValue ?? "") - })); - } - - return []; -}; - -const getRepeatableMapDiagnosticsByKey = (value: unknown): Record | undefined => { - if (value && typeof value === "object" && !Array.isArray(value)) { - return value as Record; - } - - return undefined; -}; - // Styled component for the action button description const ActionButtonDescription = styled.div` font-size: var(--vscode-font-size); @@ -273,7 +225,7 @@ export const BreadcrumbSeparator = styled.span` font-size: var(--vscode-font-size); `; -export const FlowNodeForm = forwardRef(function FlowNodeForm(props: FlowNodeFormProps, ref: React.ForwardedRef) { +export const FormGenerator = forwardRef(function FormGenerator(props: FormProps, ref: React.ForwardedRef) { const { fileName, node, @@ -308,11 +260,12 @@ export const FlowNodeForm = forwardRef([]); const [isAiUserAuthenticated, setIsAiUserAuthenticated] = useState(false); const formImportsRef = useRef({}); - const [typeEditorState, setTypeEditorState] = useState({ isOpen: false, newTypeValue: "" }); - const [isTypeEditorOpen, setIsTypeEditorOpen] = useState(false); + const [typeEditorState, setTypeEditorState] = useState({ isOpen: false, newTypeValue: "" }); + const [isTypeEditorOpen, setIsTypeEditorOpen] = useState(false); const [editingTypeName, setEditingTypeName] = useState(""); const [visualizableField, setVisualizableField] = useState(); const [recordTypeFields, setRecordTypeFields] = useState([]); + const [valueTypeConstraints, setValueTypeConstraints] = useState(); const fields = useMemo(() => { if (!props.fieldOverrides || baseFields.length === 0) { @@ -337,7 +290,7 @@ export const FlowNodeForm = forwardRef(null); + const [selectedType, setSelectedType] = useState(null); const skipFormValidation = useMemo(() => { const isAgentNode = node && ( @@ -355,7 +308,6 @@ export const FlowNodeForm = forwardRef(null); // To store codeData for getVisualizableFields - const typeResolutionId = useRef(0); //stack for recursive type creation const [stack, setStack] = useState([{ @@ -633,65 +585,23 @@ export const FlowNodeForm = forwardRef t.selected); - const isContainingRepeatableList = field.types?.some(t => t.fieldType === "REPEATABLE_LIST"); - const isContainingRepeatableMap = field.types?.some(t => t.fieldType === "REPEATABLE_MAP"); - const nodeProperties = nodeWithDiagnostics?.properties as any; let propertyDiagnostics: any = nodeProperties?.[field.key]?.diagnostics?.diagnostics; // Update value from current form data and update diagnostics if (data[field.key] !== undefined) { - if (isContainingRepeatableList && Array.isArray(nodeProperties?.[field.key]?.value)) { - if (selectedInputType?.fieldType === "REPEATABLE_LIST") { - let initialValues: string[]; - if (typeof data[field.key] === 'string') { - initialValues = stringToRawArrayElements(data[field.key]); - } else { - // When the value is an array (from FormArrayEditor), extract values directly - initialValues = (data[field.key] as any[]).map((val: any) => - typeof val === 'string' ? val : String(val?.value ?? '') - ); - } - const initialFields = initialValues.map((val, index) => { - const key = crypto.randomUUID(); - return { - ...getArraySubFormFieldFromTypes(key, (field.types[0] as any).template.types as InputType[]), - value: val, - diagnostics: nodeProperties?.[field.key]?.value?.[index]?.diagnostics?.diagnostics ?? [] - }; - }); - updatedField.value = initialFields; - } - else { - updatedField.value = data[field.key]; - propertyDiagnostics = nodeProperties?.[field.key]?.value?.map((val: any) => val?.diagnostics?.diagnostics ?? []).flat() ?? []; - } - } - - else if (isContainingRepeatableMap) { - // Diagnostics responses do not preserve a single map shape consistently. - // Optional maps may omit `value`, while populated maps can come back either - // as editor-friendly objects or as serialized source strings. - const repeatableMapDiagnostics = getRepeatableMapDiagnosticsByKey(nodeProperties?.[field.key]?.value); - if (selectedInputType?.fieldType === "REPEATABLE_MAP") { - const initialValues = getRepeatableMapEntriesFromValue(data[field.key]); - // Keep value as a Record to match processToOutputFormat shape expected by FormMapEditorNew - const outputRecord: Record = {}; - initialValues.forEach((val) => { - const key = crypto.randomUUID(); - outputRecord[val.key] = { - ...getArraySubFormFieldFromTypes(key, (field.types[0] as any).template.types as InputType[]), - key: `mp-val-${key}`, - value: val.value, - diagnostics: repeatableMapDiagnostics?.[val.key]?.diagnostics?.diagnostics ?? [] - }; - }); - updatedField.value = outputRecord; - } - else { - updatedField.value = data[field.key]; - } + if (selectedInputType?.fieldType === "REPEATABLE_LIST" && typeof data[field.key] === "string") { + const initialValues = stringToRawArrayElements(data[field.key]); + const initialFields = initialValues.map((val, index) => { + const key = crypto.randomUUID(); + return { + ...getArraySubFormFieldFromTypes(key, (field.types[0] as any).template.types as InputType[]), + value: val, + diagnostics: nodeProperties?.[field.key]?.value?.[index]?.diagnostics?.diagnostics ?? [] + }; + }); + updatedField.value = initialFields; } else { updatedField.value = data[field.key]; @@ -712,19 +622,6 @@ export const FlowNodeForm = forwardRef 0)) { - const collectedDiagnostics = Object.values( - getRepeatableMapDiagnosticsByKey(nodeProperties?.[field.key]?.value) ?? {} - ) - .map((value: any) => value?.diagnostics?.diagnostics) - .flat() - .filter(Boolean) as Array<{ message?: string; severity?: string }>; - - propertyDiagnostics = collectedDiagnostics.filter((d, i, arr) => - arr.findIndex(x => x.message === d.message) === i - ); - } - if (propertyDiagnostics && Array.isArray(propertyDiagnostics)) { updatedField.diagnostics = propertyDiagnostics; } else { @@ -745,7 +642,7 @@ export const FlowNodeForm = forwardRef { - console.log(">>> FlowNodeForm handleOnSubmit", data); + console.log(">>> FormGenerator handleOnSubmit", data); if (node && targetLineRange) { const updatedNode = mergeFormDataWithFlowNode(data, targetLineRange, dirtyFields); const editorConfig = data["editorConfig"]; @@ -900,7 +797,7 @@ export const FlowNodeForm = forwardRef val?.diagnostics?.diagnostics ?? []).flat() ?? []; diagnostics = [...diagnostics, ...valueDiagnostics]; - } else if (property?.types?.some(t => t.fieldType === "REPEATABLE_MAP") && typeof property.value === 'object' && property.value !== null && !Array.isArray(property.value)) { - // For repeatable map, check diagnostics for each entry in the map - const valueDiagnostics = Object.values(property.value as Record)?.map((val) => val?.diagnostics?.diagnostics ?? []).flat() ?? []; - diagnostics = [...diagnostics, ...valueDiagnostics]; } else { diagnostics = property.diagnostics?.diagnostics ?? []; } @@ -1177,22 +1070,10 @@ export const FlowNodeForm = forwardRef ({ ...t })) } - : { ...property, types: property.types.map(t => ({ ...t })) }; + ? { ...property, imports: mergedImports } + : property; try { - const propertyPrimaryFieldType = getPrimaryInputType(updatedProperty.types); - if (updatedProperty.types.length > 1 && propertyPrimaryFieldType.fieldType !== "REPEATABLE_LIST" && propertyPrimaryFieldType.fieldType !== "REPEATABLE_MAP") { - updatedProperty.types.forEach(t => { - if (t.fieldType === "EXPRESSION") { - t.selected = true; - } - else { - t.selected = false; - } - }); - } - const response = await rpcClient.getBIDiagramRpcClient().getExpressionDiagnostics({ filePath: fileName, context: { @@ -1271,22 +1152,39 @@ export const FlowNodeForm = forwardRef t.label === valueTypeConstraint); if (matchedReferenceType) { - updateRecordTypeFields({ - value: matchedReferenceType.insertText, - name: matchedReferenceType.label, - labelDetails: matchedReferenceType.labelDetails, - }) - return; + updateRecordTypeFields(matchedReferenceType) + setValueTypeConstraints(valueTypeConstraint); + } + else { + const type = await searchImportedTypeByName(valueTypeConstraint); + if (!type) { + setValueTypeConstraints(valueTypeConstraint); + return; + }; + + setValueTypeConstraints(type.insertText); + // Create the record type field for expression + const expressionEntry = Object.entries(getFormProperties(node)) + .find(([_, property]) => property.metadata?.label === "Expression"); + + if (!expressionEntry) return; + + const [key, property] = expressionEntry; + const typeForRecord = { label: type.insertText, labelDetails: type.labelDetails }; + const recordTypeField = createExpressionRecordTypeField(key, property, `${type.codedata.org}:${type.codedata.module}:${type.codedata.version}`, typeForRecord); + if (!recordTypeField) return; + + setRecordTypeFields(prevFields => { + const prevIndex = prevFields.findIndex(f => f.key === recordTypeField.key); + if (prevIndex !== -1) { + const updated = [...prevFields]; + updated[prevIndex] = recordTypeField; + return updated; + } else { + return [...prevFields, recordTypeField]; + } + }); } - const type = await searchImportedTypeByName(valueTypeConstraint); - if (!type) return; - - updateRecordTypeFields({ - value: type.name, - name: type.name, - codeData: type.codedata, - labelDetails: type.labelDetails, - }); } const handleGetHelperPane = ( @@ -1330,6 +1228,8 @@ export const FlowNodeForm = forwardRef { return { key, property, recordTypeMembers: [{ kind: "RECORD_TYPE", - type: type.name || "", + type: type.label || "", packageInfo: packageInfo, - packageName: packageName, selected: false }] }; }; + const isTypeExcludedFromValueTypeConstraint = (typeLabel: string) => { + return ["()"].includes(typeLabel); + } /** * Updates record type fields and value type constraints when a type is selected. * This is used in variable declaration forms where the variable type dynamically changes. */ - const updateRecordTypeFields = (type?: { value: string, codeData?: CodeData, name: string; labelDetails?: { description?: string, detail?: string } }) => { + const updateRecordTypeFields = (type?: { label: string; labelDetails?: { description?: string, detail?: string } }) => { + if (!type) { + setValueTypeConstraints(''); + return; + } + + // If not a Record, remove the 'expression' entry from recordTypeFields and return + if (type?.labelDetails?.description?.toLocaleLowerCase() !== "record") { + if (type.labelDetails.detail === "Structural Types" + || type.labelDetails.detail === "Behaviour Types" + || isTypeExcludedFromValueTypeConstraint(type.label) + ) { + setValueTypeConstraints(''); + } + else { + setValueTypeConstraints(type.label); + } + setRecordTypeFields(prevFields => prevFields.filter(f => f.key !== "expression")); + return; + } + else { + setValueTypeConstraints(type.label); + } + // Create the record type field for expression const expressionEntry = Object.entries(getFormProperties(node)) .find(([_, property]) => { @@ -1541,41 +1465,9 @@ export const FlowNodeForm = forwardRef prevFields.filter(f => f.key !== key)); - setBaseFields(prevFields => prevFields.map(field => { - if (field.key === key) { - return { - ...field, - types: [ - { fieldType: "EXPRESSION", selected: false }, - ] - }; - } - return field; - })); - return; - } - // If not a Record, remove the 'expression' entry from recordTypeFields and return - if (type?.labelDetails?.description?.toLocaleLowerCase() !== "record" && type.codeData?.node !== "RECORD") { - setRecordTypeFields(prevFields => prevFields.filter(f => f.key !== key)); - setBaseFields(prevFields => prevFields.map(field => { - if (field.key === key) { - return { - ...field, - types: [ - { fieldType: "EXPRESSION", selected: false }, - ] - }; - } - return field; - })); - return; - } - const packageInfo = type.codeData ? `${type.codeData.org}:${type.codeData.module}:${type.codeData.version}` : ''; - const recordTypeField = createExpressionRecordTypeField(key, property, packageInfo, type, type.codeData?.module); + const [key, property] = expressionEntry; + const recordTypeField = createExpressionRecordTypeField(key, property, '', type); if (!recordTypeField) return; setBaseFields(prevFields => prevFields.map(field => { @@ -1607,24 +1499,23 @@ export const FlowNodeForm = forwardRef { - const resolutionId = ++typeResolutionId.current; - if (!type) { - setSelectedType(null); - updateRecordTypeFields(undefined); - return; - } - let matchedType: ResolvedType | undefined; - if (typeof type === "string") { - matchedType = await findTypeByName(type); - } - else { - matchedType = await findTypeByName(type.value); + try { + if (typeof type === "string") { + await handleSelectedTypeByName(type); + return; + } + else { + // If the type is a Completion item, then it can be found in the reference types. + // Which cannot be an imported type. + importsCodedataRef.current = null; + await fetchVisualizableFields(fileName, (type as CompletionItem).label); + } + setSelectedType(type); + updateRecordTypeFields(type); } - if (resolutionId !== typeResolutionId.current) { - return; + catch (error) { + console.error("Error handling selected type change", error); } - setSelectedType(matchedType ?? null); - updateRecordTypeFields(matchedType); }; const findMatchedType = (items: TypeHelperItem[], typeName: string) => { @@ -1656,7 +1547,7 @@ export const FlowNodeForm = forwardRef { - return getFilteredTypesByKind(response.categories, [functionKinds.IMPORTED, functionKinds.CURRENT]); + return getFilteredTypesByKind(response.categories, functionKinds.IMPORTED); }) .finally(() => { @@ -1676,31 +1567,53 @@ export const FlowNodeForm = forwardRef => { + /** + * Handles type selection by type name (used when type is created/changed) + */ + const handleSelectedTypeByName = async (typeName: string) => { + // Early return for invalid input if (!typeName || typeName.length === 0) { - return undefined; + importsCodedataRef.current = null; + await fetchVisualizableFields(fileName, typeName); + setValueTypeConstraints(''); + return; } - let matchedOtherTypes = await searchImportedTypeByName(typeName); - if (matchedOtherTypes) { - return ({ - name: matchedOtherTypes.name, - value: matchedOtherTypes.insertText, - codeData: matchedOtherTypes.codedata, - sortText: matchedOtherTypes.sortText, - labelDetails: matchedOtherTypes.labelDetails - }); + + const type = await searchImportedTypeByName(typeName); + if (!type) { + importsCodedataRef.current = null; + await fetchVisualizableFields(fileName, typeName); + setValueTypeConstraints(''); + return; } - let matchedReferenceType = types.find(t => t.label === typeName); - if (matchedReferenceType) { - return ({ - name: matchedReferenceType.label, - value: matchedReferenceType.value, - sortText: matchedReferenceType.sortText, - labelDetails: matchedReferenceType.labelDetails - }); + else { + importsCodedataRef.current = type.codedata; + await fetchVisualizableFields(fileName, typeName); } - return undefined; - } + + setValueTypeConstraints(type.insertText); + // Create the record type field for expression + const expressionEntry = Object.entries(getFormProperties(node)) + .find(([_, property]) => property.metadata?.label === "Expression"); + + if (!expressionEntry) return; + + const [key, property] = expressionEntry; + const typeForRecord = { label: type.insertText, labelDetails: type.labelDetails }; + const recordTypeField = createExpressionRecordTypeField(key, property, `${type.codedata.org}:${type.codedata.module}:${type.codedata.version}`, typeForRecord); + if (!recordTypeField) return; + + setRecordTypeFields(prevFields => { + const prevIndex = prevFields.findIndex(f => f.key === recordTypeField.key); + if (prevIndex !== -1) { + const updated = [...prevFields]; + updated[prevIndex] = recordTypeField; + return updated; + } else { + return [...prevFields, recordTypeField]; + } + }); + }; const getDefaultValue = (typeName?: string) => { return ({ @@ -1872,7 +1785,7 @@ export const FlowNodeForm = forwardRef getPrimaryInputType(field.types)?.fieldType === "TYPE"); + const typeField = fields.find(field => field.key === "type"); if (typeField) { typeField.optional = true; } @@ -2086,7 +1999,6 @@ export const FlowNodeForm = forwardRef )} ({ isOpen: false, newTypeValue: "" }); + const [typeEditorState, setTypeEditorState] = useState({ isOpen: false, newTypeValue: "" }); /* Expression editor related state and ref variables */ const prevCompletionFetchText = useRef(""); @@ -185,6 +185,7 @@ export function ArtifactForm(props: ArtifactFormProps) { const fieldsRef = useRef(fields); const fieldsValuesRef = useRef(fields); const [formImports, setFormImports] = useState({}); + const [selectedType, setSelectedType] = useState(null); const [refetchStates, setRefetchStates] = useState([false]); const [valueTypeConstraints, setValueTypeConstraints] = useState(); @@ -234,14 +235,14 @@ export function ArtifactForm(props: ArtifactFormProps) { setEditingTypeName(""); if (type) { const typeName = typeof type === 'string' ? type : (type as Type).name; - setFields(fields.map((field) => { - if (getPrimaryInputType(field.types)?.fieldType === 'TYPE') { + const targetFieldKey = typeEditorState.field?.key || "type"; + setFields((prevFields) => prevFields.map((field) => { + if (field.key === targetFieldKey) { return { ...field, value: typeName }; } return field; })); if (imports) { - const targetFieldKey = typeEditorState.field?.key || "type"; handleUpdateImports(targetFieldKey, imports); } } @@ -703,17 +704,6 @@ export function ArtifactForm(props: ArtifactFormProps) { try { const field = fieldsRef.current.find(f => f.key === key); if (field) { - const propertyPrimaryFieldType = getPrimaryInputType(property.types); - if (property.types.length>1 && propertyPrimaryFieldType.fieldType !== "REPEATABLE_LIST" && propertyPrimaryFieldType.fieldType !== "REPEATABLE_MAP") { - property.types.forEach(t => { - if (t.fieldType === "EXPRESSION") { - t.selected = true; - } - else{ - t.selected = false; - } - }); - } const response = await rpcClient.getBIDiagramRpcClient().getExpressionDiagnostics({ filePath: fileName, context: { @@ -783,10 +773,13 @@ export function ArtifactForm(props: ArtifactFormProps) { updateImports: handleUpdateImports, completions: completions, projectPath: projectPath, + selectedType: selectedType, filteredCompletions: filteredCompletions, isInModal: false, types: types, handleRetrieveCompletions: handleRetrieveCompletions, + handleValueTypeConstChange: handleValueTypeConstChange, + forcedValueTypeConstraint: valueTypeConstraints, inputMode: inputMode, }); }; @@ -1180,4 +1173,4 @@ export function ArtifactForm(props: ArtifactFormProps) { ); } -export default ArtifactForm; +export default FormGeneratorNew; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/KnowledgeBaseForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/KnowledgeBaseForm/index.tsx index 7063154ee8..993c5f5aa6 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/KnowledgeBaseForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/KnowledgeBaseForm/index.tsx @@ -40,8 +40,9 @@ import { } from "../form-utils"; import { SidePanelView } from "../../FlowDiagram/PanelManager"; import { ConnectionKind } from "../../../../components/ConnectionSelector"; +import { URI, Utils } from "vscode-uri"; -const DEFAULT_CHUNKER_VALUE = "ai:AUTO"; +const DEFAULT_CHUNKER_VALUE = "\"AUTO\""; namespace S { export const Container = styled.div<{ footerActionButton?: boolean }>` @@ -180,18 +181,9 @@ export function KnowledgeBaseForm(props: KnowledgeBaseFormProps) { field.codedata = { ...field.codedata, searchNodesKind: searchNodesKindMap[originalName] }; } if (originalName === "chunker") { + // hack: set default value for chunker field field.defaultValue = DEFAULT_CHUNKER_VALUE; - if (!field.value) { - field.value = DEFAULT_CHUNKER_VALUE; - } - field.advanced = false; - field.codedata = { - ...field.codedata, - staticItems: [ - { id: "auto", label: "AUTO", value: "ai:AUTO" }, - { id: "disable", label: "DISABLE", value: "ai:DISABLE" }, - ], - }; + field.advanced = true; } }); setKnowledgeBaseFields(fields); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FunctionForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FunctionForm/index.tsx index 8bb5f9a3c2..2fb9537863 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FunctionForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FunctionForm/index.tsx @@ -17,12 +17,12 @@ */ import { useEffect, useRef, useState } from "react"; -import { FunctionNode, LineRange, NodeKind, NodeProperties, NodePropertyKey, Property, DIRECTORY_MAP, EVENT_TYPE, getPrimaryInputType, isTemplateType, RecordTypeField } from "@wso2/ballerina-core"; +import { FunctionNode, LineRange, NodeKind, NodeProperties, NodePropertyKey, Property, DIRECTORY_MAP, EVENT_TYPE, getPrimaryInputType, isTemplateType } from "@wso2/ballerina-core"; import { Button, Codicon, Typography, View, ViewContent } from "@wso2/ui-toolkit"; import styled from "@emotion/styled"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { FormField, FormImports, FormValues } from "@wso2/ballerina-side-panel"; -import ArtifactForm from "../Forms/ArtifactForm"; +import FormGeneratorNew from "../Forms/FormGeneratorNew"; import { TitleBar } from "../../../components/TitleBar"; import { TopNavigationBar } from "../../../components/TopNavigationBar"; import { FormHeader } from "../../../components/FormHeader"; @@ -182,7 +182,6 @@ export function FunctionForm(props: FunctionFormProps) { const [saving, setSaving] = useState(false); const [isLoading, setIsLoading] = useState(false); const [showOAuthConfig, setShowOAuthConfig] = useState(false); - const [recordTypeFields, setRecordTypeFields] = useState([]); const fileName = filePath.split(/[\\/]/).pop(); const formType = useRef("Function"); @@ -345,20 +344,6 @@ export function FunctionForm(props: FunctionFormProps) { } if (!cancelled) { - // Extract record type fields from OAuth properties for record editor modal support - const oauthRecordTypeFields = oauthConfigPropertiesRef.current - .filter(({ property }) => { - const primaryInputType = getPrimaryInputType(property?.types); - return primaryInputType?.typeMembers && - primaryInputType?.typeMembers.some(member => member.kind === "RECORD_TYPE"); - }) - .map(({ key, property }) => ({ - key, - property, - recordTypeMembers: getPrimaryInputType(property?.types)?.typeMembers.filter(member => member.kind === "RECORD_TYPE") - })); - setRecordTypeFields(oauthRecordTypeFields); - setFunctionFields(fields); setShowOAuthConfig(oauthSupported); } @@ -840,12 +825,11 @@ export function FunctionForm(props: FunctionFormProps) { )} {filePath && targetLineRange && functionFields.length > 0 && - {filePath && targetLineRange && functionFields.length > 0 && - 0) { - return "(" + type.members!.map(m => m.typeInfo?.name || m.name || m.typeName).join("|") + ")"; - } - return type.typeInfo?.name || type.typeName || "unknown"; -} - -export default function ArrayType(props: TypeProps & { bodyOnly?: boolean }) { - const { param, depth, onChange, bodyOnly } = props; - const helperStyleClass = useHelperPaneStyles(); - const requiredParam = isRequiredParam(param) && depth > 1; - if (requiredParam) { - param.selected = true; - } - - const [paramSelected, setParamSelected] = useState(param.selected || requiredParam); - - const memberType = param.memberType; - const elementTypeName = getElementDisplayName(memberType); - const isUnionElement = memberType?.typeName === "union" || memberType?.typeName === "enum"; - const unionMembers = isUnionElement ? memberType?.members : undefined; - - // For union arrays, track which union member is selected for the next "Add" - const [selectedUnionMember, setSelectedUnionMember] = useState( - () => unionMembers?.[0] ? getUnionFormFieldName(unionMembers[0]) : "" - ); - - const nextId = useRef(param.elements?.length ?? 0); - const [elements, setElements] = useState(() => - (param.elements ?? []).map((field, i) => ({ id: i, field })) - ); - const [expandedIds, setExpandedIds] = useState>(() => new Set( - (param.elements ?? []).map((_, i) => i) - )); - - const syncToParam = useCallback((newElements: ElementEntry[]) => { - param.elements = newElements.map(e => e.field); - param.selected = newElements.length > 0; - setParamSelected(newElements.length > 0); - }, [param]); - - const handleAddElement = () => { - if (!memberType) return; - - let newElement: TypeField; - if (isUnionElement && unionMembers) { - const member = unionMembers.find(m => getUnionFormFieldName(m) === selectedUnionMember) - || unionMembers[0]; - newElement = cloneElement(member); - } else { - newElement = cloneElement(memberType); - } - - const id = nextId.current++; - const newElements = [...elements, { id, field: newElement }]; - setElements(newElements); - setExpandedIds(prev => new Set(prev).add(id)); - syncToParam(newElements); - onChange(); - }; - - const handleRemoveElement = (id: number) => { - const newElements = elements.filter(e => e.id !== id); - setElements(newElements); - setExpandedIds(prev => { - const next = new Set(prev); - next.delete(id); - return next; - }); - syncToParam(newElements); - onChange(); - }; - - const toggleExpand = (id: number) => { - setExpandedIds(prev => { - const next = new Set(prev); - if (next.has(id)) next.delete(id); - else next.add(id); - return next; - }); - }; - - const toggleParamCheck = () => { - if (requiredParam) return; - const newSelectedState = !paramSelected; - param.selected = newSelectedState; - if (!newSelectedState) { - resetFieldValues(param); - setElements([]); - param.elements = []; - } - setParamSelected(newSelectedState); - onChange(); - }; - - if (!memberType) { - return <>; - } - - const renderBody = () => ( -
- {elements.map(({ id, field: element }, index) => { - const isRecordElement = element.typeName === "record" - && (element.fields?.length ?? 0) > 0; - const isNestedArray = element.typeName === "array" && element.memberType; - const isExpandable = isRecordElement || isNestedArray - || element.typeName === "inclusion"; - const elDisplayName = getElementDisplayName(element); - return ( -
-
toggleExpand(id) : undefined}> - {isExpandable && ( - - )} - - [{index}] - - - {elDisplayName} - - -
- {expandedIds.has(id) && isRecordElement && ( -
- -
- )} - {expandedIds.has(id) && isNestedArray && ( - - )} - {expandedIds.has(id) && isExpandable && !isRecordElement && !isNestedArray && ( -
- -
- )} -
- ); - })} -
-
- - - Add{!isUnionElement && ` ${elementTypeName}`} - -
- {isUnionElement && unionMembers && ( -
- ({ - id: i.toString(), - value: getUnionFormFieldName(m), - }))} - onValueChange={setSelectedUnionMember} - sx={{ width: 'fit-content' }} - /> -
- )} -
-
- ); - - // When rendered as a nested array element, skip the header (checkbox + label) - if (bodyOnly) { - return renderBody(); - } - - return ( -
-
-
- - - {param.name} - - - {elementTypeName}[] - {(param.optional || param.defaultable) && " (Optional)"} - - {param.documentation && ( - - {param.documentation} - - } - position="right" - sx={{ maxWidth: '300px', whiteSpace: 'normal', pointerEvents: 'none' }} - > - - - )} -
- {paramSelected && renderBody()} -
-
- ); -} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/index.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/index.ts index c465a38dd3..74a8994a46 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/index.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/index.ts @@ -16,7 +16,6 @@ * under the License. */ -import ArrayType from './ArrayType'; import CustomType from './CustomType'; import InclusionType from "./InclusionType"; import RecordType from "./RecordType"; @@ -25,6 +24,5 @@ import UnionType from "./UnionType"; export {RecordType as record}; export {UnionType as union}; export {UnionType as enum}; -export {ArrayType as array}; export {InclusionType as inclusion}; export {CustomType as custom}; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/styles.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/styles.ts index e8b871a5e5..d8e3dd2bd2 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/styles.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/styles.ts @@ -335,31 +335,6 @@ export const useHelperPaneStyles = () => ({ minWidth: '32px', marginLeft: '8px' }), - arrayElementHeader: css({ - cursor: 'pointer', - '&:hover': { - backgroundColor: 'var(--vscode-list-hoverBackground)', - }, - }), - arrayElementIndex: css({ - fontFamily: 'monospace', - margin: '0px 4px', - opacity: 0.7, - fontSize: '12px', - minWidth: '24px', - }), - arrayAddWrapper: css({ - display: 'flex', - alignItems: 'center', - cursor: 'pointer', - padding: '2px 4px', - borderRadius: '3px', - gap: '4px', - '&:hover': { - color: ThemeColors.PRIMARY, - backgroundColor: 'var(--vscode-list-hoverBackground)', - }, - }), }); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Configurables.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Configurables.tsx index b0acb0c41f..bd276c6189 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Configurables.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Configurables.tsx @@ -23,7 +23,7 @@ import ExpandableList from "../Components/ExpandableList"; import { Button, CheckBox, Divider, SearchBox, TextField, Typography } from "@wso2/ui-toolkit"; import { ScrollableContainer } from "../Components/ScrollableContainer"; import FooterButtons from "../Components/FooterButtons"; -import FlowNodeForm from "../../Forms/FlowNodeForm"; +import FormGenerator from "../../Forms/FormGenerator"; import { URI, Utils } from "vscode-uri"; import { POPUP_IDS, useModalStack } from "../../../../Context"; import { HelperPaneIconType, getHelperPaneIcon } from "../utils/iconUtils"; @@ -216,7 +216,7 @@ export const Configurables = (props: ConfigurablesPageProps) => { } addModal( - void; onChange: (insertText: CompletionInsertText | string) => void; updateImports: (key: string, imports: { [key: string]: string }) => void; - selectedType?: ResolvedType; + selectedType?: CompletionItem; inputMode?: InputMode; }; @@ -140,12 +139,10 @@ export const FunctionsPage = ({ setIsLoading(false) if (response) { - if (response.prefix && response.moduleId) { - const importStatement = { - [response.prefix]: response.moduleId - }; - updateImports(fieldKey, importStatement); - } + const importStatement = { + [response.prefix]: response.moduleId + }; + updateImports(fieldKey, importStatement); return extractFunctionInsertText(response.template); } @@ -192,7 +189,7 @@ export const FunctionsPage = ({ handleSubmit={handleFunctionSave} functionName={undefined} isDataMapper={false} - defaultType={selectedType?.value} + defaultType={selectedType?.label} />, POPUP_IDS.FUNCTION, "New Function", 500, 400); onClose(); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/RecordConfigModal.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/RecordConfigModal.tsx index ac3d160c7f..6eeb372e3f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/RecordConfigModal.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/RecordConfigModal.tsx @@ -277,11 +277,6 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) { setRecordModel([recordConfig]); recordModelRef.current = [recordConfig]; setSelectedMemberName(newRecordModel.name); - - // Update selected flags so the backend receives the correct type on submit - recordTypeField.recordTypeMembers.forEach(m => { - m.selected = m.type === newRecordModel.name; - }); } setIsLoading(false); @@ -366,10 +361,6 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) { const handleMemberChange = async (value: string) => { const member = recordTypeField.recordTypeMembers.find(m => m.type === value); if (member) { - // Update selected flags so the backend receives the correct type on submit - recordTypeField.recordTypeMembers.forEach(m => { - m.selected = m.type === value; - }); setIsLoading(true); setSelectedMemberName(member.type); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Variables.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Variables.tsx index f2c19fb44c..f81fcda21b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Variables.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/Variables.tsx @@ -24,7 +24,7 @@ import { Codicon, CompletionItem, Divider, HelperPaneCustom, SearchBox, ThemeCol import { useEffect, useMemo, useRef, useState } from "react" import { getPropertyFromFormField, useFieldContext, InputMode } from "@wso2/ballerina-side-panel" import FooterButtons from "../Components/FooterButtons" -import { FlowNodeForm, ResolvedType } from "../../Forms/FlowNodeForm" +import { FormGenerator } from "../../Forms/FormGenerator" import { ScrollableContainer } from "../Components/ScrollableContainer" import { FormSubmitOptions } from "../../FlowDiagram" import { URI } from "vscode-uri" @@ -43,7 +43,7 @@ type VariablesPageProps = { targetLineRange: LineRange; anchorRef: React.RefObject; handleOnFormSubmit?: (updatedNode?: FlowNode, editorConfig?: EditorConfig, options?: FormSubmitOptions, openDMInPopup?: boolean) => void; - selectedType?: ResolvedType; + selectedType?: CompletionItem; filteredCompletions: CompletionItem[]; currentValue: string; recordTypeField?: RecordTypeField; @@ -112,7 +112,7 @@ export const Variables = (props: VariablesPageProps) => { return path; }, [breadCrumbSteps]); const completionContext = useMemo(() => { - const context = navigationPath ? navigationPath + '.' : (currentValue ?? ''); + const context = navigationPath ? navigationPath + '.' : currentValue; return context; }, [navigationPath, currentValue]); @@ -213,7 +213,7 @@ export const Variables = (props: VariablesPageProps) => { const handleAddNewVariable = () => { addModal( - { description: "Type of the variable", }, types: [{ fieldType: "TYPE", selected: false }], - value: selectedType?.value, + value: selectedType?.label, placeholder: "var", optional: false, editable: true, diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/index.tsx index 9777b8172e..8059550e41 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/index.tsx @@ -16,7 +16,7 @@ * under the License. */ -import { RefObject, useEffect, useRef, useState } from 'react'; +import { RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { ExpandableList } from './Components/ExpandableList'; import { Variables } from './Views/Variables'; @@ -26,16 +26,18 @@ import { DocumentConfig } from './Views/DocumentConfig'; import { CompletionInsertText, EditorConfig, ExpressionProperty, FlowNode, getPrimaryInputType, InputType, LineRange, RecordTypeField } from '@wso2/ballerina-core'; import { CompletionItem, HelperPaneCustom, HelperPaneHeight, Typography } from '@wso2/ui-toolkit'; import { SlidingPane, SlidingPaneHeader, SlidingPaneNavContainer, SlidingWindow } from '@wso2/ui-toolkit'; +import { CreateValue } from './Views/CreateValue'; import { FunctionsPage } from './Views/Functions'; import { FormSubmitOptions } from '../FlowDiagram'; import { Configurables } from './Views/Configurables'; import { DevantConfigurables } from './Views/DevantConfigurables'; import styled from '@emotion/styled'; +import { useModalStack } from '../../../Context'; +import { getDefaultValue } from './utils/types'; import { HelperPaneIconType, getHelperPaneIcon } from './utils/iconUtils'; import { ExpressionEditorDevantProps, HelperpaneOnChangeOptions, InputMode } from '@wso2/ballerina-side-panel'; -import { ResolvedType } from '../Forms/FlowNodeForm'; -export const AI_PROMPT_TYPE = "ai:Prompt"; +const AI_PROMPT_TYPE = "ai:Prompt"; export type ValueCreationOption = { typeCheck: string | null; @@ -59,11 +61,13 @@ export type HelperPaneNewProps = { completions: CompletionItem[], projectPath?: string, handleOnFormSubmit?: (updatedNode?: FlowNode, editorConfig?: EditorConfig, options?: FormSubmitOptions) => void - selectedType?: ResolvedType; + selectedType?: CompletionItem; filteredCompletions?: CompletionItem[]; isInModal?: boolean; types?: InputType[]; + forcedValueTypeConstraint?: string; handleRetrieveCompletions: (value: string, property: ExpressionProperty, offset: number, triggerCharacter?: string) => Promise; + handleValueTypeConstChange: (valueTypeConstraint: string) => void; inputMode?: InputMode; devantExpressionEditor?: ExpressionEditorDevantProps; }; @@ -90,15 +94,25 @@ const HelperPaneNewEl = ({ isInModal, types, handleRetrieveCompletions, + forcedValueTypeConstraint, + handleValueTypeConstChange, inputMode, devantExpressionEditor, }: HelperPaneNewProps) => { const [selectedItem, setSelectedItem] = useState(); - const currentMenuItemCount = getPrimaryInputType(types)?.ballerinaType === AI_PROMPT_TYPE ? 5 : 4; + const currentMenuItemCount = types ? + (forcedValueTypeConstraint?.includes(AI_PROMPT_TYPE) ? 6 : 5) : + (forcedValueTypeConstraint?.includes(AI_PROMPT_TYPE) ? 5 : 4) // Create refs array for all menu items const menuItemRefs = useRef<(HTMLDivElement | null)[]>([]); + useEffect(() => { + if (types?.length > 0) { + handleValueTypeConstChange(getPrimaryInputType(types)?.ballerinaType); + } + }, [types, forcedValueTypeConstraint]) + const ifCTRLandUP = (e: KeyboardEvent) => { return ( (e.ctrlKey || e.metaKey) && e.key === "ArrowUp" @@ -182,6 +196,62 @@ const HelperPaneNewEl = ({ } }, [selectedItem]); + const isSelectedTypeContainsType = (selectedType: string | string[], searchType: string) => { + if (Array.isArray(selectedType)) { + return selectedType.some(type => type.includes(searchType)); + } + const unionTypes = selectedType.split("|").map(type => type.trim()); + return unionTypes.includes(searchType); + }; + + const defaultValue = getDefaultValue(selectedType?.label); + + const allValueCreationOptions = [ + { + typeCheck: "string", + value: "\"TEXT_HERE\"", + label: "Create a string value" + }, + { + typeCheck: "log:PrintableRawTemplate", + value: "string `TEXT_HERE`", + label: "Create a printable template" + }, + { + typeCheck: "error", + value: "error(\"ERROR_MESSAGE_HERE\")", + label: "Create an error" + }, + { + typeCheck: "json", + value: "{}", + label: "Create an empty json" + }, + { + typeCheck: "xml", + value: "xml ``", + label: "Create an xml template" + }, + { + typeCheck: "anydata", + value: "{}", + label: "Create an empty object" + } + ]; + + // Filter options based on type matching, and add default value if it exists + const valueCreationOptions = [ + ...(defaultValue ? [{ + typeCheck: null, // Special case for default value (if type is primitive) + value: defaultValue, + label: `Initialize to ${defaultValue}` + }] : []), + ...allValueCreationOptions.filter(option => + forcedValueTypeConstraint && + isSelectedTypeContainsType(forcedValueTypeConstraint, option.typeCheck) + ) + ]; + return ( @@ -248,7 +318,7 @@ const HelperPaneNewEl = ({ - {getPrimaryInputType(types)?.ballerinaType === AI_PROMPT_TYPE && ( + {forcedValueTypeConstraint?.includes(AI_PROMPT_TYPE) && ( menuItemRefs.current[6] = el} to="DOCUMENTS" @@ -321,6 +391,18 @@ const HelperPaneNewEl = ({ )} + + Create Value + + + Functions @@ -423,6 +505,8 @@ export const getHelperPaneNew = (props: HelperPaneNewProps) => { filteredCompletions, isInModal, types, + forcedValueTypeConstraint, + handleValueTypeConstChange, } = props; return ( @@ -447,6 +531,8 @@ export const getHelperPaneNew = (props: HelperPaneNewProps) => { isInModal={isInModal} types={types} handleRetrieveCompletions={props.handleRetrieveCompletions} + forcedValueTypeConstraint={forcedValueTypeConstraint} + handleValueTypeConstChange={handleValueTypeConstChange} inputMode={props.inputMode} devantExpressionEditor={props.devantExpressionEditor} /> diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/ConfigureProjectForm.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/ConfigureProjectForm.tsx index 7244f1791c..107067860f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/ConfigureProjectForm.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/ConfigureProjectForm.tsx @@ -16,7 +16,7 @@ * under the License. */ -import { ActionButtons, Codicon, Typography } from "@wso2/ui-toolkit"; +import { ActionButtons, Typography } from "@wso2/ui-toolkit"; import { useState } from "react"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { ValidateProjectFormErrorField } from "@wso2/ballerina-core"; @@ -24,70 +24,11 @@ import { BodyText } from "../../styles"; import { ProjectFormData, ProjectFormFields } from "../ProjectForm/ProjectFormFields"; import { validatePackageName } from "../ProjectForm/utils"; import { MultiProjectFormData, MultiProjectFormFields } from "./components/MultiProjectFormFields"; -import { - AIEnhancementSection, - AIEnhancementTitle, - ButtonWrapper, -} from "./styles"; -import { - RadioGroup, - RadioOption, - RadioInput, - RadioContent, - RadioTitle, - RadioDescription, -} from "../ProjectForm/styles"; +import { ButtonWrapper } from "./styles"; import { ConfigureProjectFormProps } from "./types"; -interface AIEnhancementToggleProps { - enabled: boolean; - onChange: (enabled: boolean) => void; -} - -function AIEnhancementToggle({ enabled, onChange }: AIEnhancementToggleProps) { - return ( - - - - AI Enhancement - - - onChange(true)}> - onChange(true)} - /> - - Enable AI Enhancement - - AI will automatically resolve unmapped elements, fix build errors, and refine tests. - - - - onChange(false)}> - onChange(false)} - /> - - Skip for Now – Enhance Later - - Open the project as-is. You can trigger AI enhancement later from the BI Copilot. - - - - - - ); -} - export function ConfigureProjectForm({ isMultiProject, onNext, onBack }: ConfigureProjectFormProps) { const { rpcClient } = useRpcContext(); - const [aiEnhancementEnabled, setAiEnhancementEnabled] = useState(true); const [singleIntegrationData, setSingleIntegrationData] = useState({ integrationName: "", packageName: "", @@ -228,7 +169,7 @@ export function ConfigureProjectForm({ isMultiProject, onNext, onBack }: Configu isLibrary: singleIntegrationData.isLibrary, }; setIsValidating(false); - onNext(payload, aiEnhancementEnabled); + onNext(payload); } catch (error) { setSingleIntegrationPathError("An error occurred during validation"); setIsValidating(false); @@ -284,7 +225,7 @@ export function ConfigureProjectForm({ isMultiProject, onNext, onBack }: Configu projectPath: multiProjectData.path, createDirectory: multiProjectData.createDirectory, createAsWorkspace: false, - }, aiEnhancementEnabled); + }); } catch (error) { setPathError("An error occurred during validation"); setIsValidating(false); @@ -305,12 +246,10 @@ export function ConfigureProjectForm({ isMultiProject, onNext, onBack }: Configu folderNameError={folderNameError || undefined} /> - {} - - {} - ` - font-size: 12px; - font-weight: 500; - color: ${(props: { variant?: string }) => { - switch (props.variant) { - case "success": - return "var(--vscode-testing-iconPassed)"; - case "error": - return "var(--vscode-errorForeground)"; - default: - return "var(--vscode-descriptionForeground)"; - } - }}; -`; - -const StreamArea = styled.div` - flex: 1; - max-height: 60vh; - overflow-y: auto; - padding: 12px; - border: 1px solid var(--vscode-panel-border); - border-radius: 6px; - background-color: var(--vscode-editor-background); - font-size: 13px; - line-height: 1.6; -`; - -const ButtonRow = styled.div` - display: flex; - gap: 8px; - align-items: center; -`; - -const ActionButton = styled.button<{ variant?: "primary" | "secondary" }>` - display: inline-flex; - align-items: center; - gap: 4px; - padding: 6px 14px; - border-radius: 3px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - border: 1px solid - ${(props: { variant?: string }) => - props.variant === "secondary" - ? "var(--vscode-button-secondaryBackground)" - : "var(--vscode-button-background)"}; - background-color: ${(props: { variant?: string }) => - props.variant === "secondary" - ? "var(--vscode-button-secondaryBackground)" - : "var(--vscode-button-background)"}; - color: ${(props: { variant?: string }) => - props.variant === "secondary" - ? "var(--vscode-button-secondaryForeground)" - : "var(--vscode-button-foreground)"}; - - &:hover { - opacity: 0.85; - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } -`; - -const SpinnerIcon = styled.span` - display: inline-block; - animation: spin 1s linear infinite; - @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } - } -`; - -const SubText = styled.span` - font-size: 11px; - font-weight: 400; - color: var(--vscode-descriptionForeground); - margin-top: 1px; -`; - -// ────────────────────────────────────────────────────────────────────────────── -// Component -// ────────────────────────────────────────────────────────────────────────────── - -export function WizardAIEnhancementView() { - const { rpcClient } = useRpcContext(); - const scrollRef = useRef(null); - const enhancementTriggered = useRef(false); - - const [status, setStatus] = useState("running"); - // Single content string with inline / markup, - // exactly like the AI Panel's approach. - const [content, setContent] = useState(""); - // Uptime counter – seconds since the enhancement started - const [elapsed, setElapsed] = useState(0); - - // Track terminal status in a ref so the callback always sees the latest - // value without needing `status` in its dependency array. - const terminalRef = useRef(false); - - // ── Uptime counter ───────────────────────────────────────────────────── - useEffect(() => { - if (status !== "running") { - return; - } - setElapsed(0); - const id = setInterval(() => setElapsed((s) => s + 1), 1000); - return () => clearInterval(id); - }, [status]); - - function formatElapsed(seconds: number): string { - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = seconds % 60; - if (h > 0) { - return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; - } - return `${m}:${String(s).padStart(2, "0")}`; - } - - // ── Helper to update content ────────────────────────────────────────── - const updateContent = useCallback( - (updater: (prev: string) => string) => setContent(updater), - [] - ); - - // ── Chat event handler ──────────────────────────────────────────────── - const handleChatEvent = useCallback( - (event: ChatNotify) => { - if (terminalRef.current) { - return; - } - - switch (event.type) { - case "start": - setStatus("running"); - setContent(""); - break; - - case "content_block": - updateContent((prev) => prev + event.content); - break; - - case "content_replace": - setContent(event.content); - break; - - // ── Inject markup into the content string ────── - case "tool_call": { - const toolName = event.toolName; - const toolCallId = event.toolCallId; - const toolInput = event.toolInput; - - if (toolName === "LibrarySearchTool") { - const desc = toolInput?.searchDescription; - const msg = desc - ? `Searching for ${desc}...` - : "Searching for libraries..."; - updateContent( - (prev) => - prev + - `\n\n${msg}` - ); - } else if (toolName === "LibraryGetTool") { - updateContent( - (prev) => - prev + - `\n\nFetching library details...` - ); - } else if (toolName === "HealthcareLibraryProviderTool") { - updateContent( - (prev) => - prev + - `\n\nAnalyzing request & selecting healthcare libraries...` - ); - } else if ( - ["file_write", "file_edit", "file_batch_edit"].includes( - toolName - ) - ) { - const fileName = - toolInput?.fileName || "file"; - const displayName = - formatFileNameForDisplay(fileName); - const msg = - toolName === "file_write" - ? `Creating ${displayName}...` - : `Updating ${displayName}...`; - updateContent( - (prev) => - prev + - `\n\n${msg}` - ); - } else if (toolName === "getCompilationErrors") { - updateContent( - (prev) => - prev + - `\n\nChecking for errors...` - ); - } else if (toolName === "runTests") { - updateContent( - (prev) => - prev + - `\n\nRunning tests...` - ); - } - break; - } - - // ── Replace with ───────────────── - case "tool_result": { - const toolName = event.toolName; - const toolCallId = event.toolCallId; - const toolOutput = event.toolOutput; - - if (toolName === "LibrarySearchTool") { - const desc = toolOutput?.searchDescription; - const origMsg = desc - ? `Searching for ${desc}...` - : "Searching for libraries..."; - const doneMsg = desc - ? `${desc.charAt(0).toUpperCase() + desc.slice(1)} search completed` - : "Library search completed"; - updateContent((prev) => - prev.replace( - `${origMsg}`, - `${doneMsg}` - ) - ); - } else if (toolName === "LibraryGetTool") { - const libs = toolOutput || []; - const resultMsg = - libs.length === 0 - ? "No relevant libraries found" - : `Fetched libraries: [${libs.join(", ")}]`; - updateContent((prev) => - prev.replace( - `Fetching library details...`, - `${resultMsg}` - ) - ); - } else if (toolName === "HealthcareLibraryProviderTool") { - const libs = toolOutput || []; - const resultMsg = - libs.length === 0 - ? "No relevant healthcare libraries found." - : `Fetched healthcare libraries: [${libs.join(", ")}]`; - updateContent((prev) => - prev.replace( - `Analyzing request & selecting healthcare libraries...`, - `${resultMsg}` - ) - ); - } else if ( - ["file_write", "file_edit", "file_batch_edit"].includes( - toolName - ) - ) { - updateContent((prev) => { - const creatingPattern = - /Creating (.+?)\.\.\.<\/toolcall>/; - const updatingPattern = - /Updating (.+?)\.\.\.<\/toolcall>/; - - if (creatingPattern.test(prev)) { - const action = toolOutput?.action; - const resultText = - action === "updated" - ? "Updated" - : "Created"; - return prev.replace( - creatingPattern, - (_m, tn, fn) => - `${resultText} ${fn}` - ); - } - if (updatingPattern.test(prev)) { - return prev.replace( - updatingPattern, - (_m, tn, fn) => - `Updated ${fn}` - ); - } - return prev; - }); - } else if (toolName === "getCompilationErrors") { - const errors = - toolOutput?.diagnostics || []; - const errorCount = errors.length; - const msg = - errorCount === 0 - ? "No errors found" - : `Found ${errorCount} error${errorCount > 1 ? "s" : ""}`; - const pattern = new RegExp( - `Checking for errors\\.\\.\\.<\\/toolcall>` - ); - updateContent((prev) => - prev.replace( - pattern, - `${msg}` - ) - ); - } else if (toolName === "runTests") { - if (toolCallId) { - const resultMsg = - toolOutput?.summary ?? "Tests completed"; - updateContent((prev) => - prev.replace( - `Running tests...`, - `${resultMsg}` - ) - ); - } - } - break; - } - - case "stop": - terminalRef.current = true; - setStatus("completed"); - break; - - case "error": - updateContent( - (prev) => - prev + - `\n\n**Error:** ${event.content ?? "An unexpected error occurred."}` - ); - terminalRef.current = true; - setStatus("error"); - break; - - case "abort": - terminalRef.current = true; - setStatus("aborted"); - break; - - default: - break; - } - }, - [updateContent] - ); - - // ── Subscribe to streaming events & trigger the agent ───────────────── - useEffect(() => { - rpcClient.onChatNotify((event: ChatNotify) => { - handleChatEvent(event); - }); - }, [rpcClient, handleChatEvent]); - - useEffect(() => { - if (enhancementTriggered.current) { - return; - } - enhancementTriggered.current = true; - - const client = rpcClient.getMigrateIntegrationRpcClient(); - client.wizardEnhancementReady().catch((err: unknown) => { - console.error( - "[WizardAIEnhancementView] wizardEnhancementReady failed:", - err - ); - setStatus("error"); - }); - }, [rpcClient]); - - // ── Auto-scroll ─────────────────────────────────────────────────────── - useEffect(() => { - scrollRef.current?.scrollTo({ - top: scrollRef.current.scrollHeight, - behavior: "smooth", - }); - }, [content]); - - // ── Parse content into segments ─────────────────────────────────────── - const segments = useMemo(() => splitContent(content), [content]); - - // ── Actions ─────────────────────────────────────────────────────────── - const handleOpenProject = useCallback(() => { - rpcClient - .getMigrateIntegrationRpcClient() - .openMigratedProject() - .catch((err: unknown) => { - console.error( - "[WizardAIEnhancementView] openMigratedProject failed:", - err - ); - }); - }, [rpcClient]); - - const handleSkipAndOpen = useCallback(async () => { - const client = rpcClient.getMigrateIntegrationRpcClient(); - try { - await client.abortMigrationAgent(); - } catch { - /* best effort */ - } - client.openMigratedProject().catch((err: unknown) => { - console.error( - "[WizardAIEnhancementView] openMigratedProject (skip) failed:", - err - ); - }); - }, [rpcClient]); - - // ── Render ──────────────────────────────────────────────────────────── - const isRunning = status === "running"; - const isDone = - status === "completed" || - status === "error" || - status === "aborted"; - - return ( - - - {isRunning && ( - <> -
- - - AI Enhancement in progress… - - - [{formatElapsed(elapsed)}] - -
- This may take a while. - - )} - {status === "completed" && ( -
- - - AI Enhancement completed - -
- )} - {status === "error" && ( -
- - - AI Enhancement encountered an error - -
- )} - {status === "aborted" && ( -
- - - AI Enhancement was skipped - -
- )} -
- - - {/* Render parsed segments inline — text, tool calls, etc. */} - {segments.map((segment, i) => { - if (segment.type === SegmentType.Text) { - if (!segment.text.trim()) { - return null; - } - return ( - - ); - } - - if (segment.type === SegmentType.ToolCall) { - const currentToolName = segment.toolName; - - // Skip if the next non-whitespace segment is the - // same tool (will be grouped from that later segment). - let nextIdx = i + 1; - while ( - nextIdx < segments.length && - segments[nextIdx].type === SegmentType.Text && - segments[nextIdx].text.trim() === "" - ) { - nextIdx++; - } - const nextSeg = segments[nextIdx]; - if ( - nextSeg && - nextSeg.type === SegmentType.ToolCall && - nextSeg.toolName === currentToolName - ) { - return null; - } - - // Collect consecutive same-tool segments backward - const groupItems: ToolCallItem[] = []; - let j = i; - while (j >= 0) { - const seg = segments[j]; - if ( - seg.type === SegmentType.ToolCall && - seg.toolName === currentToolName - ) { - groupItems.unshift({ - text: seg.text, - loading: seg.loading, - failed: seg.failed, - toolName: seg.toolName, - }); - } else if ( - seg.type === SegmentType.Text && - seg.text.trim() === "" - ) { - j--; - continue; - } else { - break; - } - j--; - } - - if (groupItems.length === 1) { - return ( - - ); - } - - return ( - - ); - } - - // Fallback: render as markdown - if (segment.text.trim()) { - return ( - - ); - } - return null; - })} - - {/* Placeholder while waiting for first event */} - {isRunning && segments.length === 0 && ( - - Starting AI enhancement agent… - - )} - - - - {isDone && ( - - - Open Project - - )} - {isRunning && ( - - - Pause & Open - - )} - -
- ); -} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/index.tsx index 6dd5e39393..9aee8256df 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/index.tsx @@ -34,7 +34,6 @@ import { useEffect, useState } from "react"; import { ConfigureProjectForm } from "./ConfigureProjectForm"; import { ImportIntegrationForm } from "./ImportIntegrationForm"; import { MigrationProgressView } from "./MigrationProgressView"; -import { WizardAIEnhancementView } from "./WizardAIEnhancementView"; import { FormContainer, TitleContainer, IconButton } from "./styles"; import { FinalIntegrationParams } from "./types"; @@ -54,11 +53,8 @@ export function ImportIntegration() { const [migrationCompleted, setMigrationCompleted] = useState(false); const [migrationSuccessful, setMigrationSuccessful] = useState(false); const [migrationResponse, setMigrationResponse] = useState(null); - const [aiEnhancementEnabled, setAiEnhancementEnabled] = useState(false); - const steps = aiEnhancementEnabled - ? ["Select Source Project", "Static Migration Progress", "Configure Project", "AI Enhancement"] - : ["Select Source Project", "Static Migration Progress", "Configure Project"]; + const defaultSteps = ["Select Source Integration", "Migration Status", "Create and Open Project"]; const isMultiProject = migratedProjects.length! > 0; @@ -103,26 +99,15 @@ export function ImportIntegration() { }); }; - const handleCreateIntegrationFiles = (project: ProjectRequest, aiFeatureUsed: boolean) => { - console.log("Creating integration files with params:", importParams, "aiFeatureUsed:", aiFeatureUsed); + const handleCreateIntegrationFiles = (project: ProjectRequest) => { + console.log("Creating integration files with params:", importParams); if (migrationResponse) { const params: MigrateRequest = { project: project, textEdits: migrationResponse.textEdits, projects: migratedProjects, - aiFeatureUsed: aiFeatureUsed, - sourcePath: importParams?.importSourcePath, }; rpcClient.getMigrateIntegrationRpcClient().migrateProject(params); - - // Track whether AI enhancement is enabled for the stepper - setAiEnhancementEnabled(aiFeatureUsed); - - if (aiFeatureUsed) { - // Advance to the AI Enhancement step (step 3) - setStep(3); - } - // When AI is not enabled, migrateProject opens the project directly } }; @@ -193,7 +178,7 @@ export function ImportIntegration() { }, [toolPullProgress, importParams, selectedIntegration]); return ( - + @@ -202,7 +187,7 @@ export function ImportIntegration() { - + {step === 0 && ( )} - {step === 2 && } - {step === 3 && } + {step === 2 && } ); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/styles.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/styles.ts index f02ce474d2..ff6f03bfd3 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/styles.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/styles.ts @@ -20,9 +20,9 @@ import styled from "@emotion/styled"; import { Button, Codicon, Typography } from "@wso2/ui-toolkit"; // Main container styles -export const FormContainer = styled.div<{ wide?: boolean }>` - max-width: ${(props: { wide?: boolean }) => (props.wide ? "960px" : "660px")}; - margin: ${(props: { wide?: boolean }) => (props.wide ? "40px 60px" : "80px 120px")}; +export const FormContainer = styled.div` + max-width: 660px; + margin: 80px 120px; overflow-y: auto; max-height: calc(100vh - 100px); @@ -56,45 +56,6 @@ export const ButtonWrapper = styled.div` justify-content: flex-end; `; -export const AIEnhancementSection = styled.div` - margin-top: 28px; - padding-top: 20px; - border-top: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border)); -`; - -export const AIEnhancementTitle = styled.div` - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 12px; - font-size: 12px; - font-weight: 600; - color: var(--vscode-foreground); - text-transform: uppercase; - letter-spacing: 0.04em; -`; - -export const AIEnhancementToggleRow = styled.div` - display: flex; - align-items: flex-start; - gap: 10px; - padding: 10px 12px; - border-radius: 4px; - border: 1px solid var(--vscode-editorWidget-border, var(--vscode-panel-border)); - cursor: pointer; - transition: border-color 0.1s, background 0.1s; - &:hover { - border-color: var(--vscode-focusBorder); - background: var(--vscode-list-hoverBackground); - } -`; - -export const AIEnhancementDescription = styled.div` - font-size: 11px; - color: var(--vscode-descriptionForeground); - margin-top: 2px; -`; - // Form-specific styles export const IntegrationCardGrid = styled.div` display: flex; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/types.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/types.ts index 765e5beba1..6672e07a49 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/types.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/types.ts @@ -75,9 +75,7 @@ export interface MigrationProgressProps { export interface ConfigureProjectFormProps { isMultiProject: boolean; - /** Absolute path to the original source project (e.g. Mule XML directory). */ - importSourcePath?: string; - onNext: (project: ProjectRequest, aiFeatureUsed: boolean) => void; + onNext: (project: ProjectRequest) => void; onBack: () => void; } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/utils.ts index 3fbe490249..1d4c5bc71d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ImportIntegration/utils.ts @@ -44,24 +44,24 @@ export const getMigrationProgressHeaderData = (state: MigrationDisplayState, isM if (state.isSuccess) { if (isMultiProject) { - headerText = "Static Migration Completed"; + headerText = "Migration Completed Successfully!"; headerDesc = - "Your integration project has been successfully migrated through static mapping. You can now move on to configuring your project, with the option to enhance it further using AI."; + "Your project with multiple integrations has been successfully migrated. You can now proceed to the final step to create and open your project."; } else { - headerText = "Static Migration Completed"; + headerText = "Migration Completed Successfully!"; headerDesc = - "Your integration project has been successfully migrated through static mapping. You can now move on to configuring your project, with the option to enhance it further using AI."; + "Your integration has been successfully migrated. You can now proceed to the final step to create and open your integration."; } } else if (state.isFailed) { headerText = "Migration Failed"; headerDesc = "The migration process encountered errors and could not be completed."; } else if (state.isInProgress) { if (isMultiProject) { - headerText = "Static Migration in Progress..."; + headerText = "Migration in Progress..."; headerDesc = "Please wait while we migrate your multi-project integration."; } else { - headerText = "Static Migration in Progress..."; - headerDesc = "Please wait while we set up your new integration project."; + headerText = "Migration in Progress..."; + headerDesc = "Please wait while we set up your new integration."; } } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/LibraryOverview.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/LibraryOverview.tsx index b65f7843a2..7f33df34f7 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/LibraryOverview.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/LibraryOverview.tsx @@ -25,7 +25,6 @@ import { MACHINE_VIEW, ProjectStructure, ProjectStructureArtifactResponse, - VISIBILITY, } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { Button, Codicon, Icon, ThemeColors, Typography } from "@wso2/ui-toolkit"; @@ -314,18 +313,6 @@ const ArtifactCardTitle = styled.p` text-overflow: ellipsis; `; -const PublicBadge = styled.span` - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - margin-left: -8px; - color: ${ThemeColors.ON_SURFACE}; - opacity: 0.7; - line-height: 1; - > * { display: flex; align-items: center; justify-content: center; } -`; - const HighlightMatch = styled.span` font-weight: 700; text-decoration: underline; @@ -522,11 +509,10 @@ interface ArtifactCardProps { icon: React.ReactNode; title: string; query: string; - isPublic?: boolean; onClick: () => void; } -function ArtifactCard({ icon, title, query, isPublic, onClick }: ArtifactCardProps) { +function ArtifactCard({ icon, title, query, onClick }: ArtifactCardProps) { const highlightedTitle = useMemo(() => { if (!query) return <>{title}; const idx = title.toLowerCase().indexOf(query); @@ -548,11 +534,6 @@ function ArtifactCard({ icon, title, query, isPublic, onClick }: ArtifactCardPro > {icon} - {isPublic && ( - - - - )} {highlightedTitle} @@ -759,7 +740,6 @@ export function LibraryOverview({ projectStructure, isNPSupported, projectPath, icon={} title={item.name} query={query} - isPublic={item.visibility === VISIBILITY.PUBLIC} onClick={() => handleItemClick(sectionKey, item)} /> {item.position && item.path && ( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx index 36ff05ba23..356f226910 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/PackageOverview/index.tsx @@ -16,14 +16,14 @@ * under the License. */ -import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { EditableTitle } from "../../../components/EditableTitle"; +import React, { ReactNode, useEffect, useMemo, useState } from "react"; import { ProjectStructure, EVENT_TYPE, MACHINE_VIEW, BuildMode, BI_COMMANDS, + SHARED_COMMANDS, DIRECTORY_MAP, } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -35,7 +35,7 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import ReactMarkdown from "react-markdown"; import { IOpenInConsoleCmdParams, WICommandIds } from "@wso2/wso2-platform-core"; import { AlertBoxWithClose } from "../../AIPanel/AlertBoxWithClose"; -import { getIntegrationTypes, validateComponentName } from "./utils"; +import { getIntegrationTypes } from "./utils"; import { UndoRedoGroup } from "../../../components/UndoRedoGroup"; import { usePlatformExtContext } from "../../../providers/platform-ext-ctx-provider"; import { TopNavigationBar } from "../../../components/TopNavigationBar"; @@ -476,7 +476,7 @@ function DeploymentOptions({ buttonText={isDeployed ? "View in Console" : "Deploy"} isExpanded={expandedOptions.has("devant")} onToggle={() => toggleOption("devant")} - onDeploy={isDeployed ? () => goToDevant() : handleDeploy} + onDeploy={isDeployed? () => goToDevant() : handleDeploy} learnMoreLink={"https://wso2.com/devant/docs"} hasDeployableIntegration={hasDeployableIntegration} secondaryAction={ @@ -493,7 +493,7 @@ function DeploymentOptions({ } /> )} - + (false); const [isNPSupported, setIsNPSupported] = useState(false); - const fetchContext = useCallback(() => { + + const fetchContext = () => { rpcClient .getBIDiagramRpcClient() .getProjectStructure() @@ -697,29 +698,20 @@ export function PackageOverview(props: PackageOverviewProps) { .then((res) => { setReadmeContent(res.content); }); - }, [rpcClient, projectPath]); + }; + + rpcClient?.onProjectContentUpdated((state: boolean) => { + if (state) { + fetchContext(); + } + }); useEffect(() => { fetchContext(); showLoginAlert().then((status) => { setShowAlert(status); }); - }, [projectPath, fetchContext]); - - // Keep a stable ref so the subscription callback always calls the latest fetchContext - // without needing to re-register the listener every time fetchContext changes. - const fetchContextRef = useRef(fetchContext); - fetchContextRef.current = fetchContext; - - useEffect(() => { - if (!rpcClient) return; - const unsubscribe = rpcClient.onProjectContentUpdated((state: boolean) => { - if (state) { - fetchContextRef.current(); - } - }); - return unsubscribe; - }, [rpcClient]); + }, [projectPath]); const deployableIntegrationTypes = useMemo(() => { return getIntegrationTypes(projectStructure); @@ -729,23 +721,6 @@ export function PackageOverview(props: PackageOverviewProps) { return projectStructure?.projectTitle || projectStructure?.projectName; }, [projectStructure]); - const validateTitle = useCallback((value: string): string => { - return validateComponentName(value.trim(), isLibrary) ?? ""; - }, [isLibrary]); - - const handleTitleUpdate = useCallback(async (newTitle: string) => { - await rpcClient.getBIDiagramRpcClient().updatePackageTitle({ - packagePath: projectPath, - title: newTitle, - }); - // Optimistically update the displayed title immediately. - // Do NOT call fetchContext() here — buildProjectsStructure in the backend is async; - // calling getProjectStructure() too early would return stale data and overwrite this update. - // The backend's notifyCurrentWebview() fires after buildProjectsStructure completes, - // which triggers onProjectContentUpdated → fetchContext() as the background confirm. - setProjectStructure(prev => prev ? { ...prev, projectTitle: newTitle } : prev); - }, [projectPath, rpcClient]); - function isEmptyIntegration(): boolean { // Filter out connections that start with underscore const validConnections = projectStructure.directoryMap[DIRECTORY_MAP.CONNECTION]?.filter( @@ -917,19 +892,11 @@ export function PackageOverview(props: PackageOverviewProps) { subtitle={isLibrary ? "Library" : "Integration"} onBack={handleBack} actions={headerActions} - onTitleEdit={handleTitleUpdate} - validateTitle={validateTitle} /> ) : ( - - {integrationTitle} - + {integrationTitle} {isLibrary ? "Library" : "Integration"} @@ -965,7 +932,7 @@ export function PackageOverview(props: PackageOverviewProps) { {!isEmptyIntegration() && ( { - serviceModel && (isHttpService) && ( - <> - - - ) - } - { - serviceModel && (isMcpService) && ( + serviceModel && (isHttpService || isMcpService) && ( <> - )} diff --git a/workspaces/ballerina/component-diagram/src/components/Diagram.tsx b/workspaces/ballerina/component-diagram/src/components/Diagram.tsx index a0d293fc4a..0ceb7c7852 100644 --- a/workspaces/ballerina/component-diagram/src/components/Diagram.tsx +++ b/workspaces/ballerina/component-diagram/src/components/Diagram.tsx @@ -52,7 +52,6 @@ export interface DiagramProps { onAutomationSelect: (automation: CDAutomation) => void; onConnectionSelect: (connection: CDConnection) => void; onDeleteComponent: (component: CDListener | CDService | CDAutomation | CDConnection, nodeType?: string) => void; - onCleanupTestServices?: () => void; } export type GQLFuncListType = Record>; @@ -331,7 +330,7 @@ export function Diagram(props: DiagramProps) { return ( <> - + {diagramEngine && diagramModel && ( diff --git a/workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/AIServiceWidget.tsx b/workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/AIServiceWidget.tsx index b57b85f9e7..f7390373eb 100644 --- a/workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/AIServiceWidget.tsx +++ b/workspaces/ballerina/component-diagram/src/components/nodes/EntryNode/components/AIServiceWidget.tsx @@ -17,9 +17,8 @@ */ import React, { useState } from "react"; -import styled from "@emotion/styled"; import { CDService } from "@wso2/ballerina-core"; -import { Item, Menu, MenuItem, Popover, Icon, ThemeColors } from "@wso2/ui-toolkit"; +import { Item, Menu, MenuItem, Popover, Icon } from "@wso2/ui-toolkit"; import { useDiagramContext } from "../../../DiagramContext"; import { MoreVertIcon } from "../../../../resources/icons/nodes/MoreVertIcon"; import { getEntryNodeFunctionPortName } from "../../../../utils/diagram"; @@ -38,49 +37,6 @@ import { BottomPortWidget } from "./styles"; -type NodeStyleProp = { hovered: boolean }; - -const DashedBox = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - gap: 8px; - width: 100%; - border: 2.5px dashed - ${(props: NodeStyleProp) => (props.hovered ? ThemeColors.HIGHLIGHT : ThemeColors.OUTLINE_VARIANT)}; - border-radius: 8px; - background-color: ${ThemeColors.SURFACE_DIM}; - padding: 8px; -`; - -const IconWithBadge = styled.div` - position: relative; - padding: 4px; - max-width: 32px; - svg { - fill: ${ThemeColors.ON_SURFACE}; - } - > div:first-child { - width: 24px; - height: 24px; - font-size: 24px; - } -`; - -const BeakerBadge = styled.span` - position: absolute; - bottom: -2px; - right: -8px; - font-size: 8px; - color: var(--vscode-editorWarning-foreground, #cca700); -`; - -const isTestService = (model: EntryNodeModel): boolean => { - const filePath = (model.node as CDService)?.location?.filePath || ''; - return filePath.endsWith('_agent_chat.bal'); -}; - // Utility functions specific to AI Service const getNodeTitle = (model: EntryNodeModel) => { const serviceName = (model.node as any)?.serviceName || @@ -143,13 +99,10 @@ export function AIServiceWidget({ model, engine }: BaseNodeWidgetProps) { { id: "delete", label: "Delete", onClick: () => onDeleteComponent(model.node) }, ]; - const isTest = isTestService(model); - const BoxComponent = isTest ? DashedBox : Box; - return ( - + !readonly && setIsHovered(true)} onMouseLeave={() => !readonly && setIsHovered(false)} @@ -157,14 +110,7 @@ export function AIServiceWidget({ model, engine }: BaseNodeWidgetProps) { onMouseUp={!readonly ? handleMouseUp : undefined} readonly={readonly} > - {isTest ? ( - - - - - ) : ( - - )} +
{getNodeTitle(model)} {getNodeDescription(model)} @@ -179,7 +125,7 @@ export function AIServiceWidget({ model, engine }: BaseNodeWidgetProps) { - + ` color: ${ThemeColors.ON_SURFACE}; `; -const DashedCircle = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - width: ${LISTENER_NODE_HEIGHT}px; - height: ${LISTENER_NODE_HEIGHT}px; - border: 2.5px dashed - ${(props: NodeStyleProp) => (props.hovered ? ThemeColors.HIGHLIGHT : ThemeColors.OUTLINE_VARIANT)}; - border-radius: 50%; - background-color: ${ThemeColors.SURFACE_DIM}; - color: ${ThemeColors.ON_SURFACE}; - position: relative; -`; - -const IconWithBadge = styled.div` - position: relative; - padding: 4px; - svg { - fill: ${ThemeColors.ON_SURFACE}; - } -`; - -const BeakerBadge = styled.span` - position: absolute; - bottom: -2px; - right: -6px; - font-size: 8px; - color: var(--vscode-editorWarning-foreground, #cca700); -`; - const LeftPortWidget = styled(PortWidget)` margin-top: -3px; `; @@ -158,11 +128,6 @@ interface ListenerNodeWidgetProps { export interface NodeWidgetProps extends Omit { } -const isTestListener = (model: ListenerNodeModel): boolean => { - const filePath = (model.node as CDListener)?.location?.filePath || ''; - return filePath.endsWith('_agent_chat.bal'); -}; - export function ListenerNodeWidget(props: ListenerNodeWidgetProps) { const { model, engine } = props; const [isHovered, setIsHovered] = React.useState(false); @@ -225,9 +190,6 @@ export function ListenerNodeWidget(props: ListenerNodeWidgetProps) { : []) ]; - const isTest = isTestListener(model); - const CircleComponent = isTest ? DashedCircle : Circle; - return ( - + - {isTest ? ( - - {getNodeIcon()} - - - ) : ( - {getNodeIcon()} - )} + {getNodeIcon()} + - - + { if (initialTraceData) { setCurrentTraceData(initialTraceData); - setSessionTraces([]); - setCurrentSessionId(undefined); setViewMode('details'); } }, [initialTraceData]); @@ -101,12 +100,6 @@ export function TraceVisualizer({ }, []); const handleViewSession = () => { - // If we already have session traces loaded, just switch back to overview - if (sessionTraces.length > 0 && currentSessionId) { - setViewMode('overview'); - return; - } - if (!currentTraceData) return; // Extract session ID from the current trace @@ -120,6 +113,7 @@ export function TraceVisualizer({ } if (extractedSessionId) { + // Request session traces from extension if (window.vscode) { window.vscode.postMessage({ command: 'requestSessionTraces', diff --git a/workspaces/ballerina/trace-visualizer/src/components/AIBadge.tsx b/workspaces/ballerina/trace-visualizer/src/components/AIBadge.tsx index 8cb7070ffc..c87fed0922 100644 --- a/workspaces/ballerina/trace-visualizer/src/components/AIBadge.tsx +++ b/workspaces/ballerina/trace-visualizer/src/components/AIBadge.tsx @@ -20,7 +20,7 @@ import styled from "@emotion/styled"; import { Icon } from "@wso2/ui-toolkit"; interface AIBadgeProps { - type: 'invoke' | 'chat' | 'tool' | 'kb_retrieve' | 'kb_ingest' | 'embeddings' | 'generate_content' | 'other'; + type: 'invoke' | 'chat' | 'tool' | 'other'; } const AISpanBadge = styled.span<{ type: string }>` @@ -38,10 +38,6 @@ const AISpanBadge = styled.span<{ type: string }>` case 'invoke': return 'var(--vscode-terminal-ansiCyan)'; case 'chat': return 'var(--vscode-terminalSymbolIcon-optionForeground)'; case 'tool': return 'var(--vscode-terminal-ansiBrightMagenta)'; - case 'kb_retrieve': return 'var(--vscode-charts-purple)'; - case 'kb_ingest': return 'var(--vscode-charts-purple)'; - case 'embeddings': return 'var(--vscode-terminalSymbolIcon-optionForeground)'; - case 'generate_content': return 'var(--vscode-charts-green)'; default: return 'var(--vscode-badge-foreground)'; } }}; @@ -58,10 +54,6 @@ export function AIBadge({ type }: AIBadgeProps) { case 'invoke': return 'bi-ai-agent'; case 'chat': return 'bi-chat'; case 'tool': return 'bi-wrench'; - case 'kb_retrieve': return 'bi-ai-search'; - case 'kb_ingest': return 'bi-import'; - case 'embeddings': return 'bi-ai-model'; - case 'generate_content': return 'bi-doc'; default: return 'bi-action'; } }; diff --git a/workspaces/ballerina/trace-visualizer/src/components/SpanDetails.tsx b/workspaces/ballerina/trace-visualizer/src/components/SpanDetails.tsx index 4e27ce24fc..5081d39ea7 100644 --- a/workspaces/ballerina/trace-visualizer/src/components/SpanDetails.tsx +++ b/workspaces/ballerina/trace-visualizer/src/components/SpanDetails.tsx @@ -85,10 +85,6 @@ const SpanIcon = styled.span<{ type: string; spanKind?: string }>` case 'invoke': return 'var(--vscode-terminal-ansiCyan)'; case 'chat': return 'var(--vscode-terminalSymbolIcon-optionForeground)'; case 'tool': return 'var(--vscode-terminal-ansiBrightMagenta)'; - case 'kb_retrieve': return 'var(--vscode-charts-purple)'; - case 'kb_ingest': return 'var(--vscode-charts-purple)'; - case 'embeddings': return 'var(--vscode-terminalSymbolIcon-optionForeground)'; - case 'generate_content': return 'var(--vscode-charts-green)'; case 'other': // Use span kind colors for non-AI spans (matching TraceDetails) switch (props.spanKind?.toLowerCase()) { @@ -254,18 +250,6 @@ const SubSectionTitle = styled.h4` gap: 6px; `; -const InputContentBlock = styled.div` - background-color: var(--vscode-input-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 4px; - padding: 12px; - font-size: 13px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; - color: var(--vscode-editor-foreground); -`; - const ErrorContent = styled.div` background-color: var(--vscode-input-background); border: 1px solid var(--vscode-errorForeground); @@ -461,10 +445,6 @@ const EXCLUDED_ATTRIBUTE_KEYS = [ 'gen_ai.tool.output', 'gen_ai.system_instructions', 'gen_ai.input.tools', - 'gen_ai.input.content', - 'gen_ai.knowledge_base.ingest.input.chunks', - 'gen_ai.knowledge_base.retrieve.input.query', - 'gen_ai.knowledge_base.retrieve.output', 'error.message' ]; @@ -481,10 +461,6 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT if (operationName.startsWith('invoke_agent')) return 'invoke'; if (operationName.startsWith('chat') || spanName?.toLowerCase().startsWith('chat')) return 'chat'; if (operationName.startsWith('execute_tool') || spanName?.toLowerCase().startsWith('execute_tool')) return 'tool'; - if (operationName.startsWith('knowledge_base_retrieve') || spanName?.toLowerCase().startsWith('knowledge_base_retrieve')) return 'kb_retrieve'; - if (operationName.startsWith('knowledge_base_ingest') || spanName?.toLowerCase().startsWith('knowledge_base_ingest')) return 'kb_ingest'; - if (operationName.startsWith('embeddings') || spanName?.toLowerCase().startsWith('embeddings')) return 'embeddings'; - if (operationName.startsWith('generate_content') || spanName?.toLowerCase().startsWith('generate_content')) return 'generate_content'; return 'other'; }, [operationName, spanName]); @@ -540,18 +516,12 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT const inputMessages = getAttributeValue(spanData.attributes, 'gen_ai.input.messages'); const toolArguments = getAttributeValue(spanData.attributes, 'gen_ai.tool.arguments'); const inputTools = getAttributeValue(spanData.attributes, 'gen_ai.input.tools'); - const inputContent = getAttributeValue(spanData.attributes, 'gen_ai.input.content'); - const kbIngestChunks = getAttributeValue(spanData.attributes, 'gen_ai.knowledge_base.ingest.input.chunks'); - const kbRetrieveQuery = getAttributeValue(spanData.attributes, 'gen_ai.knowledge_base.retrieve.input.query'); return { systemInstructions, messages: inputMessages || toolArguments, messagesLabel: toolArguments ? 'Tool Arguments' : (operationName?.includes('invoke_agent') ? 'User' : 'Messages'), - tools: inputTools, - inputContent, - kbIngestChunks, - kbRetrieveQuery + tools: inputTools }; }, [spanData.attributes, operationName]); @@ -559,13 +529,11 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT const outputMessages = getAttributeValue(spanData.attributes, 'gen_ai.output.messages'); const toolOutput = getAttributeValue(spanData.attributes, 'gen_ai.tool.output'); const errorMessage = getAttributeValue(spanData.attributes, 'error.message'); - const kbRetrieveOutput = getAttributeValue(spanData.attributes, 'gen_ai.knowledge_base.retrieve.output'); return { messages: outputMessages || toolOutput, messagesLabel: toolOutput ? 'Tool Output' : 'Messages', - error: errorMessage, - kbRetrieveOutput + error: errorMessage }; }, [spanData.attributes]); @@ -574,17 +542,13 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT if (!searchQuery) return true; return textContainsSearch(inputData.systemInstructions, searchQuery) || textContainsSearch(inputData.messages, searchQuery) || - textContainsSearch(inputData.tools, searchQuery) || - textContainsSearch(inputData.inputContent, searchQuery) || - textContainsSearch(inputData.kbIngestChunks, searchQuery) || - textContainsSearch(inputData.kbRetrieveQuery, searchQuery); + textContainsSearch(inputData.tools, searchQuery); }, [searchQuery, inputData]); const outputMatches = useMemo(() => { if (!searchQuery) return true; return textContainsSearch(outputData.messages, searchQuery) || - textContainsSearch(outputData.error, searchQuery) || - textContainsSearch(outputData.kbRetrieveOutput, searchQuery); + textContainsSearch(outputData.error, searchQuery); }, [searchQuery, outputData]); const metricsMatch = useMemo(() => { @@ -648,8 +612,8 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT return technicalIdsMatch || filteredAdvancedAttributes.length > 0; }, [searchQuery, technicalIdsMatch, filteredAdvancedAttributes]); - const hasInput = inputData.systemInstructions || inputData.messages || inputData.tools || inputData.inputContent || inputData.kbIngestChunks || inputData.kbRetrieveQuery; - const hasOutput = outputData.messages || outputData.error || outputData.kbRetrieveOutput; + const hasInput = inputData.systemInstructions || inputData.messages || inputData.tools; + const hasOutput = outputData.messages || outputData.error; const hasError = !!outputData.error; const noMatches = searchQuery && !inputMatches && !outputMatches && !metricsMatch && !advancedMatches; @@ -701,10 +665,6 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT {spanType === 'invoke' && 'Invoke Agent - '} {spanType === 'chat' && 'Chat - '} {spanType === 'tool' && 'Execute Tool - '} - {spanType === 'kb_retrieve' && 'Knowledge Base Retrieve - '} - {spanType === 'kb_ingest' && 'Knowledge Base Ingest - '} - {spanType === 'embeddings' && 'Embeddings - '} - {spanType === 'generate_content' && 'Generate Content - '} {spanType === 'other' && spanKind.toLowerCase() === 'server' && 'Server - '} {spanType === 'other' && spanKind.toLowerCase() === 'client' && 'Client - '} @@ -892,31 +852,6 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT /> )} - {inputData.inputContent && textContainsSearch(inputData.inputContent, searchQuery) && ( - - Input Content - - {highlightText(inputData.inputContent, searchQuery)} - - - )} - {inputData.kbRetrieveQuery && textContainsSearch(inputData.kbRetrieveQuery, searchQuery) && ( - - Query - - {highlightText(inputData.kbRetrieveQuery, searchQuery)} - - - )} - {inputData.kbIngestChunks && textContainsSearch(inputData.kbIngestChunks, searchQuery) && ( - - - - )} )} @@ -974,15 +909,6 @@ export function SpanDetails({ spanData, spanName, totalInputTokens, totalOutputT /> )} - {outputData.kbRetrieveOutput && textContainsSearch(outputData.kbRetrieveOutput, searchQuery) && ( - - - - )} )} diff --git a/workspaces/ballerina/trace-visualizer/src/components/WaterfallView.tsx b/workspaces/ballerina/trace-visualizer/src/components/WaterfallView.tsx index 0ef79b4e21..284b1cfcfb 100644 --- a/workspaces/ballerina/trace-visualizer/src/components/WaterfallView.tsx +++ b/workspaces/ballerina/trace-visualizer/src/components/WaterfallView.tsx @@ -400,17 +400,13 @@ const getSpanBgColor = (type: string) => { return 'var(--vscode-editor-background)'; }; -const getSpanType = (span: SpanData): 'invoke' | 'chat' | 'tool' | 'kb_retrieve' | 'kb_ingest' | 'embeddings' | 'generate_content' | 'error' | 'client' | 'server' | 'other' => { +const getSpanType = (span: SpanData): 'invoke' | 'chat' | 'tool' | 'error' | 'client' | 'server' | 'other' => { if (span.status?.code === 2) return 'error'; const operationName = span.attributes?.find(attr => attr.key === 'gen_ai.operation.name')?.value || ''; if (operationName.startsWith('invoke_agent')) return 'invoke'; if (operationName.startsWith('chat') || span.name.toLowerCase().startsWith('chat')) return 'chat'; if (operationName.startsWith('execute_tool') || span.name.toLowerCase().startsWith('execute_tool')) return 'tool'; - if (operationName.startsWith('knowledge_base_retrieve') || span.name.toLowerCase().startsWith('knowledge_base_retrieve')) return 'kb_retrieve'; - if (operationName.startsWith('knowledge_base_ingest') || span.name.toLowerCase().startsWith('knowledge_base_ingest')) return 'kb_ingest'; - if (operationName.startsWith('embeddings') || span.name.toLowerCase().startsWith('embeddings')) return 'embeddings'; - if (operationName.startsWith('generate_content') || span.name.toLowerCase().startsWith('generate_content')) return 'generate_content'; const kind = getSpanKindString(span.kind); if (kind === 'client') return 'client'; @@ -424,10 +420,6 @@ const getTypeLabel = (type: string): string => { case 'invoke': return 'Agent'; case 'chat': return 'Model'; case 'tool': return 'Tool'; - case 'kb_retrieve': return 'Knowledge Base Retrieve'; - case 'kb_ingest': return 'Knowledge Base Ingest'; - case 'embeddings': return 'Embeddings'; - case 'generate_content': return 'Generate Content'; case 'error': return 'Error'; case 'client': return 'Client'; case 'server': return 'Server'; @@ -440,10 +432,6 @@ const getTypeIcon = (type: string): string => { case 'invoke': return 'bi-ai-agent'; case 'chat': return 'bi-chat'; case 'tool': return 'bi-wrench'; - case 'kb_retrieve': return 'bi-ai-search'; - case 'kb_ingest': return 'bi-import'; - case 'embeddings': return 'bi-ai-model'; - case 'generate_content': return 'bi-doc'; case 'error': return 'bi-error'; case 'client': return 'bi-arrow-outward'; case 'server': return 'bi-server'; diff --git a/workspaces/ballerina/trace-visualizer/src/utils.tsx b/workspaces/ballerina/trace-visualizer/src/utils.tsx index 5779936cf9..be5406c6f6 100644 --- a/workspaces/ballerina/trace-visualizer/src/utils.tsx +++ b/workspaces/ballerina/trace-visualizer/src/utils.tsx @@ -96,7 +96,7 @@ export const getSpanKindLabel = (kind: string | number): string => { }; export const stripSpanPrefix = (spanName: string): string => { - const prefixes = ['invoke_agent ', 'execute_tool ', 'chat ', 'knowledge_base_retrieve ', 'knowledge_base_ingest ', 'embeddings ', 'generate_content ']; + const prefixes = ['invoke_agent ', 'execute_tool ', 'chat ']; for (const prefix of prefixes) { if (spanName.startsWith(prefix)) { return spanName.substring(prefix.length); @@ -105,15 +105,11 @@ export const stripSpanPrefix = (spanName: string): string => { return spanName; }; -export const getSpanTypeBadge = (span: SpanData): 'invoke' | 'chat' | 'tool' | 'kb_retrieve' | 'kb_ingest' | 'embeddings' | 'generate_content' | 'other' => { +export const getSpanTypeBadge = (span: SpanData): 'invoke' | 'chat' | 'tool' | 'other' => { const operationName = span.attributes?.find(attr => attr.key === 'gen_ai.operation.name')?.value || ''; if (operationName.startsWith('invoke_agent')) return 'invoke'; if (operationName.startsWith('chat') || span.name.toLowerCase().startsWith('chat')) return 'chat'; if (operationName.startsWith('execute_tool') || span.name.toLowerCase().startsWith('execute_tool')) return 'tool'; - if (operationName.startsWith('knowledge_base_retrieve') || span.name.toLowerCase().startsWith('knowledge_base_retrieve')) return 'kb_retrieve'; - if (operationName.startsWith('knowledge_base_ingest') || span.name.toLowerCase().startsWith('knowledge_base_ingest')) return 'kb_ingest'; - if (operationName.startsWith('embeddings') || span.name.toLowerCase().startsWith('embeddings')) return 'embeddings'; - if (operationName.startsWith('generate_content') || span.name.toLowerCase().startsWith('generate_content')) return 'generate_content'; return 'other'; }; @@ -260,10 +256,6 @@ export const getSpanLabel = (type: string) => { case 'invoke': return 'Invoke Agent'; case 'chat': return 'Chat'; case 'tool': return 'Execute Tool'; - case 'kb_retrieve': return 'Knowledge Base Retrieve'; - case 'kb_ingest': return 'Knowledge Base Ingest'; - case 'embeddings': return 'Embeddings'; - case 'generate_content': return 'Generate Content'; default: return 'Operation'; } }; @@ -348,7 +340,7 @@ export function formatDate(isoString: string): string { /** * Gets the appropriate icon name for a span type */ -export function getSpanIconName(spanType: 'invoke' | 'chat' | 'tool' | 'kb_retrieve' | 'kb_ingest' | 'embeddings' | 'generate_content' | 'other', spanKind?: string): string { +export function getSpanIconName(spanType: 'invoke' | 'chat' | 'tool' | 'other', spanKind?: string): string { switch (spanType) { case 'invoke': return 'bi-ai-agent'; @@ -356,14 +348,6 @@ export function getSpanIconName(spanType: 'invoke' | 'chat' | 'tool' | 'kb_retri return 'bi-chat'; case 'tool': return 'bi-wrench'; - case 'kb_retrieve': - return 'bi-ai-search'; - case 'kb_ingest': - return 'bi-import'; - case 'embeddings': - return 'bi-ai-model'; - case 'generate_content': - return 'bi-doc'; case 'other': // For non-AI spans, use icons based on span kind (server/client) switch (spanKind?.toLowerCase()) { @@ -390,14 +374,6 @@ export function getSpanColor(type: string): string { return 'var(--vscode-terminalSymbolIcon-optionForeground)'; case 'tool': return 'var(--vscode-terminal-ansiBrightMagenta)'; - case 'kb_retrieve': - return 'var(--vscode-charts-purple)'; - case 'kb_ingest': - return 'var(--vscode-charts-purple)'; - case 'embeddings': - return 'var(--vscode-terminalSymbolIcon-optionForeground)'; - case 'generate_content': - return 'var(--vscode-charts-green)'; case 'error': return 'var(--vscode-terminal-ansiRed)'; case 'client': diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx index 041e980bea..4ca834dd71 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx @@ -111,7 +111,6 @@ enum TypeKind { interface TypeCreatorTabProps { editingType: Type; newType: boolean; - filePath: string; isGraphql: boolean; initialTypeKind: TypeNodeKind; onTypeSave: (type: Type) => Promise; @@ -129,8 +128,7 @@ export function TypeCreatorTab(props: TypeCreatorTabProps) { onTypeSave, isSaving, setIsSaving, - onTypeChange, - filePath + onTypeChange } = props; const [type, setType] = useState(editingType); @@ -356,7 +354,7 @@ export function TypeCreatorTab(props: TypeCreatorTabProps) { }); const response = await rpcClient.getBIDiagramRpcClient().getExpressionDiagnostics({ - filePath: type?.codedata?.lineRange?.fileName || filePath, + filePath: type?.codedata?.lineRange?.fileName || "types.bal", context: { expression: value, startLine: { diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx index ed68414f88..7cb116dfa3 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx @@ -36,7 +36,6 @@ namespace S { interface TypeEditorProps { type?: Type; - filePath: string; imports?: Imports; rpcClient: BallerinaRpcClient; onTypeChange: (type: Type, rename?: boolean) => void; @@ -174,7 +173,6 @@ export function TypeEditor(props: TypeEditorProps) { isGraphql={isGraphql} initialTypeKind={initialTypeKind} onTypeSave={onTypeSave} - filePath={props.filePath} isSaving={isSaving} setIsSaving={setIsSaving} /> @@ -194,7 +192,6 @@ export function TypeEditor(props: TypeEditorProps) { ((props, re const handleTypeItemClick = async (item: TypeHelperItem): Promise => { const response = await onTypeItemClick(item) as AddImportItemResponse; - if (response) { - if (response.prefix && response.moduleId) { - const importStatement = { - [response.prefix]: response.moduleId - }; - onUpdateImports(importStatement); + if (response.prefix && response.moduleId) { + const importStatement = { + [response.prefix]: response.moduleId } - return response.template; + onUpdateImports(importStatement); } - return ""; + return response.template; }; const handleTypeCreate = (typeName?: string) => { diff --git a/workspaces/ballerina/type-editor/src/TypeHelper/TypeHelper.tsx b/workspaces/ballerina/type-editor/src/TypeHelper/TypeHelper.tsx index 99590135cd..e4fbfc3495 100644 --- a/workspaces/ballerina/type-editor/src/TypeHelper/TypeHelper.tsx +++ b/workspaces/ballerina/type-editor/src/TypeHelper/TypeHelper.tsx @@ -208,17 +208,22 @@ export const TypeHelperComponent = (props: TypeHelperComponentProps) => { const handleTypeItemClick = (item: TypeHelperItem) => { - const prefixRegex = /[a-zA-Z0-9_':]*$/; - const suffixRegex = /^[a-zA-Z0-9_':]*/; - const prefixMatch = currentType.slice(0, currentCursorPosition).match(prefixRegex); - const suffixMatch = currentType.slice(currentCursorPosition).match(suffixRegex); - const prefixCursorPosition = currentCursorPosition - (prefixMatch?.[0]?.length ?? 0); - const suffixCursorPosition = currentCursorPosition + (suffixMatch?.[0]?.length ?? 0); - - onChange( - currentType.slice(0, prefixCursorPosition) + item.insertText + currentType.slice(suffixCursorPosition), - prefixCursorPosition + item.insertText.length - ); + const trimmedType = currentType.trimEnd(); + if ( + /\|$/.test(trimmedType) || + /readonly\s*&$/.test(trimmedType) + ) { + const newType = currentType + item.insertText; + onChange( + newType, + newType.length + ); + } else { + onChange( + item.insertText, + item.insertText.length + ); + } onCloseCompletions?.(); onClose(); }; diff --git a/workspaces/bi/bi-extension/src/extension.ts b/workspaces/bi/bi-extension/src/extension.ts index 01345dc07b..d62ce9e0eb 100644 --- a/workspaces/bi/bi-extension/src/extension.ts +++ b/workspaces/bi/bi-extension/src/extension.ts @@ -19,35 +19,22 @@ import * as vscode from 'vscode'; const DEPRECATION_SHOWN_KEY = 'bi.deprecation.noticeShown'; -const BI_EXTENSION_ID = 'wso2.ballerina-integrator'; -const CMD_UNINSTALL_EXTENSION = 'workbench.extensions.uninstallExtension'; -const CMD_RELOAD_WINDOW = 'workbench.action.reloadWindow'; -const BTN_REMOVE = 'Remove BI Extension'; -const BTN_RELOAD = 'Reload Window'; +const WI_EXPLORER_VIEW_ID = 'wso2-integrator.explorer'; export function activate(context: vscode.ExtensionContext) { const alreadyShown = context.globalState.get(DEPRECATION_SHOWN_KEY); if (!alreadyShown) { vscode.window.showWarningMessage( - 'WSO2 Integrator is now installed and ready to use. ' + - 'This replaces the "WSO2 Integrator: BI extension" which is no longer needed. ' + - 'You can safely remove the "WSO2 Integrator: BI extension" from VS Code Editor.', - BTN_REMOVE + 'WSO2 Integrator: BI has been deprecated. ' + + 'WSO2 Integrator (WI) has been installed and provides all the same functionality with continued updates.', + 'Open WSO2 Integrator' ).then(async action => { - await context.globalState.update(DEPRECATION_SHOWN_KEY, true); - if (action === BTN_REMOVE) { + if (action === 'Open WSO2 Integrator') { try { - await vscode.commands.executeCommand(CMD_UNINSTALL_EXTENSION, BI_EXTENSION_ID); - const reload = await vscode.window.showInformationMessage( - 'WSO2 Integrator: BI extension has been uninstalled. Please reload the window to complete the process.', - BTN_RELOAD - ); - if (reload === BTN_RELOAD) { - await vscode.commands.executeCommand(CMD_RELOAD_WINDOW); - } + await vscode.commands.executeCommand(`${WI_EXPLORER_VIEW_ID}.focus`); + await context.globalState.update(DEPRECATION_SHOWN_KEY, true); } catch (error) { - console.error('Failed to uninstall WSO2 Integrator: BI extension:', error); - await vscode.window.showErrorMessage('Failed to uninstall "WSO2 Integrator: BI extension". Please uninstall it manually from the Extensions view.'); + console.error('Failed to focus WSO2 Integrator explorer:', error); } } }); diff --git a/workspaces/choreo/choreo-extension/package.json b/workspaces/choreo/choreo-extension/package.json index 905e083e99..09bbd75588 100644 --- a/workspaces/choreo/choreo-extension/package.json +++ b/workspaces/choreo/choreo-extension/package.json @@ -153,11 +153,11 @@ } ], "icons": { - "choreo-choreo-2": { + "distro-choreo-2": { "description": "choreo-2", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f1aa" + "fontCharacter": "\\f18a" } } } diff --git a/workspaces/common-libs/copilot-utilities/package.json b/workspaces/common-libs/copilot-utilities/package.json index 9bf403846d..387b44ed5b 100644 --- a/workspaces/common-libs/copilot-utilities/package.json +++ b/workspaces/common-libs/copilot-utilities/package.json @@ -6,11 +6,11 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "exports": { - "./context-management": "./lib/context-management/index.js" + "./compaction": "./lib/compaction/index.js" }, "typesVersions": { "*": { - "context-management": ["lib/context-management/index.d.ts"] + "compaction": ["lib/compaction/index.d.ts"] } }, "scripts": { diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/CompactionEngine.ts b/workspaces/common-libs/copilot-utilities/src/compaction/CompactionEngine.ts new file mode 100644 index 0000000000..5135810851 --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/CompactionEngine.ts @@ -0,0 +1,286 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { + CompactionEngineConfig, + CompactionMetadata, + CompactionOptions, + CompactionResult, + SummarizationCallback, + TokenEstimationContext, +} from './types'; +import { TokenEstimator } from './core/TokenEstimator'; +import { ThresholdCalculator } from './core/ThresholdCalculator'; +import { SummarizationService } from './core/SummarizationService'; +import { createContinuationMessages } from './utils/messageUtils'; + +/** + * CompactionEngine — main orchestrator for context compaction. + * + * Provider-agnostic: receives summarization via callback, no SDK dependency. + * + * Usage: + * 1. Construct with config (modelConfig + tokenCountCallback) + * 2. Call setSummarizationCallback() once the LLM model is available (M02) + * 3. Call shouldCompact() to check if compaction is needed + * 4. Call compact() to perform compaction + */ +export class CompactionEngine { + private tokenEstimator: TokenEstimator; + private thresholdCalculator: ThresholdCalculator; + private summarizationService: SummarizationService | null; + private isCompacting: boolean = false; // Concurrency guard + + constructor(config: CompactionEngineConfig) { + this.tokenEstimator = new TokenEstimator(config.tokenCountCallback); + this.thresholdCalculator = new ThresholdCalculator(config.modelConfig); + this.summarizationService = config.summarizationCallback + ? new SummarizationService(config.summarizationCallback) + : null; + } + + /** + * Bind or replace the summarization callback. + * Must be called before compact() with the caller's authenticated model instance. + */ + setSummarizationCallback(callback: SummarizationCallback): void { + this.summarizationService = new SummarizationService(callback); + } + + /** + * Check if a summarization callback has been bound. + */ + hasSummarizationCallback(): boolean { + return this.summarizationService !== null; + } + + /** + * Update token estimation context with actual usage data from streamText. + * Call this after each LLM step for most accurate threshold checks. + */ + updateTokenContext(context: TokenEstimationContext): void { + this.tokenEstimator.updateContext(context); + } + + /** + * Check if the message history is above the auto-compaction threshold. + */ + async shouldCompact(messages: any[]): Promise { + const tokenCount = await this.tokenEstimator.estimateTokens(messages); + return this.thresholdCalculator.isAboveAutoCompactThreshold(tokenCount); + } + + /** + * Get the current token status of the message history. + */ + async getTokenStatus(messages: any[]): Promise<{ + currentTokens: number; + threshold: number; + percentageUsed: number; + isAboveThreshold: boolean; + }> { + const tokenCount = await this.tokenEstimator.estimateTokens(messages); + const threshold = this.thresholdCalculator.getAutoCompactThreshold(); + return { + currentTokens: tokenCount, + threshold, + percentageUsed: (tokenCount / threshold) * 100, + isAboveThreshold: tokenCount >= threshold, + }; + } + + /** + * Compact the message history. + * + * Returns a CompactionResult with success=false (and original messages) on error, + * so the caller can decide whether to continue without compaction (C10 fix). + */ + async compact(messages: any[], options: CompactionOptions): Promise { + let cachedOriginalTokens: number | undefined; + try { + // Compute token estimate once to reuse in case of fail branches. + cachedOriginalTokens = await this.tokenEstimator.estimateTokens(messages); + + // Concurrency guard — prevent parallel compaction calls gracefully + if (this.isCompacting) { + console.warn('[CompactionEngine] Compaction already in progress. Bypassing request.'); + return { + success: false, + originalTokens: cachedOriginalTokens, + compactedTokens: 0, + reductionPercentage: 0, + compactedMessages: messages, + summary: '', + retriesUsed: 0, + metadata: { + compactedAt: Date.now(), + originalMessageCount: messages.length, + originalTokenEstimate: cachedOriginalTokens, + compactedTokenEstimate: 0, + retries: 0, + mode: options.mode, + userInstructions: options.customInstructions, + }, + }; + } + + this.isCompacting = true; + + // Ensure summarization callback is bound + if (!this.summarizationService) { + throw new Error( + 'Summarization callback not set. Call setSummarizationCallback() before compact().' + ); + } + + // Validate messages at engine boundary + this.validateMessages(messages); + + // We pass the already estimated tokens to bypass re-computation if desired, + // or let compactWithRetry handle it. + return await this.compactWithRetry(messages, options, 0); + } catch (error) { + // Graceful degradation — return failure result instead of throwing + console.error('[CompactionEngine] Compaction failed:', error); + + return { + success: false, + originalTokens: cachedOriginalTokens ?? 0, + compactedTokens: 0, + reductionPercentage: 0, + compactedMessages: messages, + summary: '', + retriesUsed: 0, + metadata: { + compactedAt: Date.now(), + originalMessageCount: messages.length, + originalTokenEstimate: cachedOriginalTokens ?? 0, + compactedTokenEstimate: 0, + retries: 0, + mode: options.mode, + userInstructions: options.customInstructions, + }, + }; + } finally { + this.isCompacting = false; + } + } + + /** + * Validate that messages conform to expected structure. + */ + private validateMessages(messages: any[]): void { + if (!Array.isArray(messages)) { + throw new Error('Messages must be an array'); + } + + for (const msg of messages) { + if (!msg.role || !['user', 'assistant', 'system', 'tool'].includes(msg.role)) { + throw new Error(`Invalid message role: ${msg.role}`); + } + if (msg.content === undefined || msg.content === null) { + throw new Error('Message missing content property'); + } + } + } + + /** + * Core compaction with retry logic. + * + * Always retries from ORIGINAL messages (never re-summarizes a summary). + * On retry, passes targetTokenBudget to guide a more concise output. + */ + private async compactWithRetry( + messages: any[], + options: CompactionOptions, + retryCount: number + ): Promise { + const maxRetries = options.maxRetries ?? 3; + const originalTokens = await this.tokenEstimator.estimateTokens(messages); + const threshold = this.thresholdCalculator.getAutoCompactThreshold(); + + // Always retries from ORIGINAL messages (never re-summarizes a summary). + const targetTokenBudget = retryCount > 0 + ? Math.floor(threshold * 0.5) // Aim for 50% of threshold on retries + : undefined; + + // Summarize + const summary = await this.summarizationService!.summarize( + messages, + options.customInstructions, + options.abortSignal, + targetTokenBudget + ); + + // Build continuation messages with optional project state + const continuationMessages = createContinuationMessages(summary, options.projectState); + const compactedMessages = [...continuationMessages]; + + const compactedTokens = await this.tokenEstimator.estimateTokens(compactedMessages); + const reductionPercentage = ((originalTokens - compactedTokens) / originalTokens) * 100; + + // If still above threshold, retry from ORIGINAL messages with stricter budget + if (compactedTokens >= threshold && retryCount < maxRetries) { + console.warn( + `[CompactionEngine] Summary still ${compactedTokens} tokens (threshold: ${threshold}). ` + + `Re-summarizing original messages with tighter budget... ` + + `(${retryCount + 1}/${maxRetries})` + ); + return this.compactWithRetry(messages, options, retryCount + 1); + } + + // Clear cache for fresh start after compaction + this.tokenEstimator.clearCache(); + + // Compaction audit metadata + const metadata: CompactionMetadata = { + compactedAt: Date.now(), + originalMessageCount: messages.length, + originalTokenEstimate: originalTokens, + compactedTokenEstimate: compactedTokens, + retries: retryCount, + mode: options.mode, + userInstructions: options.customInstructions, + }; + + if (compactedTokens >= threshold) { + console.error(`[CompactionEngine] Failed to meet token budget after ${retryCount} retries. Final size: ${compactedTokens}`); + return { + success: false, + originalTokens, + compactedTokens, + reductionPercentage, + compactedMessages, + summary, + retriesUsed: retryCount, + metadata, + }; + } + + return { + success: true, + originalTokens, + compactedTokens, + reductionPercentage, + compactedMessages, + summary, + retriesUsed: retryCount, + metadata, + }; + } +} diff --git a/workspaces/common-libs/copilot-utilities/src/context-management/defaults.ts b/workspaces/common-libs/copilot-utilities/src/compaction/config/defaults.ts similarity index 50% rename from workspaces/common-libs/copilot-utilities/src/context-management/defaults.ts rename to workspaces/common-libs/copilot-utilities/src/compaction/config/defaults.ts index 35d40de76d..5c964e5444 100644 --- a/workspaces/common-libs/copilot-utilities/src/context-management/defaults.ts +++ b/workspaces/common-libs/copilot-utilities/src/compaction/config/defaults.ts @@ -16,12 +16,20 @@ * under the License. */ -/** Anthropic API default trigger for compact_20260112 (tokens). */ -export const DEFAULT_COMPACT_TRIGGER = 500_000; - -/** Trigger for clear_tool_uses_20250919 (tokens). Fires before compact to reduce overhead. */ -export const DEFAULT_CLEAR_TOOL_USES_TRIGGER = 200_000; - -/** Number of recent tool-use pairs to preserve when clearing. */ -export const DEFAULT_KEEP_RECENT_TOOL_USES = 20; +import { ModelConfig } from '../types'; +/** + * Default model configuration for Claude Sonnet 4. + * + * maxOutputTokens MUST match the value configured in AgentExecutor.ts + * streamText call. If AgentExecutor changes maxOutputTokens, update this constant. + * + * Threshold calculation: + * effectiveWindow = maxContextWindow - maxOutputTokens = 200_000 - 8_192 = 191_808 + * autoCompactThreshold = effectiveWindow - autoCompactBuffer = 191_808 - 13_000 = 178_808 + */ +export const DEFAULT_MODEL_CONFIG: ModelConfig = { + maxContextWindow: 200_000, + maxOutputTokens: 8_192, // Matches AgentExecutor.ts streamText maxOutputTokens + autoCompactBuffer: 13_000, +}; diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/core/SummarizationService.ts b/workspaces/common-libs/copilot-utilities/src/compaction/core/SummarizationService.ts new file mode 100644 index 0000000000..a5987b54f9 --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/core/SummarizationService.ts @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { SummarizationCallback } from '../types'; +import { SUMMARIZATION_PROMPT } from '../prompts/summarizationPrompt'; +import { prepareMessagesForSummarization } from '../utils/messagePreparation'; + +/** + * Handles the LLM summarization call via a provider-agnostic callback. + * + * Responsibilities: + * - Prepares messages (strips system messages, converts tool messages) + * - Builds the system prompt (base + custom instructions + optional token budget) + * - Invokes the summarization callback + * - Parses the ... block from the response + */ +export class SummarizationService { + constructor(private summarizationCallback: SummarizationCallback) {} + + /** + * Summarize a conversation history. + * + * @param messages - Full conversation history + * @param customInstructions - Optional user-provided or mid-stream instructions + * @param abortSignal - Propagated abort signal + * @param targetTokenBudget - On retry, instructs LLM to produce a shorter summary + * @returns Extracted summary text (content between ... tags) + */ + async summarize( + messages: any[], + customInstructions?: string, + abortSignal?: AbortSignal, + targetTokenBudget?: number + ): Promise { + const preparedMessages = prepareMessagesForSummarization(messages); + + // The Anthropic API expects alternate user/assistant messages generally, but + // to reliably break out of a coding/tooling mindset into compaction, we MUST + // append this terminal command explicitly, even if the last message was a user message. + preparedMessages.push({ + role: 'user', + content: [ + '--- END OF CONVERSATION TO SUMMARIZE ---', + '', + 'STOP. Do NOT continue the task above.', + 'You are now in SUMMARIZATION MODE.', + '', + 'Generate a structured summary of the conversation above.', + 'Your ENTIRE response MUST be wrapped in ... tags.', + 'Do NOT output anything outside of those tags.', + 'Do NOT call any tools.', + 'Do NOT continue the coding task.', + 'Begin your response with then end with ....', + ].join('\n'), + }); + + // Build system prompt + let systemPrompt = SUMMARIZATION_PROMPT; + + if (customInstructions) { + systemPrompt += `\n\n## Additional Summarization Instructions from User\n\n${customInstructions}\n`; + } + + // On retry, append a token budget constraint to guide concise output. + // This avoids re-summarizing an already-compacted summary (quality degradation). + if (targetTokenBudget) { + systemPrompt += + `\n\n## Token Budget Constraint\n\nIMPORTANT: Your summary MUST be concise enough to fit within approximately ${targetTokenBudget} tokens. ` + + `Focus only on the most critical information: active tasks, key decisions, recent code changes, and unresolved errors. ` + + `Omit completed tasks, exploratory discussions, and intermediate steps that led to the final approach.\n`; + } + + // Forward abortSignal so the LLM call can be cancelled + const response = await this.summarizationCallback(preparedMessages, systemPrompt, abortSignal); + + // Debug: log the response so we can diagnose parsing failures + console.log(`[SummarizationService] LLM response length: ${response?.length ?? 0} chars`); + if (!response || response.trim().length === 0) { + throw new Error('Summarization LLM returned an empty response'); + } + + // Parse ... block — try multiple patterns + const summaryMatch = response.match(/([\s\S]*?)<\/summary>/); + if (summaryMatch) { + const extractedSummary = summaryMatch[1].trim(); + console.log(`[SummarizationService] Successfully extracted summary (${extractedSummary.length} chars).`); + return extractedSummary; + } + + // Fallback: if the LLM returned substantive text but skipped the tags, + // strip the ... block (if present) and use the rest. + // This prevents hard failure when the LLM produces a valid summary + // but doesn't wrap it in tags. + console.warn('[SummarizationService] No tags found in LLM response, using fallback extraction'); + const withoutAnalysis = response.replace(/[\s\S]*?<\/analysis>/g, '').trim(); + if (withoutAnalysis.length > 50) { + console.log(`[SummarizationService] Successfully extracted fallback summary (${withoutAnalysis.length} chars).`); + return withoutAnalysis; + } + + // Last resort: use the full response if it's substantive + if (response.trim().length > 50) { + return response.trim(); + } + + throw new Error( + 'Summarization LLM did not return a ... block or sufficient fallback text. ' + + `Total response length: ${response.length} chars.` + ); + } +} diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/core/ThresholdCalculator.ts b/workspaces/common-libs/copilot-utilities/src/compaction/core/ThresholdCalculator.ts new file mode 100644 index 0000000000..c3ea1ef42a --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/core/ThresholdCalculator.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { ModelConfig } from '../types'; +import { DEFAULT_MODEL_CONFIG } from '../config/defaults'; + +/** + * Calculates the auto-compaction token threshold from model configuration. + * + * Formula: + * effectiveWindow = maxContextWindow - maxOutputTokens + * threshold = effectiveWindow - autoCompactBuffer + * + * Default (Claude Sonnet 4): + * 200_000 - 8_192 - 13_000 = 178_808 tokens + */ +export class ThresholdCalculator { + private config: ModelConfig; + + constructor(config: ModelConfig = DEFAULT_MODEL_CONFIG) { + this.config = config; + } + + /** + * The token count at which automatic compaction is triggered. + */ + getAutoCompactThreshold(): number { + const effectiveWindow = this.config.maxContextWindow - this.config.maxOutputTokens; + return effectiveWindow - this.config.autoCompactBuffer; + } + + /** + * Returns true if the given token count is at or above the compaction threshold. + */ + isAboveAutoCompactThreshold(tokenCount: number): boolean { + return tokenCount >= this.getAutoCompactThreshold(); + } +} diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/core/TokenEstimator.ts b/workspaces/common-libs/copilot-utilities/src/compaction/core/TokenEstimator.ts new file mode 100644 index 0000000000..1b46217a94 --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/core/TokenEstimator.ts @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import * as crypto from 'crypto'; +import { TokenCountCallback, TokenEstimationContext } from '../types'; + +interface CachedEntry { + tokenCount: number; + contentHash: string; +} + +/** + * Hybrid token estimator that uses actual SDK usage data when available + * and falls back to a callback-based estimation. + * + * Strategy: + * 1. After each streamText step, call updateContext() with actual inputTokens + * 2. estimateTokens() returns the actual count directly (most accurate) + * 3. When no actual data is available, falls back to tokenCountCallback + SHA-256 cache + */ +export class TokenEstimator { + private cache: Map = new Map(); + private lastContext: TokenEstimationContext | null = null; + + constructor(private tokenCountCallback: TokenCountCallback) {} + + /** + * Update estimation context with actual usage data from streamText. + * Call this after each step completes. + */ + updateContext(context: TokenEstimationContext): void { + this.lastContext = context; + } + + /** + * Estimate total token count for message history. + * + * Uses hybrid approach: + * - If actual inputTokens are available, returns them directly (already includes + * system prompt + tool definitions + messages) + * - Otherwise, estimates via callback and adds system/tool overheads + */ + async estimateTokens(messages: any[]): Promise { + // Estimate using callback (with SHA-256 caching for speed) + const messageTokens = await this.estimateMessageTokens(messages); + + const systemTokens = this.lastContext?.systemPromptTokenEstimate ?? 0; + const toolTokens = this.lastContext?.toolDefinitionsTokenEstimate ?? 0; + + return messageTokens + systemTokens + toolTokens; + } + + /** + * Estimate tokens for messages only (no system/tool overhead). + * Uses SHA-256 cache to avoid redundant callback calls. + */ + private async estimateMessageTokens(messages: any[]): Promise { + // Fast path for empty arrays + if (messages.length === 0) { + return 0; + } + + const compositeHash = this.hashContent(messages); + const cached = this.cache.get(compositeHash); + + if (cached !== undefined && cached.contentHash === compositeHash) { + return cached.tokenCount; + } + + const totalTokens = await this.tokenCountCallback(messages); + + // Cache the entire subset estimate with its exact composite hash + this.cache.set(compositeHash, { + tokenCount: totalTokens, + contentHash: compositeHash, + }); + + return totalTokens; + } + + /** + * Clear cache. Call this after compaction so stale entries don't persist. + */ + clearCache(): void { + this.cache.clear(); + } + + /** + * SHA-256 hash for cache keying — avoids collisions from simple string hashing. + */ + private hashContent(message: any): string { + const str = JSON.stringify(message); + return crypto.createHash('sha256').update(str).digest('hex'); + } +} diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/index.ts b/workspaces/common-libs/copilot-utilities/src/compaction/index.ts new file mode 100644 index 0000000000..388bfcbc8a --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/index.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +// Public API for @wso2/copilot-utilities/compaction + +// Main engine +export { CompactionEngine } from './CompactionEngine'; + +// Types +export type { + TokenCountCallback, + SummarizationCallback, + TokenEstimationContext, + ModelConfig, + ProjectStateContext, + CompactionMetadata, + CompactionOptions, + CompactionResult, + CompactionEngineConfig, +} from './types'; + +// Config +export { DEFAULT_MODEL_CONFIG } from './config/defaults'; + +// Utilities (available for integrators that need them directly) +export { createContinuationMessages } from './utils/messageUtils'; +export { prepareMessagesForSummarization } from './utils/messagePreparation'; + +// Core components (available for integrators that need them directly) +export { TokenEstimator } from './core/TokenEstimator'; +export { ThresholdCalculator } from './core/ThresholdCalculator'; +export { SummarizationService } from './core/SummarizationService'; + +// Prompt (available for customization) +export { SUMMARIZATION_PROMPT } from './prompts/summarizationPrompt'; diff --git a/workspaces/common-libs/copilot-utilities/src/context-management/summarizationPrompt.ts b/workspaces/common-libs/copilot-utilities/src/compaction/prompts/summarizationPrompt.ts similarity index 100% rename from workspaces/common-libs/copilot-utilities/src/context-management/summarizationPrompt.ts rename to workspaces/common-libs/copilot-utilities/src/compaction/prompts/summarizationPrompt.ts diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/types/index.ts b/workspaces/common-libs/copilot-utilities/src/compaction/types/index.ts new file mode 100644 index 0000000000..745c561897 --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/types/index.ts @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +/** + * Token counting callback - provided by the integrating copilot. + * Can be sync (char estimation) or async (API call). + */ +export type TokenCountCallback = (messages: any[]) => Promise | number; + +/** + * LLM summarization callback - provided by integrating copilot. + * @param messages - Conversation history to summarize (system messages stripped) + * @param systemPrompt - System prompt with summarization instructions + * @param abortSignal - Optional signal to cancel the LLM call + */ +export type SummarizationCallback = ( + messages: any[], + systemPrompt: string, + abortSignal?: AbortSignal +) => Promise; + +/** + * Context information for accurate token estimation. + */ +export interface TokenEstimationContext { + /** From last streamText usage.inputTokens — most accurate, already includes system + tools */ + lastActualInputTokens?: number; + /** Estimated tokens for the system prompt */ + systemPromptTokenEstimate?: number; + /** Estimated tokens for tool definitions */ + toolDefinitionsTokenEstimate?: number; +} + +/** + * Model configuration controlling context window and output limits. + * CRITICAL: maxOutputTokens MUST match the value in AgentExecutor.ts streamText call. + */ +export interface ModelConfig { + /** Total context window size in tokens (e.g. 200_000 for Claude) */ + maxContextWindow: number; + /** Maximum output tokens — MUST match AgentExecutor.ts streamText maxOutputTokens */ + maxOutputTokens: number; + /** Safety buffer below effective context limit (default: 13_000) */ + autoCompactBuffer: number; +} + +/** + * Project state context to preserve after compaction (C09 fix). + */ +export interface ProjectStateContext { + /** List of files that have been modified */ + modifiedFiles?: string[]; + /** Temporary project path for agent */ + tempProjectPath?: string; + /** Files pending review */ + pendingReviewFiles?: string[]; + /** Current working directory */ + workingDirectory?: string; +} + +/** + * Compaction metadata for audit trail (C15 fix). + */ +export interface CompactionMetadata { + /** Unix timestamp when compaction occurred */ + compactedAt: number; + /** Message count before compaction */ + originalMessageCount: number; + /** Token estimate before compaction */ + originalTokenEstimate: number; + /** Token estimate after compaction */ + compactedTokenEstimate: number; + /** Number of retry attempts used */ + retries: number; + /** Whether triggered automatically or manually */ + mode: 'auto' | 'manual'; + /** Custom instructions provided by user (if any) */ + userInstructions?: string; + /** Path to pre-compaction backup file */ + backupPath?: string; + /** IDs of generations that were compacted */ + compactedGenerationIds?: string[]; +} + +/** + * Options for a compaction operation. + */ +export interface CompactionOptions { + /** Whether this is an auto-triggered or manually requested compaction */ + mode: 'auto' | 'manual'; + /** Optional user-provided instructions to guide summarization */ + customInstructions?: string; + /** Maximum retry attempts if compacted output is still too large (default: 3) */ + maxRetries?: number; + /** Project state to include in continuation messages */ + projectState?: ProjectStateContext; + /** AbortSignal to cancel the summarization LLM call */ + abortSignal?: AbortSignal; +} + +/** + * Result of a compaction operation. + */ +export interface CompactionResult { + /** Whether compaction succeeded */ + success: boolean; + /** Token count before compaction */ + originalTokens: number; + /** Token count after compaction */ + compactedTokens: number; + /** Percentage of tokens reduced */ + reductionPercentage: number; + /** Replacement message array to use in place of the original history */ + compactedMessages: any[]; + /** The extracted summary text */ + summary: string; + /** Number of retry attempts used */ + retriesUsed: number; + /** Compaction audit metadata */ + metadata?: CompactionMetadata; +} + +/** + * Configuration for CompactionEngine. + */ +export interface CompactionEngineConfig { + /** Model configuration (context window, output limits, buffer) */ + modelConfig: ModelConfig; + /** Token counting callback (provider-agnostic) */ + tokenCountCallback: TokenCountCallback; + /** Optional — set via setSummarizationCallback() to defer model binding */ + summarizationCallback?: SummarizationCallback; +} diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/utils/messagePreparation.ts b/workspaces/common-libs/copilot-utilities/src/compaction/utils/messagePreparation.ts new file mode 100644 index 0000000000..929416728b --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/utils/messagePreparation.ts @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +/** + * Prepares messages for summarization by: + * - Stripping system messages (C06: prevents system prompt conflicts) + * - Converting tool messages to readable text (C05) + * - Replacing image/document blocks with placeholders + * - Stripping thinking blocks + * - Converting tool-call/tool-result content blocks to text + * + * @param messages - Raw conversation messages from AI SDK + * @returns Cleaned messages safe for the summarization LLM + */ +export function prepareMessagesForSummarization(messages: any[]): any[] { + return messages + .filter((msg: any) => msg.role !== 'system') // C06: Strip system prompts + .map((msg: any) => { + // Convert tool messages (role: 'tool') to user messages with text + if (msg.role === 'tool') { + return convertToolMessageToText(msg); + } + + // Handle string content directly + if (typeof msg.content === 'string') { + return msg; + } + + // Not an array — pass through unchanged + if (!Array.isArray(msg.content)) { + return msg; + } + + // Process content blocks array + const filteredContent = msg.content + .filter((block: any) => block != null && block.type !== 'thinking') // Strip thinking blocks and nulls + .map((block: any) => { + // Inline tool-call blocks in assistant messages + if (block.type === 'tool-call') { + const argsStr = block.args != null ? JSON.stringify(block.args) : ''; + return { + type: 'text', + text: `[Tool Call: ${block.toolName || 'unknown'} with args ${argsStr.substring(0, 100)}...]`, + }; + } + // Inline tool-result blocks with "middle-out" truncation + if (block.type === 'tool-result') { + const resultStr = block.result != null ? JSON.stringify(block.result) : ''; + let truncated = resultStr; + if (resultStr.length > 500) { + truncated = resultStr.substring(0, 200) + + '\n\n...[omitted]...\n\n' + + resultStr.substring(resultStr.length - 300); + } + return { + type: 'text', + text: `[Tool Result: ${truncated}]`, + }; + } + // Replace images with placeholder + if (block.type === 'image') { + return { type: 'text', text: '[image]' }; + } + // Replace documents with placeholder + if (block.type === 'document') { + return { type: 'text', text: '[document]' }; + } + return block; + }); + + if (filteredContent.length === 0) { + return null; + } + + return { ...msg, content: filteredContent }; + }) + .filter((msg: any) => msg !== null); +} + +/** + * Converts a tool-role message into a user-role message with a text description. + * This is needed because summarization LLMs expect only user/assistant roles. + */ +function convertToolMessageToText(toolMsg: any): any { + const content = Array.isArray(toolMsg.content) ? toolMsg.content : [toolMsg.content]; + const textDescriptions = content + .filter((item: any) => item != null) + .map((item: any) => { + if (item.type === 'tool-result') { + const resultStr = item.result != null ? JSON.stringify(item.result) : ''; + let truncated = resultStr; + if (resultStr.length > 1000) { + truncated = resultStr.substring(0, 400) + + '\n\n...[omitted]...\n\n' + + resultStr.substring(resultStr.length - 600); + } + return `[Tool: ${item.toolName || 'unknown'} returned: ${truncated}]`; + } + const itemStr = JSON.stringify(item) || ''; + return itemStr.substring(0, 200); + }) + .join('\n'); + + return { + role: 'user', + content: textDescriptions, + }; +} diff --git a/workspaces/common-libs/copilot-utilities/src/compaction/utils/messageUtils.ts b/workspaces/common-libs/copilot-utilities/src/compaction/utils/messageUtils.ts new file mode 100644 index 0000000000..93df35acce --- /dev/null +++ b/workspaces/common-libs/copilot-utilities/src/compaction/utils/messageUtils.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * 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. + */ + +import { ProjectStateContext } from '../types'; + +/** + * Creates a valid user-assistant message pair that carries the compaction summary + * as the new start of the conversation history (L04 fix). + * + * The pair is structured so that: + * - The user message signals context restoration (prevents bare assistant message) + * - The assistant message contains the full summary + optional project state + * + * @param summary - The extracted summary text from the LLM + * @param projectState - Optional project state to append (C09 fix) + * @returns A two-element array: [userMessage, assistantMessage] + */ +export function createContinuationMessages( + summary: string, + projectState?: ProjectStateContext +): any[] { + // Build project state context section + let projectStateSection = ''; + if (projectState) { + projectStateSection = '\n\n## Current Project State\n\n'; + + if (projectState.tempProjectPath) { + projectStateSection += `**Working Directory:** \`${projectState.tempProjectPath}\`\n\n`; + } + + if (projectState.modifiedFiles && projectState.modifiedFiles.length > 0) { + projectStateSection += `**Modified Files:**\n${projectState.modifiedFiles.map(f => `- \`${f}\``).join('\n')}\n\n`; + } + + if (projectState.pendingReviewFiles && projectState.pendingReviewFiles.length > 0) { + projectStateSection += `**Pending Review:**\n${projectState.pendingReviewFiles.map(f => `- \`${f}\``).join('\n')}\n\n`; + } + } + + return [ + { + role: 'user', + content: '[Context restored from previous conversation - conversation history has been compacted to manage token limits. Continue from the summary below.]', + }, + { + role: 'assistant', + content: `This session is being continued from a previous conversation. Below is a summary of what was discussed and accomplished: + +--- + +${summary} + +--- +${projectStateSection} +I'm ready to continue our work. What would you like to do next?`, + }, + ]; +} diff --git a/workspaces/common-libs/copilot-utilities/src/context-management/index.ts b/workspaces/common-libs/copilot-utilities/src/context-management/index.ts deleted file mode 100644 index 8c4e7ecf09..0000000000 --- a/workspaces/common-libs/copilot-utilities/src/context-management/index.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -export { SUMMARIZATION_PROMPT } from './summarizationPrompt'; -export type { ContextManagementConfig, AppliedCompactionResult } from './types'; -export { - DEFAULT_COMPACT_TRIGGER, - DEFAULT_CLEAR_TOOL_USES_TRIGGER, - DEFAULT_KEEP_RECENT_TOOL_USES, -} from './defaults'; - -/** Sentinel prefix emitted by Anthropic at the start of a compaction block. */ -export const COMPACTION_BLOCK_PREFIX = ''; - -import { ContextManagementConfig, AppliedCompactionResult } from './types'; -import { - DEFAULT_COMPACT_TRIGGER, - DEFAULT_CLEAR_TOOL_USES_TRIGGER, - DEFAULT_KEEP_RECENT_TOOL_USES, -} from './defaults'; -import { SUMMARIZATION_PROMPT } from './summarizationPrompt'; - -// Internal types matching @ai-sdk/anthropic contextManagement schema -interface ClearToolUsesEdit { - type: 'clear_tool_uses_20250919'; - trigger?: { type: 'input_tokens'; value: number }; - keep?: { type: 'tool_uses'; value: number }; -} - -interface CompactEdit { - type: 'compact_20260112'; - trigger?: { type: 'input_tokens'; value: number }; - instructions?: string; -} - -type ContextManagementEdit = ClearToolUsesEdit | CompactEdit; - -interface AnthropicContextManagementOptions { - anthropic: { - contextManagement: { - edits: ContextManagementEdit[]; - }; - }; -} - -/** - * Builds the providerOptions.anthropic.contextManagement config for streamText. - * - * Returns null when: - * - estimatedFloorTokens >= compactTriggerTokens (codebase too large; compaction - * would fire on every turn against an empty history, producing no benefit) - * - * The caller is responsible for checking whether the current login provider - * supports contextManagement (ANTHROPIC_KEY only for now) and passing null - * to providerOptions for unsupported providers. - */ -export function buildContextManagementOptions( - config: ContextManagementConfig = {} -): AnthropicContextManagementOptions | null { - const compactTrigger = config.compactTriggerTokens ?? DEFAULT_COMPACT_TRIGGER; - const clearTrigger = config.clearToolUsesTriggerTokens ?? DEFAULT_CLEAR_TOOL_USES_TRIGGER; - const keepToolUses = config.keepRecentToolUses ?? DEFAULT_KEEP_RECENT_TOOL_USES; - const instructions = config.compactionInstructions ?? SUMMARIZATION_PROMPT; - - // Disable compaction if static overhead alone exceeds the trigger - if (config.estimatedFloorTokens !== undefined && config.estimatedFloorTokens >= compactTrigger) { - return null; - } - - const edits: ContextManagementEdit[] = [ - { - type: 'clear_tool_uses_20250919', - trigger: { type: 'input_tokens', value: clearTrigger }, - keep: { type: 'tool_uses', value: keepToolUses }, - }, - { - type: 'compact_20260112', - trigger: { type: 'input_tokens', value: compactTrigger }, - instructions, - }, - ]; - - return { anthropic: { contextManagement: { edits } } }; -} - -/** - * Parses step.providerMetadata to detect whether the server applied any - * context management edits during the step. - * - * Used as a fallback to detect clear_tool_uses events (which have no mid-stream - * signal) and as a secondary check for compact_20260112. - */ -export function detectAppliedCompaction(providerMetadata: unknown): AppliedCompactionResult | null { - const meta = providerMetadata as any; - const appliedEdits: any[] = meta?.anthropic?.contextManagement?.appliedEdits; - if (!Array.isArray(appliedEdits) || appliedEdits.length === 0) { - return null; - } - - let compacted = false; - let clearedToolUses: number | undefined; - let clearedInputTokens = 0; - - for (const edit of appliedEdits) { - if (edit.type === 'compact_20260112') { - compacted = true; - } else if (edit.type === 'clear_tool_uses_20250919') { - clearedToolUses = (clearedToolUses ?? 0) + (edit.clearedToolUses ?? 0); - clearedInputTokens += edit.clearedInputTokens ?? 0; - } - } - - return { - compacted, - clearedToolUses, - clearedInputTokens: clearedInputTokens > 0 ? clearedInputTokens : undefined, - }; -} - -/** - * Estimates the token floor (system prompt + user message overhead) using a - * characters-per-4-tokens heuristic. Used to decide whether compaction is viable. - */ -export function estimateFloorTokens(systemPrompt: string, userMessageText: string): number { - return Math.ceil((systemPrompt.length + userMessageText.length) / 4); -} - -/** - * Extracts the ... block from a raw compaction response. - * Returns null if no summary tag is found. - */ -export function extractCompactionSummary(rawContent: string): string | null { - const match = rawContent.match(/([\s\S]*?)<\/summary>/); - return match ? match[1].trim() : null; -} - -/** - * Builds providerOptions for Amazon Bedrock's context management API. - * - * Bedrock does not use providerOptions.anthropic — it reads providerOptions.bedrock - * and passes context_management via additionalModelRequestFields. The beta header - * is sent via the anthropicBeta array. - * - * Returns null under the same conditions as buildContextManagementOptions (floor >= trigger). - */ -export function buildBedrockContextManagementOptions( - config: ContextManagementConfig = {} -): { bedrock: { anthropicBeta: string[]; additionalModelRequestFields: Record } } | null { - const compactTrigger = config.compactTriggerTokens ?? DEFAULT_COMPACT_TRIGGER; - const clearTrigger = config.clearToolUsesTriggerTokens ?? DEFAULT_CLEAR_TOOL_USES_TRIGGER; - const keepToolUses = config.keepRecentToolUses ?? DEFAULT_KEEP_RECENT_TOOL_USES; - const instructions = config.compactionInstructions ?? SUMMARIZATION_PROMPT; - - if (config.estimatedFloorTokens !== undefined && config.estimatedFloorTokens >= compactTrigger) { - return null; - } - - return { - bedrock: { - anthropicBeta: ['compact-2026-01-12'], - additionalModelRequestFields: { - context_management: { - edits: [ - { - type: 'clear_tool_uses_20250919', - trigger: { type: 'input_tokens', value: clearTrigger }, - keep: { type: 'tool_uses', value: keepToolUses }, - }, - { - type: 'compact_20260112', - trigger: { type: 'input_tokens', value: compactTrigger }, - instructions, - }, - ], - }, - }, - }, - }; -} - -/** - * Strips ... blocks from compaction text in model messages. - * Called in prepareStep to avoid re-sending verbose reasoning tokens on every - * subsequent turn after compaction. - */ -export function stripAnalysisFromCompactionBlocks(messages: any[]): void { - for (const msg of messages) { - if (msg.role !== 'assistant') continue; - const content = msg.content; - if (typeof content === 'string' && content.includes('')) { - msg.content = content.replace(/[\s\S]*?<\/analysis>\s*/g, ''); - } else if (Array.isArray(content)) { - for (const part of content) { - if (part.type === 'text' && typeof part.text === 'string' && part.text.includes('')) { - part.text = part.text.replace(/[\s\S]*?<\/analysis>\s*/g, ''); - } - } - } - } -} diff --git a/workspaces/common-libs/copilot-utilities/src/context-management/types.ts b/workspaces/common-libs/copilot-utilities/src/context-management/types.ts deleted file mode 100644 index 85b3c0af13..0000000000 --- a/workspaces/common-libs/copilot-utilities/src/context-management/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * 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. - */ - -export interface ContextManagementConfig { - /** Token threshold to trigger compact_20260112. Default: 150_000 (Anthropic API default). */ - compactTriggerTokens?: number; - /** Token threshold to trigger clear_tool_uses_20250919. Default: 120_000. */ - clearToolUsesTriggerTokens?: number; - /** Number of recent tool-use pairs to preserve when clearing. Default: 6. */ - keepRecentToolUses?: number; - /** Custom instructions passed as `instructions` to the compact_20260112 edit. */ - compactionInstructions?: string; - /** - * Rough token estimate for the fixed overhead (system prompt + user message codebase). - * If this value exceeds compactTriggerTokens, buildContextManagementOptions returns null - * because compaction would fire on every turn with no benefit. - */ - estimatedFloorTokens?: number; -} - -export interface AppliedCompactionResult { - /** Whether compact_20260112 fired during this step. */ - compacted: boolean; - /** Number of tool uses cleared by clear_tool_uses_20250919. */ - clearedToolUses?: number; - /** Total input tokens freed across all applied edits. */ - clearedInputTokens?: number; -} diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/NewChat.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/NewChat.svg deleted file mode 100644 index bb444140c9..0000000000 --- a/workspaces/common-libs/font-wso2-vscode/src/icons/NewChat.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-clean.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-clean.svg deleted file mode 100644 index 9b55c5f70e..0000000000 --- a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-clean.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-mssql.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-mssql.svg index dac300c0d2..44ef6ad971 100644 --- a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-mssql.svg +++ b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-mssql.svg @@ -1,19 +1 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/workspaces/common-libs/font-wso2-vscode/src/plugin-icons/configurePlugins.js b/workspaces/common-libs/font-wso2-vscode/src/plugin-icons/configurePlugins.js index 44403ffd7c..7e087ebfcf 100644 --- a/workspaces/common-libs/font-wso2-vscode/src/plugin-icons/configurePlugins.js +++ b/workspaces/common-libs/font-wso2-vscode/src/plugin-icons/configurePlugins.js @@ -15,7 +15,6 @@ * specific language governing permissions and limitations * under the License. */ -const { log } = require("console"); const fs = require("fs"); const path = require("path"); @@ -82,6 +81,18 @@ if (fs.existsSync(configPath)) { const wso2FontPath = "./resources/font-wso2-vscode/dist/wso2-vscode.woff"; const codiconFontPath = "./resources/codicons/codicon.ttf"; +// Define a function to extract content value from Font CSS +function extractWso2FontContentValue(iconName) { + const classRegex = new RegExp(`\\.fw-${iconName}:before\\s*{\\s*content:\\s*"\\\\([a-fA-F0-9]+)";\\s*}`, 'g'); + const match = classRegex.exec(wso2FontCss); + + if (match && match[1]) { + return `\\${match[1]}`; + } else { + return null; + } +} + // Define a function to extract content value from Codicon CSS function extractCodiconContentValue(iconName) { const classRegex = new RegExp(`\\.codicon-${iconName}:before\\s*{\\s*content:\\s*"\\\\([a-zA-Z0-9]+)"\\s*}`, 'g'); @@ -94,22 +105,27 @@ function extractCodiconContentValue(iconName) { } // Define a function to generate icons contribution -function generateWso2FontContribution(selectedIconJson, extensionName) { +function generateWso2FontContribution(selectedIconJson) { let iconsContribution = {}; for (const selectedIconName of selectedIconJson) { - const fontName = selectedIconName.replace('.svg', ''); - const codepoint = wso2FontJson[fontName]; - - if (codepoint !== undefined) { - iconsContribution[`${extensionName ? `${extensionName}-` : 'distro-'}${fontName}`] = { - description: fontName, - default: { - fontPath: wso2FontPath, - fontCharacter: `\\${codepoint.toString(16)}` + for (const fontName in wso2FontJson) { + if (selectedIconName === `${fontName}.svg`) { + const contentValue = extractWso2FontContentValue(fontName); + + if (contentValue) { + const iconDescription = fontName; + const iconCharacter = contentValue; + + iconsContribution[`distro-${fontName}`] = { + description: iconDescription, + default: { + fontPath: wso2FontPath, + fontCharacter: iconCharacter + } + }; + break; } - }; - } else { - console.warn(`Icon not found in wso2-vscode font: ${fontName}`); + } } } return iconsContribution; @@ -136,17 +152,17 @@ function generateCodiconContribution(selectedIconJson) { return iconsContribution; } -function generateFontIconsContribution(extIcons, extensionName) { +function generateFontIconsContribution(extIcons) { + if (!extIcons) { + return {}; + } + const wso2FontIcons = extIcons.wso2Font; const codiconIcons = extIcons.codiconFont; - let wso2FontContributions; - let codiconContributions; - if (wso2FontIcons) { - wso2FontContributions = generateWso2FontContribution(wso2FontIcons, extensionName); - } - if (codiconIcons) { - codiconContributions = generateCodiconContribution(codiconIcons); - } + + const wso2FontContributions = wso2FontIcons ? generateWso2FontContribution(wso2FontIcons) : {}; + const codiconContributions = codiconIcons ? generateCodiconContribution(codiconIcons) : {}; + const mergedContributions = { ...wso2FontContributions, ...codiconContributions, @@ -181,9 +197,9 @@ const ballerinaIcons = config.ballerinaExtIcons || []; const choreoIcons = config.choreoExtIcons || []; const mIIcons = config.mIExtIcons || []; -const ballerinaIconsContribution = generateFontIconsContribution(ballerinaIcons, "ballerina"); -const choreoIconsContribution = generateFontIconsContribution(choreoIcons, "choreo"); -const mIIconsContribution = generateFontIconsContribution(mIIcons, "mi"); +const ballerinaIconsContribution = generateFontIconsContribution(ballerinaIcons); +const choreoIconsContribution = generateFontIconsContribution(choreoIcons); +const mIIconsContribution = generateFontIconsContribution(mIIcons); // Merge the generated icons contribution into the existing package.json contributes const choreoExtPackageJsonPath = path.join(__dirname, "..", "..", "..", "..", "choreo", "choreo-extension", "package.json"); diff --git a/workspaces/common-libs/ui-toolkit/src/components/LlmIcons/getAIModuleIcon.tsx b/workspaces/common-libs/ui-toolkit/src/components/LlmIcons/getAIModuleIcon.tsx index ef61011721..6faded1142 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/LlmIcons/getAIModuleIcon.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/LlmIcons/getAIModuleIcon.tsx @@ -57,7 +57,6 @@ const AI_MODULE_ICON_MAP: Record = { "ai.pinecone": { iconName: "bi-pinecone" }, "ai.pgvector": { iconName: "bi-postgresql" }, "ai.openrouter": { iconName: "bi-openrouter" }, - "ai.memory.mssql": { iconName: "bi-mssql" } }; export function getAIModuleIcon(moduleType: string, size: number = 24): React.ReactElement | null { diff --git a/workspaces/common-libs/ui-toolkit/src/components/RadioButtonGroup/RadioButtonGroup.tsx b/workspaces/common-libs/ui-toolkit/src/components/RadioButtonGroup/RadioButtonGroup.tsx index 4ef35f433d..c43cb15d98 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/RadioButtonGroup/RadioButtonGroup.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/RadioButtonGroup/RadioButtonGroup.tsx @@ -47,10 +47,10 @@ export interface RadioButtonGroupProps extends ComponentProps<"input"> { } export const RadioButtonGroup = React.forwardRef((props, ref) => { - const { id, className, label, options, orientation, sx, value, ...rest } = props; + const { id, className, label, options, orientation, sx, ...rest } = props; return ( - +
@@ -64,7 +64,6 @@ export const RadioButtonGroup = React.forwardRef {option.content} diff --git a/workspaces/common-libs/ui-toolkit/src/components/TextField/TextField.tsx b/workspaces/common-libs/ui-toolkit/src/components/TextField/TextField.tsx index a178fd46f0..17cd41ad7f 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/TextField/TextField.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/TextField/TextField.tsx @@ -41,7 +41,6 @@ export interface TextFieldProps extends ComponentProps<"input"> { forceAutoFocus?: boolean; icon?: IconProps; size?: number; - readOnly?: boolean; readonly?: boolean; required?: boolean; errorMsg?: string; @@ -77,11 +76,10 @@ const Description = styled.div` `; export const TextField = React.forwardRef((props, ref) => { - const { label, type = "text", size = 20, disabled, icon, readOnly, readonly, id, autoFocus, required, + const { label, type = "text", size = 20, disabled, icon, readonly, id, autoFocus, required, placeholder, description, validationMessage, errorMsg, sx, textFieldSx, descriptionSx, inputProps, onTextChange, labelAdornment, onKeyDown, forceAutoFocus, ...rest } = props; - const resolvedReadOnly = readOnly ?? readonly; const [, setIsFocused] = React.useState(false); const textFieldRef = useRef(null); @@ -134,7 +132,7 @@ export const TextField = React.forwardRef((pro type={type} size={size} disabled={disabled || undefined} - readonly={resolvedReadOnly || undefined} + readonly={readonly} validationMessage={validationMessage} placeholder={placeholder} id={id} diff --git a/workspaces/mi/mi-extension/package.json b/workspaces/mi/mi-extension/package.json index 6c669bd986..428d4f66f6 100644 --- a/workspaces/mi/mi-extension/package.json +++ b/workspaces/mi/mi-extension/package.json @@ -1033,14 +1033,14 @@ "description": "design-view", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f1c5" + "fontCharacter": "\\f1bb" } }, "mi-build-package": { "description": "build-package", "default": { "fontPath": "./resources/font-wso2-vscode/dist/wso2-vscode.woff", - "fontCharacter": "\\f19d" + "fontCharacter": "\\f193" } } } @@ -1121,12 +1121,14 @@ "@wso2/playwright-vscode-tester": "workspace:*", "adm-zip": "0.5.16", "axios": "1.15.0", + "canonicalize": "2.1.0", "copyfiles": "2.4.1", "cors-anywhere": "0.4.4", "dotenv": "16.5.0", "fast-xml-parser": "5.2.3", "find-process": "1.4.10", "fs-extra": "11.3.0", + "glob": "11.1.0", "handlebars": "4.7.9", "json-schema": "0.4.0", "json-schema-to-ts": "3.1.1", diff --git a/workspaces/mi/mi-extension/src/test/e2e-playwright-tests/Utils.ts b/workspaces/mi/mi-extension/src/test/e2e-playwright-tests/Utils.ts index d934d920cc..5718d2b77d 100644 --- a/workspaces/mi/mi-extension/src/test/e2e-playwright-tests/Utils.ts +++ b/workspaces/mi/mi-extension/src/test/e2e-playwright-tests/Utils.ts @@ -29,7 +29,7 @@ import { Page } from "@playwright/test"; export const dataFolder = path.join(__dirname, 'data'); const extensionsFolder = path.join(__dirname, '..', '..', '..', 'vsix'); -const vscodeVersion = 'latest'; +const vscodeVersion = '1.115.0'; export const resourcesFolder = path.join(__dirname, '..', 'test-resources'); export const newProjectPath = path.join(dataFolder, 'new-project', 'testProjectFolder'); export const screenShotsFolder = path.join(__dirname, '..', 'test-resources', 'screenshots'); diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts index 026421e7f2..4620f0be08 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/cli-rpc.types.ts @@ -93,14 +93,8 @@ export interface CreateProjectReq { orgId: string; orgHandler: string; projectName: string; - projectHandler?: string; region: string; } -export interface UpdateProjectReq { - orgId: string; - projectId: string; - name: string; -} export interface DeleteCompReq { orgId: string; orgHandler: string; @@ -686,7 +680,6 @@ export interface IChoreoRPCClient { getProjects(orgID: string): Promise; getComponentList(params: GetComponentsReq): Promise; createProject(params: CreateProjectReq): Promise; - updateProject(params: UpdateProjectReq): Promise; createComponent(params: CreateComponentReq): Promise; getBuildPacks(params: BuildPackReq): Promise; getRepoBranches(params: GetBranchesReq): Promise; @@ -742,9 +735,6 @@ export class ChoreoRpcWebview implements IChoreoRPCClient { createProject(params: CreateProjectReq): Promise { return this._messenger.sendRequest(ChoreoRpcCreateProjectRequest, HOST_EXTENSION, params); } - updateProject(params: UpdateProjectReq): Promise { - return this._messenger.sendRequest(ChoreoRpcUpdateProjectRequest, HOST_EXTENSION, params); - } createComponent(params: CreateComponentReq): Promise { return this._messenger.sendRequest(ChoreoRpcCreateComponentRequest, HOST_EXTENSION, params); } @@ -868,7 +858,6 @@ export const ChoreoRpcGetProjectsRequest: RequestType = { met export const ChoreoRpcGetComponentsRequest: RequestType = { method: "rpc/component/getList" }; export const ChoreoRpcGetComponentItemRequest: RequestType = { method: "rpc/component/getItem" }; export const ChoreoRpcCreateProjectRequest: RequestType = { method: "rpc/project/create" }; -export const ChoreoRpcUpdateProjectRequest: RequestType = { method: "rpc/project/update" }; export const ChoreoRpcCreateComponentRequest: RequestType = { method: "rpc/component/create" }; export const ChoreoRpcGetBuildPacksRequest: RequestType = { method: "rpc/component/getBuildPacks" }; export const ChoreoRpcGetBranchesRequest: RequestType = { method: "rpc/repo/getBranches" }; diff --git a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts index 4a399bbb3d..916f0d1d34 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/types/common.types.ts @@ -17,7 +17,7 @@ */ import type { DeploymentStatus } from "../enums"; -import { GetMarketplaceListReq, MarketplaceListResp, GetMarketplaceIdlReq, MarketplaceIdlResp, CreateComponentConnectionReq, GetConnectionsReq, DeleteConnectionReq, GetMarketplaceItemReq, GetConnectionItemReq, GetProjectEnvsReq, CreateThirdPartyConnectionReq, RegisterMarketplaceConnectionReq, GetComponentsReq, MarketplaceDatabaseListResp, DatabaseServer, GetDatabaseServerReq, DatabaseAdminCredential, DatabaseCredential, CreateDatabaseConnectionReq, GetDatabaseItemReq, ResolveConnectionSecretsReq, ResolveConnectionSecretsResp, UpdateProjectReq } from "./cli-rpc.types"; +import { GetMarketplaceListReq, MarketplaceListResp, GetMarketplaceIdlReq, MarketplaceIdlResp, CreateComponentConnectionReq, GetConnectionsReq, DeleteConnectionReq, GetMarketplaceItemReq, GetConnectionItemReq, GetProjectEnvsReq, CreateThirdPartyConnectionReq, RegisterMarketplaceConnectionReq, GetComponentsReq, MarketplaceDatabaseListResp, DatabaseServer, GetDatabaseServerReq, DatabaseAdminCredential, DatabaseCredential, CreateDatabaseConnectionReq, GetDatabaseItemReq, ResolveConnectionSecretsReq, ResolveConnectionSecretsResp } from "./cli-rpc.types"; import { CreateLocalConnectionsConfigReq, DeleteLocalConnectionsConfigReq } from "./messenger-rpc.types"; import type { AuthState, ContextItemEnriched, ContextStoreState, WebviewState } from "./store.types"; @@ -56,8 +56,6 @@ export interface IWso2PlatformExtensionAPI { stopProxyServer: (params: StopProxyServerReq) => Promise; getComponentList: (params: GetComponentsReq) => Promise; resolveConnectionSecrets: (params: ResolveConnectionSecretsReq) => Promise; - updateProject: (params: UpdateProjectReq) => Promise; - getProjects: (orgId: string) => Promise; // Auth Subscription subscribeAuthState(callback: (state: AuthState)=>void): () => void; diff --git a/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js b/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js index 503192675f..bc3ff5e535 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js +++ b/workspaces/wso2-platform/wso2-platform-extension/scripts/download-choreo-cli.js @@ -337,7 +337,7 @@ function extractBinary(archivePath, platform, destDir) { try { if (platform.ext === '.tar.gz') { - execSync(`tar -xzf "${archivePath}" -C "${tmpExtractDir}"`, { stdio: 'inherit' }); + execSync(`tar -xzf '${archivePath}' -C '${tmpExtractDir}'`, { stdio: 'inherit' }); } else if (platform.ext === '.zip') { if (os.platform() === 'win32') { execSync(`powershell.exe -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpExtractDir}' -Force"`, { stdio: 'inherit' }); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index 0099aa91ad..2e515a3345 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -16,7 +16,7 @@ * under the License. */ -import type { ComponentKind, GetMarketplaceListReq, IWso2PlatformExtensionAPI, openClonedDirReq, GetMarketplaceIdlReq, CreateComponentConnectionReq, CreateLocalConnectionsConfigReq, GetConnectionsReq, DeleteConnectionReq, DeleteLocalConnectionsConfigReq, GetMarketplaceItemReq, GetConnectionItemReq, StartProxyServerReq, StopProxyServerReq, AuthState, ContextStoreComponentState, ContextItemEnriched, GetProjectEnvsReq, CreateThirdPartyConnectionReq, RegisterMarketplaceConnectionReq, GetComponentsReq, GetDatabaseServerReq, CreateDatabaseConnectionReq, GetDatabaseItemReq, ResolveConnectionSecretsReq, UpdateProjectReq } from "@wso2/wso2-platform-core"; +import type { ComponentKind, GetMarketplaceListReq, IWso2PlatformExtensionAPI, openClonedDirReq, GetMarketplaceIdlReq, CreateComponentConnectionReq, CreateLocalConnectionsConfigReq, GetConnectionsReq, DeleteConnectionReq, DeleteLocalConnectionsConfigReq, GetMarketplaceItemReq, GetConnectionItemReq, StartProxyServerReq, StopProxyServerReq, AuthState, ContextStoreComponentState, ContextItemEnriched, GetProjectEnvsReq, CreateThirdPartyConnectionReq, RegisterMarketplaceConnectionReq, GetComponentsReq, GetDatabaseServerReq, CreateDatabaseConnectionReq, GetDatabaseItemReq, ResolveConnectionSecretsReq } from "@wso2/wso2-platform-core"; import { ext } from "./extensionVariables"; import { hasDirtyRepo } from "./git/util"; import { contextStore } from "./stores/context-store"; @@ -63,8 +63,6 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { public stopProxyServer = async(params: StopProxyServerReq) => ext.clients.rpcClient.stopProxyServer(params); public getComponentList = async(params: GetComponentsReq) => ext.clients.rpcClient.getComponentList(params); public resolveConnectionSecrets = async(params: ResolveConnectionSecretsReq) => ext.clients.rpcClient.resolveConnectionSecrets(params); - public getProjects = async(orgId: string) => ext.clients.rpcClient.getProjects(orgId); - public updateProject = async(params: UpdateProjectReq) => ext.clients.rpcClient.updateProject(params); // Auth state subscriptions public subscribeAuthState = (callback: (state: AuthState)=>void) => ext.authProvider?.subscribe((state)=>callback(state.state)) ?? (() => {}); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts index 0f4d511a8a..10c6a067ac 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/choreo-rpc/client.ts @@ -103,7 +103,6 @@ import type { ToggleAutoBuildReq, ToggleAutoBuildResp, UpdateCodeServerReq, - UpdateProjectReq, UserInfo, } from "@wso2/wso2-platform-core"; import { workspace } from "vscode"; @@ -213,14 +212,6 @@ export class ChoreoRPCClient implements IChoreoRPCClient { return resp.project; } - async updateProject(params: UpdateProjectReq): Promise { - if (!this.client) { - throw new Error("RPC client is not initialized"); - } - const resp = await this.client.sendRequest<{ project: Project }>("project/update", params); - return resp.project; - } - async createComponent(params: CreateComponentReq): Promise { if (!this.client) { throw new Error("RPC client is not initialized");