Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import { AiModuleOrgRequest, AiModuleOrgResponse, AIGentToolsRequest, AIGentToolsResponse, AIModelsRequest, AINodesRequest, AINodesResponse, AIToolsRequest, AIToolsResponse, AIModelsResponse, MemoryManagersResponse, MemoryManagersRequest, McpToolsRequest, McpToolsResponse, AIToolResponse, AIToolRequest } from "../../interfaces/extended-lang-client";
import { AIAgentRequest, AIAgentResponse, AIAgentToolsUpdateRequest, McpToolUpdateRequest } from "./interfaces";
import { AIAgentRequest, AIAgentResponse, AIAgentToolsUpdateRequest, McpToolUpdateRequest, ConfigureDefaultModelProviderRequest } from "./interfaces";

export interface AIAgentAPI {
getAiModuleOrg: (params: AiModuleOrgRequest) => Promise<AiModuleOrgResponse>;
Expand All @@ -29,7 +29,7 @@ export interface AIAgentAPI {
getTool: (params: AIToolRequest) => Promise<AIToolResponse>;
getMcpTools: (params: McpToolsRequest) => Promise<McpToolsResponse>;
genTool: (params: AIGentToolsRequest) => Promise<AIGentToolsResponse>;
configureDefaultModelProvider: () => Promise<void>;
configureDefaultModelProvider: (params: ConfigureDefaultModelProviderRequest) => Promise<void>;
createAIAgent: (params: AIAgentRequest) => Promise<AIAgentResponse>;
updateAIAgentTools: (params: AIAgentToolsUpdateRequest) => Promise<AIAgentResponse>;
updateMCPToolKit: (params: McpToolUpdateRequest) => Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export interface McpToolUpdateRequest {
updatedNode?: FlowNode;
}

export interface ConfigureDefaultModelProviderRequest {
projectPath?: string;
}

export interface ToolParameters {
metadata: Metadata;
valueType: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* THIS FILE INCLUDES AUTO GENERATED CODE
*/
import { AiModuleOrgRequest, AiModuleOrgResponse, AIGentToolsRequest, AIGentToolsResponse, AIModelsRequest, AINodesRequest, AINodesResponse, AIToolsRequest, AIToolsResponse, AIModelsResponse, MemoryManagersResponse, MemoryManagersRequest, McpToolsRequest, McpToolsResponse, AIToolResponse, AIToolRequest } from "../../interfaces/extended-lang-client";
import { AIAgentRequest, AIAgentResponse, AIAgentToolsUpdateRequest, McpToolUpdateRequest } from "./interfaces";
import { AIAgentRequest, AIAgentResponse, AIAgentToolsUpdateRequest, McpToolUpdateRequest, ConfigureDefaultModelProviderRequest } from "./interfaces";
import { RequestType, NotificationType } from "vscode-messenger-common";

const _preFix = "ai-agent";
Expand All @@ -31,7 +31,7 @@ export const getTools: RequestType<AIToolsRequest, AIToolsResponse> = { method:
export const getTool: RequestType<AIToolRequest, AIToolResponse> = { method: `${_preFix}/getTool` };
export const getMcpTools: RequestType<McpToolsRequest, McpToolsResponse> = { method: `${_preFix}/getMcpTools` };
export const genTool: RequestType<AIGentToolsRequest, AIGentToolsResponse> = { method: `${_preFix}/genTool` };
export const configureDefaultModelProvider: NotificationType<void> = { method: `${_preFix}/configureDefaultModelProvider` };
export const configureDefaultModelProvider: NotificationType<ConfigureDefaultModelProviderRequest> = { method: `${_preFix}/configureDefaultModelProvider` };
export const createAIAgent: RequestType<AIAgentRequest, AIAgentResponse> = { method: `${_preFix}/createAIAgent` };
export const updateAIAgentTools: RequestType<AIAgentToolsUpdateRequest, AIAgentResponse> = { method: `${_preFix}/updateAIAgentTools` };
export const updateMCPToolKit: NotificationType<McpToolUpdateRequest> = { method: `${_preFix}/updateMCPToolKit` };
5 changes: 5 additions & 0 deletions workspaces/ballerina/ballerina-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,11 @@
"title": "Configure default WSO2 model provider",
"category": "Ballerina"
},
{
"command": "ballerina.configureDefaultDevantChunker",
"title": "Configure default Devant chunker",
"category": "Ballerina"
},
{
"command": "ballerina.configureDefaultModelForNaturalFunctions",
"title": "Configure default model for natural functions (Experimental)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import { activateCopilotLoginCommand, resetBIAuth } from './completions';
import { generateCodeCore } from './service/code/code';
import { GenerateCodeRequest, ProcessMappingParametersRequest } from '@wso2/ballerina-core';
import { CopilotEventHandler } from './service/event';
import { addConfigFile, getConfigFilePath } from './utils';
import { addConfigFile, AIProviderConfigType, getConfigFilePath } from './utils';
import { StateMachine } from "../../stateMachine";
import { CONFIGURE_DEFAULT_MODEL_COMMAND, DEFAULT_PROVIDER_ADDED, LOGIN_REQUIRED_WARNING_FOR_DEFAULT_MODEL, SIGN_IN_BI_COPILOT } from './constants';
import { REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE, TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL } from '../..//utils/ai/auth';
import { CONFIGURE_DEFAULT_DEVANT_CHUNKER_COMMAND, CONFIGURE_DEFAULT_MODEL_COMMAND, DEFAULT_DEVANT_CHUNKER_ADDED, DEFAULT_PROVIDER_ADDED, LOGIN_REQUIRED_WARNING_FOR_DEFAULT_DEVANT_CHUNKER, LOGIN_REQUIRED_WARNING_FOR_DEFAULT_MODEL, SIGN_IN_BI_COPILOT } from './constants';
import { REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE, TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL } from '../../utils/ai/auth';
import { AIStateMachine } from '../../views/ai-panel/aiMachine';
import { AIMachineEventType } from '@wso2/ballerina-core';
import { generateMappingCodeCore } from './service/datamapper/datamapper';
Expand Down Expand Up @@ -86,13 +86,13 @@ export function activateAIFeatures(ballerinaExternalInstance: BallerinaExtension
});
}

const projectPath = StateMachine.context().projectPath;

commands.registerCommand(CONFIGURE_DEFAULT_MODEL_COMMAND, async (...args: any[]) => {
const projectPath = args[0] || StateMachine.context().projectPath;

const configPath = await getConfigFilePath(ballerinaExternalInstance, projectPath);
if (configPath !== null) {
try {
const result = await addConfigFile(configPath);
const result = await addConfigFile(configPath, AIProviderConfigType.WSO2_MODEL_PROVIDER);
if (result) {
window.showInformationMessage(DEFAULT_PROVIDER_ADDED);
}
Expand All @@ -109,4 +109,28 @@ export function activateAIFeatures(ballerinaExternalInstance: BallerinaExtension
}
}
});
}

commands.registerCommand(CONFIGURE_DEFAULT_DEVANT_CHUNKER_COMMAND, async (...args: any[]) => {
const projectPath = args[0] || StateMachine.context().projectPath;

const configPath = await getConfigFilePath(ballerinaExternalInstance, projectPath);
if (configPath !== null) {
try {
const result = await addConfigFile(configPath, AIProviderConfigType.DEVANT_CHUNKER);
if (result) {
window.showInformationMessage(DEFAULT_DEVANT_CHUNKER_ADDED);
}
} catch (error) {
if ((error as Error).message === REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE || (error as Error).message === TOKEN_REFRESH_ONLY_SUPPORTED_FOR_BI_INTEL) {
window.showWarningMessage(LOGIN_REQUIRED_WARNING_FOR_DEFAULT_DEVANT_CHUNKER, SIGN_IN_BI_COPILOT).then(selection => {
if (selection === SIGN_IN_BI_COPILOT) {
AIStateMachine.service().send(AIMachineEventType.LOGIN);
}
});
} else {
window.showErrorMessage((error as Error).message);
}
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,25 @@

export const CONFIG_FILE_NAME = "Config.toml";
export const CONFIGURE_DEFAULT_MODEL_COMMAND = "ballerina.configureWso2DefaultModelProvider";
export const CONFIGURE_DEFAULT_DEVANT_CHUNKER_COMMAND = "ballerina.configureDefaultDevantChunker";
export const OPEN_AI_PANEL_COMMAND = "ballerina.open.ai.panel";
export const CLOSE_AI_PANEL_COMMAND = "ballerina.close.ai.panel";
export const SIGN_IN_BI_COPILOT = "Sign in to BI Copilot";
export const PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL = "Fetching and saving access token for WSO2 default model provider.";
export const PROGRESS_BAR_MESSAGE_FROM_DEFAULT_DEVANT_CHUNKER = "Fetching and saving access token for default Devant chunker.";
export const ERROR_NO_BALLERINA_SOURCES = "No Ballerina sources";
export const LOGIN_REQUIRED_WARNING = "Please sign in to BI Copilot to use this feature.";
export const LOGIN_REQUIRED_WARNING_FOR_DEFAULT_MODEL = "Please sign in to BI Copilot to configure the WSO2 default model provider.";
export const DEFAULT_PROVIDER_ADDED = "WSO2 default model provider configuration values were added to the Config.toml file.";
export const LOGIN_REQUIRED_WARNING_FOR_DEFAULT_DEVANT_CHUNKER = "Please sign in to BI Copilot to configure the default Devant chunker.";
export const DEFAULT_DEVANT_CHUNKER_ADDED = "Default Devant chunker configuration values were added to the Config.toml file.";

// AI Provider Configuration Constants
export const WSO2_PROVIDER_CONFIG_TABLE = "[ballerina.ai.wso2ProviderConfig]";
export const DEVANT_CHUNKER_CONFIG_TABLE = "[ballerina.ai.devant.chunkerConfig]";
export const DEVANT_CHUNKER_SERVICE_URL = "https://7eff1239-64bb-4663-b256-30a00d187a5c-prod.e1-us-east-azure.choreoapis.dev/rag-agent/rag-ingester/v1.0";
export const CONFIG_KEY_SERVICE_URL = 'serviceUrl';
export const CONFIG_KEY_ACCESS_TOKEN = 'accessToken';

// Datamapper Constants
// Primitive data types supported by the datamapper
Expand Down Expand Up @@ -59,7 +70,7 @@ export enum PrimitiveArrayType {
DECIMAL_ARRAY_NULLABLE = "decimal[]?",
BOOLEAN_ARRAY = "boolean[]",
BOOLEAN_ARRAY_NULLABLE = "boolean[]?",

// Arrays with nullable elements
STRING_OR_NULL_ARRAY = "string?[]",
STRING_OR_NULL_ARRAY_NULLABLE = "string?[]?",
Expand Down
132 changes: 76 additions & 56 deletions workspaces/ballerina/ballerina-extension/src/features/ai/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ import { StateMachine } from "../../stateMachine";
import { getRefreshedAccessToken, REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE } from '../../../src/utils/ai/auth';
import { AIStateMachine } from '../../../src/views/ai-panel/aiMachine';
import { AIMachineEventType } from '@wso2/ballerina-core/lib/state-machine-types';
import { CONFIG_FILE_NAME, ERROR_NO_BALLERINA_SOURCES, PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL } from './constants';
import { getCurrentBallerinaProjectFromContext } from '../config-generator/configGenerator';
import { BallerinaProject } from '@wso2/ballerina-core';
import {
CONFIG_FILE_NAME,
CONFIG_KEY_ACCESS_TOKEN,
CONFIG_KEY_SERVICE_URL,
DEVANT_CHUNKER_CONFIG_TABLE,
DEVANT_CHUNKER_SERVICE_URL,
ERROR_NO_BALLERINA_SOURCES,
PROGRESS_BAR_MESSAGE_FROM_DEFAULT_DEVANT_CHUNKER,
PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL,
WSO2_PROVIDER_CONFIG_TABLE
} from './constants';
import { BallerinaExtension } from 'src/core';

const config = workspace.getConfiguration('ballerina');
Expand All @@ -38,6 +46,12 @@ export const AUTH_REDIRECT_URL: string = config.get('authRedirectURL') || proces
// This refers to old backend before FE Migration. We need to eventually remove this.
export const OLD_BACKEND_URL: string = BACKEND_URL + "/v2.0";

// AI Provider Configuration Types
export enum AIProviderConfigType {
WSO2_MODEL_PROVIDER = 'wso2ModelProvider',
DEVANT_CHUNKER = 'devantChunker'
}

export async function closeAllBallerinaFiles(dirPath: string): Promise<void> {
// Check if the directory exists
if (!fs.existsSync(dirPath)) {
Expand Down Expand Up @@ -85,47 +99,14 @@ export async function closeAllBallerinaFiles(dirPath: string): Promise<void> {
}

export async function getConfigFilePath(ballerinaExtInstance: BallerinaExtension, rootPath: string): Promise<string> {
if (await isBallerinaProjectAsync(rootPath)) {
return rootPath;
}

const activeTextEditor = vscode.window.activeTextEditor;
const currentProject = ballerinaExtInstance.getDocumentContext().getCurrentProject();
let activeFilePath = "";
let configPath = "";

if (rootPath !== "") {
return rootPath;
}
const { resolveConfigFilePath } = await import('../../utils/project-utils');
const result = await resolveConfigFilePath(ballerinaExtInstance, rootPath);

if (activeTextEditor) {
activeFilePath = activeTextEditor.document.uri.fsPath;
}

if (currentProject == null && activeFilePath == "") {
if (result === null) {
return await showNoBallerinaSourceWarningMessage();
}

try {
const currentBallerinaProject: BallerinaProject = await getCurrentBallerinaProjectFromContext(ballerinaExtInstance);

if (!currentBallerinaProject) {
return await showNoBallerinaSourceWarningMessage();
}

if (currentBallerinaProject.kind == 'SINGLE_FILE_PROJECT') {
configPath = path.dirname(currentBallerinaProject.path);
} else {
configPath = currentBallerinaProject.path;
}

if (configPath == undefined || configPath == "") {
return await showNoBallerinaSourceWarningMessage();
}
return configPath;
} catch (error) {
return await showNoBallerinaSourceWarningMessage();
}
return result;
}

export async function getTokenForDefaultModel() {
Expand Down Expand Up @@ -163,13 +144,15 @@ function addOrReplaceConfigLine(lines: string[], key: string, value: string) {
}
}

function addDefaultModelConfig(
projectPath: string, token: string, backendUrl: string): boolean {
const targetTable = `[ballerina.ai.wso2ProviderConfig]`;
const SERVICE_URL_KEY = 'serviceUrl';
const ACCESS_TOKEN_KEY = 'accessToken';
const urlLine = `${SERVICE_URL_KEY} = "${backendUrl}"`;
const accessTokenLine = `${ACCESS_TOKEN_KEY} = "${token}"`;
function addAIProviderConfig(
projectPath: string,
token: string,
serviceUrl: string,
configTable: string
): boolean {
const targetTable = configTable;
const urlLine = `${CONFIG_KEY_SERVICE_URL} = "${serviceUrl}"`;
const accessTokenLine = `${CONFIG_KEY_ACCESS_TOKEN} = "${token}"`;
const configFilePath = findFileCaseInsensitive(projectPath, CONFIG_FILE_NAME);

let fileContent = '';
Expand Down Expand Up @@ -202,15 +185,15 @@ function addDefaultModelConfig(
let lines = tableContent.split('\n');

// Add or replace serviceUrl
addOrReplaceConfigLine(lines, SERVICE_URL_KEY, backendUrl);
addOrReplaceConfigLine(lines, CONFIG_KEY_SERVICE_URL, serviceUrl);
// Add or replace accessToken (after serviceUrl)
// Ensure accessToken is after serviceUrl
let serviceUrlIdx = lines.findIndex(l => l.trim().startsWith(`${SERVICE_URL_KEY} =`));
let accessTokenIdx = lines.findIndex(l => l.trim().startsWith(`${ACCESS_TOKEN_KEY} =`));
let serviceUrlIdx = lines.findIndex(l => l.trim().startsWith(`${CONFIG_KEY_SERVICE_URL} =`));
let accessTokenIdx = lines.findIndex(l => l.trim().startsWith(`${CONFIG_KEY_ACCESS_TOKEN} =`));
if (accessTokenIdx === -1) {
lines.splice(serviceUrlIdx + 1, 0, `${ACCESS_TOKEN_KEY} = "${token}"`);
lines.splice(serviceUrlIdx + 1, 0, `${CONFIG_KEY_ACCESS_TOKEN} = "${token}"`);
} else {
lines[accessTokenIdx] = `${ACCESS_TOKEN_KEY} = "${token}"`;
lines[accessTokenIdx] = `${CONFIG_KEY_ACCESS_TOKEN} = "${token}"`;
// Move accessToken if not after serviceUrl
if (accessTokenIdx !== serviceUrlIdx + 1) {
const accessTokenLine = lines[accessTokenIdx];
Expand All @@ -226,11 +209,17 @@ function addDefaultModelConfig(
return true;
}

export async function addConfigFile(configPath: string): Promise<boolean> {
export async function addConfigFile(
configPath: string,
configType: AIProviderConfigType = AIProviderConfigType.WSO2_MODEL_PROVIDER
): Promise<boolean> {
// Determine the configuration details based on the type
const configDetails = getConfigDetails(configType);

const progress = await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL,
title: configDetails.progressMessage,
cancellable: false,
},
async () => {
Expand All @@ -240,7 +229,12 @@ export async function addConfigFile(configPath: string): Promise<boolean> {
AIStateMachine.service().send(AIMachineEventType.LOGOUT);
throw new Error(REFRESH_TOKEN_NOT_AVAILABLE_ERROR_MESSAGE);
}
const success = addDefaultModelConfig(configPath, token, await getBackendURL());
const success = addAIProviderConfig(
configPath,
token,
configDetails.serviceUrl,
configDetails.configTable
);
if (success) {
return true;
}
Expand All @@ -253,6 +247,32 @@ export async function addConfigFile(configPath: string): Promise<boolean> {
return progress;
}

/**
* Helper function to get configuration details based on the config type
*/
function getConfigDetails(configType: AIProviderConfigType): {
configTable: string;
serviceUrl: string;
progressMessage: string;
} {
switch (configType) {
case AIProviderConfigType.WSO2_MODEL_PROVIDER:
return {
configTable: WSO2_PROVIDER_CONFIG_TABLE,
serviceUrl: OLD_BACKEND_URL,
progressMessage: PROGRESS_BAR_MESSAGE_FROM_WSO2_DEFAULT_MODEL,
};
case AIProviderConfigType.DEVANT_CHUNKER:
return {
configTable: DEVANT_CHUNKER_CONFIG_TABLE,
serviceUrl: DEVANT_CHUNKER_SERVICE_URL,
progressMessage: PROGRESS_BAR_MESSAGE_FROM_DEFAULT_DEVANT_CHUNKER,
};
default:
throw new Error(`Unknown configuration type: ${configType}`);
}
}

export async function isBallerinaProjectAsync(rootPath: string): Promise<boolean> {
try {
if (!fs.existsSync(rootPath)) {
Expand All @@ -270,7 +290,7 @@ export async function isBallerinaProjectAsync(rootPath: string): Promise<boolean
}
}

async function showNoBallerinaSourceWarningMessage() {
export async function showNoBallerinaSourceWarningMessage() {
return await vscode.window.showWarningMessage(ERROR_NO_BALLERINA_SOURCES);
}

Expand Down
Loading