Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b15b5e8
Usage limit bug fixes
xlight05 Feb 18, 2026
7c2aaec
Disable form fields when save button clicked
sachiniSam Feb 23, 2026
110ade3
Fix variable creation in helper pane
senithkay Feb 24, 2026
6d037a6
Fix create new model provider action not being shown in form
dan-niles Feb 24, 2026
8fa2ad1
Fix slider editor not showing in AI evaluation form
dan-niles Feb 24, 2026
002eae2
Address code rabbit comments
xlight05 Feb 24, 2026
2a4a71d
Add saving state management to AIEvaluationForm and TestFunctionForm
dan-niles Feb 24, 2026
db06152
Add error banner for save connection step
sachiniSam Feb 24, 2026
789cb6e
Merge remote-tracking branch 'upstream/release/bi-1.8.x' into persist…
sachiniSam Feb 24, 2026
641a040
make expression mode default in record editor
senithkay Feb 24, 2026
7bd9070
Merge pull request #1544 from sachiniSam/persistImprovements
sachiniSam Feb 24, 2026
7c44bac
Merge pull request #1543 from senithkay/fix-helper-variable-creation
kanushka Feb 24, 2026
e91c9f0
Merge pull request #1546 from senithkay/make-expression-mode-default-…
kanushka Feb 24, 2026
4dc3398
Fix ACTION_EXPRESSION editors to use fieldInputType.fieldType
dan-niles Feb 24, 2026
d089eb2
Quote executable paths to handle spaces in directory names across var…
NipunaRanasinghe Feb 24, 2026
a3477d8
Refactor to use quoteShellPath utility for quoting executable paths
NipunaRanasinghe Feb 24, 2026
825ce5c
Merge pull request #1542 from dan-niles/fix-model-provider-form
senithkay Feb 24, 2026
11d6560
Address review suggestions
NipunaRanasinghe Feb 24, 2026
a3a33f4
Merge pull request #1551 from NipunaRanasinghe/release/bi-1.8.x
kanushka Feb 25, 2026
f8453f6
Merge pull request #1540 from xlight05/usage-limits
xlight05 Feb 25, 2026
8fc32ac
Fix client config import
VellummyilumVinoth Feb 25, 2026
ab734ba
avoid compressing cli files into zip
kaje94 Feb 25, 2026
7cd9807
Merge pull request #1563 from wso2/devant-connections-v3
kaje94 Feb 25, 2026
0aa5333
Merge pull request #1559 from VellummyilumVinoth/fix-config
xlight05 Feb 25, 2026
dcaa672
Fix bn.js vulnerability
gigara Feb 23, 2026
dbc26ad
Update pnpm-lock.yaml to resolve caniuse-db and caniuse-lite versions
gigara Feb 23, 2026
614a664
Enable skipLibCheck in TypeScript configuration for improved type-che…
gigara Feb 23, 2026
eea2463
update pnpm lock file
kaje94 Feb 25, 2026
e4ba4ac
Merge pull request #1565 from wso2/devant-connections-v3
kaje94 Feb 25, 2026
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
6 changes: 6 additions & 0 deletions common/config/rush/.pnpmfile.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ module.exports = {
if (pkg.dependencies['lodash']) {
pkg.dependencies['lodash'] = '4.17.23';
}
if (pkg.dependencies['bn.js']) {
pkg.dependencies['bn.js'] = '5.2.3';
}
}

if (pkg.devDependencies) {
Expand Down Expand Up @@ -109,6 +112,9 @@ module.exports = {
if (pkg.devDependencies['lodash']) {
pkg.devDependencies['lodash'] = '4.17.23';
}
if (pkg.devDependencies['bn.js']) {
pkg.devDependencies['bn.js'] = '5.2.3';
}
}

return pkg;
Expand Down
2,471 changes: 1,244 additions & 1,227 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions workspaces/apk/apk-extension/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib": [
"ES2020"
],
"skipLibCheck": true,
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
UIChatMessage,
CheckpointInfo,
AbortAIGenerationRequest,
UsageResponse,
} from "./interfaces";

export interface AIPanelAPI {
Expand Down Expand Up @@ -107,4 +108,5 @@ export interface AIPanelAPI {
clearChat: () => Promise<void>;
updateChatMessage: (params: UpdateChatMessageRequest) => Promise<void>;
getActiveTempDir: () => Promise<string>;
getUsage: () => Promise<UsageResponse | undefined>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,8 @@ export interface AbortAIGenerationRequest {
/** Thread identifier (defaults to 'default') */
threadId?: string;
}

export interface UsageResponse {
remainingUsagePercentage: number;
resetsIn: number; // in seconds
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
UIChatMessage,
CheckpointInfo,
AbortAIGenerationRequest,
UsageResponse,
} from "./interfaces";
import { RequestType, NotificationType } from "vscode-messenger-common";

Expand Down Expand Up @@ -96,3 +97,4 @@ export const restoreCheckpoint: RequestType<RestoreCheckpointRequest, void> = {
export const clearChat: RequestType<void, void> = { method: `${_preFix}/clearChat` };
export const updateChatMessage: RequestType<UpdateChatMessageRequest, void> = { method: `${_preFix}/updateChatMessage` };
export const getActiveTempDir: RequestType<void, string> = { method: `${_preFix}/getActiveTempDir` };
export const getUsage: RequestType<void, UsageResponse | undefined> = { method: `${_preFix}/getUsage` };
18 changes: 11 additions & 7 deletions workspaces/ballerina/ballerina-extension/src/core/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
outputChannel,
isWindows,
isWSL,
quoteShellPath,
isSupportedVersion,
VERSION,
isSupportedSLVersion,
Expand Down Expand Up @@ -1819,7 +1820,8 @@ export class BallerinaExtension {
debug("[VERSION] Non-Windows platform detected, no extension needed");
}

let ballerinaCommand = distPath + 'bal' + exeExtension + ' version';
// Build the executable path separately so we can quote it for shell execution
let balExecutablePath = distPath + 'bal' + exeExtension;

// Handle WSL environment - prefer native Linux installation over Windows .bat files
if (isWSL()) {
Expand All @@ -1829,26 +1831,28 @@ export class BallerinaExtension {
// Check if 'bal' command is available in PATH
execSync('which bal', { encoding: 'utf8', timeout: 5000 });
// If we get here, 'bal' is available, use it instead of .bat
ballerinaCommand = 'bal version';
balExecutablePath = 'bal';
debug("[VERSION] WSL detected native 'bal' command, using it instead of .bat file");
} catch (error) {
debug("[VERSION] No native 'bal' command found in WSL, will try .bat file");
// If the path contains Windows-style paths, we need to handle them properly
if (ballerinaCommand.includes('\\') || ballerinaCommand.match(/^[A-Za-z]:/)) {
if (balExecutablePath.includes('\\') || balExecutablePath.match(/^[A-Za-z]:/)) {
debug("[VERSION] WSL detected with Windows path, attempting to convert to WSL path");
// Try to convert Windows path to WSL path
const wslPath = ballerinaCommand.replace(/^([A-Za-z]):/, '/mnt/$1').replace(/\\/g, '/').toLowerCase();
debug(`[VERSION] Converted Windows path to WSL path: ${wslPath}`);
ballerinaCommand = wslPath;
balExecutablePath = balExecutablePath.replace(/^([A-Za-z]):/, '/mnt/$1').replace(/\\/g, '/').toLowerCase();
debug(`[VERSION] Converted Windows path to WSL path: ${balExecutablePath}`);
}
}
} else {
// We have a native Linux installation, use it directly
ballerinaCommand = 'bal version';
balExecutablePath = 'bal';
debug("[VERSION] WSL detected with native Linux installation, using 'bal version'");
}
}

// Quote the executable path to handle spaces in directory names
let ballerinaCommand = `${quoteShellPath(balExecutablePath)} version`;

debug(`[VERSION] Executing command: '${ballerinaCommand}'`);

let ballerinaExecutor = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { StateMachine } from '../../../../stateMachine';
import * as path from 'path';
import { Uri } from 'vscode';

export const DIAGNOSTICS_TOOL_NAME = "getCompilationErrors";

/**
* Diagnostic entry enriched with resolving hints
*/
Expand Down Expand Up @@ -128,6 +130,24 @@ export async function checkCompilationErrors(
};
}

// HACK: When the generated code includes `import ballerinax/client.config;` (without the quoted
// identifier), the language server returns diagnostics with the module name stripped to
// `ballerinax/.config as config` — omitting "client". As a workaround, we detect this and
// instruct the agent to use the correct quoted form `import ballerinax/'client.config;`
// instead of attempting to resolve the dependency automatically.
const hasInvalidClientModuleImport = enrichedDiagnostics.some(
d => d.code === "BCE2003" && d.message.includes("ballerinax/.config as config")
);
if (hasInvalidClientModuleImport) {
console.log(`[DiagnosticsUtils] Detected invalid client module import 'ballerinax/client.config'.`);
return {
diagnostics: enrichedDiagnostics,
message: `Found a module resolution error: the import 'import ballerinax/client.config;' is invalid. ` +
`Fix this by replacing the import statement with 'import ballerinax/'client.config;'. ` +
`After applying the fix, call the ${DIAGNOSTICS_TOOL_NAME} tool again to verify there are no remaining errors.`
};
}

console.log(`[DiagnosticsUtils] Enriched Diagnostics:`, enrichedDiagnostics);
return {
diagnostics: enrichedDiagnostics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { z } from 'zod';
import child_process from 'child_process';
import { CopilotEventHandler } from '../../utils/events';
import { extension } from '../../../../BalExtensionContext';
import { quoteShellPath } from '../../../../utils/config';
import { DIAGNOSTICS_TOOL_NAME } from './diagnostics';

export const TEST_RUNNER_TOOL_NAME = "runTests";
Expand Down Expand Up @@ -99,7 +100,7 @@ function parseTestSummary(output: string): string {
async function runBallerinaTests(cwd: string): Promise<TestRunResult> {
return new Promise((resolve) => {
const balCmd = extension.ballerinaExtInstance.getBallerinaCmd();
const command = `${balCmd} test`;
const command = `${quoteShellPath(balCmd)} test`;

console.log(`[TestRunner] Running: ${command} in ${cwd}`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export async function fetchWithAuth(input: string | URL | Request, options: Requ
// Handle usage limit exceeded
if (response.status === 429) {
console.log("Usage limit exceeded (429)");
const error = new Error("Usage limit exceeded. Please try again later.");
const error = new Error("Usage limit exceeded.");
error.name = "UsageLimitError";
(error as any).statusCode = 429;
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
// Standard Error objects have a .message property
if (error.name === "UsageLimitError") {
return "Usage limit exceeded. Please try again later.";
return "Usage limit exceeded.";
}
if (error.name === "AI_RetryError") {
return "An error occured connecting with the AI service. Please try again later.";
Expand All @@ -333,7 +333,7 @@ export function getErrorMessage(error: unknown): string {
) {
// Check if it has a statusCode property indicating 429
if ("statusCode" in error && (error as any).statusCode === 429) {
return "Usage limit exceeded. Please try again later.";
return "Usage limit exceeded.";
}
return (error as { message: string }).message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
createVersionNumber
} from "../../utils";
import { getProjectWorkingDirectory } from "../../utils/file-utils";
import { quoteShellPath } from "../../utils/config";
import { decimal, ExecutableOptions } from 'vscode-languageclient/node';
import { BAL_NOTEBOOK, getTempFile, NOTEBOOK_CELL_SCHEME } from '../../views/notebook';
import fileUriToPath from 'file-uri-to-path';
Expand Down Expand Up @@ -603,7 +604,8 @@ class BallerinaDebugAdapterDescriptorFactory implements DebugAdapterDescriptorFa
}
getScriptPath(args: string[]): string {
args.push('start-debugger-adapter');
return extension.ballerinaExtInstance.getBallerinaCmd();
// Quote the path to handle spaces in directory names (used with shell: true)
return quoteShellPath(extension.ballerinaExtInstance.getBallerinaCmd());
}
getCurrentWorkingDir(): string {
return path.join(extension.ballerinaExtInstance.ballerinaHome, "bin");
Expand Down Expand Up @@ -662,7 +664,7 @@ class BIRunAdapter extends LoggingDebugSession {
task: 'run'
};

let runCommand: string = `${extension.ballerinaExtInstance.getBallerinaCmd()} run`;
let runCommand: string = `${quoteShellPath(extension.ballerinaExtInstance.getBallerinaCmd())} run`;

const programArgs = (args as any).programArgs;
if (programArgs && programArgs.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import { BallerinaProject } from "@wso2/ballerina-core";
import { Terminal, window, workspace } from "vscode";
import { isSupportedSLVersion, isWindows, createVersionNumber } from "../../../utils";
import { isSupportedSLVersion, isWindows, createVersionNumber, quoteShellPath } from "../../../utils";
import { extension } from "../../../BalExtensionContext";
import { TracerMachine } from "../../../features/tracing";

Expand Down Expand Up @@ -128,7 +128,7 @@ export function runCommandWithConf(file: BallerinaProject | string, executor: st
}
let commandText;
if (cmd === BALLERINA_COMMANDS.OTHER) {
commandText = `${executor} ${argsList}`;
commandText = `${quoteShellPath(executor)} ${argsList}`;
terminal = window.createTerminal({ name: TERMINAL_NAME });
} else {
let env = {};
Expand Down Expand Up @@ -164,7 +164,7 @@ export function runCommandWithConf(file: BallerinaProject | string, executor: st
}
}

commandText = `${executor} ${cmd} ${argsList}`;
commandText = `${quoteShellPath(executor)} ${cmd} ${argsList}`;
if (confPath !== '') {
const configs = env['BAL_CONFIG_FILES'] ? `${env['BAL_CONFIG_FILES']}:${confPath}` : confPath;
Object.assign(env, { BAL_CONFIG_FILES: configs });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { BALLERINA_COMMANDS } from "../project";
import { discoverTests, gatherTestItems } from "./discover";
import { testController, projectRoot } from "./activator";
import { extension } from "../../BalExtensionContext";
import { quoteShellPath } from "../../utils/config";

enum EXEC_ARG {
TESTS = '--tests',
Expand Down Expand Up @@ -72,7 +73,7 @@ export function runHandler(request: TestRunRequest, cancellation: CancellationTo
try {
// execute test
const executor = extension.ballerinaExtInstance.getBallerinaCmd();
const commandText = `${executor} ${BALLERINA_COMMANDS.TEST} ${EXEC_ARG.TESTS} ${testNames} ${EXEC_ARG.COVERAGE}`;
const commandText = `${quoteShellPath(executor)} ${BALLERINA_COMMANDS.TEST} ${EXEC_ARG.TESTS} ${testNames} ${EXEC_ARG.COVERAGE}`;
await runCommand(commandText, projectRoot);

} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ export async function isModuleNotFoundDiagsExist(diagnosticsResult: Diagnostics[

// Process each unique diagnostic only once
let projectModified = false;
for (const [_, { uri }] of uniqueDiagnosticMap.entries()) {
for (const [message, { uri }] of uniqueDiagnosticMap.entries()) {
// Skip resolving dependencies for the invalid config import pattern
if (message.includes("ballerinax/.config as config")) {
continue;
}

const dependenciesResponse = await langClient.resolveModuleDependencies({
documentIdentifier: {
uri: uri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ import {
TaskDeclineRequest,
updateChatMessage,
UpdateChatMessageRequest,
updateRequirementSpecification
updateRequirementSpecification,
getUsage
} from "@wso2/ballerina-core";
import { Messenger } from "vscode-messenger";
import { AiPanelRpcManager } from "./rpc-manager";
Expand Down Expand Up @@ -137,4 +138,5 @@ export function registerAiPanelRpcHandlers(messenger: Messenger) {
messenger.onRequest(clearChat, () => rpcManger.clearChat());
messenger.onRequest(updateChatMessage, (args: UpdateChatMessageRequest) => rpcManger.updateChatMessage(args));
messenger.onRequest(getActiveTempDir, () => rpcManger.getActiveTempDir());
messenger.onRequest(getUsage, () => rpcManger.getUsage());
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import {
SubmitFeedbackRequest,
TestGenerationMentions,
UIChatMessage,
UpdateChatMessageRequest
UpdateChatMessageRequest,
UsageResponse
} from "@wso2/ballerina-core";
import * as fs from 'fs';
import path from "path";
Expand All @@ -65,12 +66,14 @@ import { refreshDataMapper } from "../data-mapper/utils";
import {
TEST_DIR_NAME
} from "./constants";
import { fetchWithAuth } from "../../features/ai/utils/ai-client";
import { BACKEND_URL } from "../../features/ai/utils";
import { addToIntegration, cleanDiagnosticMessages, searchDocumentation } from "./utils";

import { onHideReviewActions } from '@wso2/ballerina-core';
import { createExecutionContextFromStateMachine, createExecutorConfig, generateAgent } from '../../features/ai/agent/index';
import { integrateCodeToWorkspace } from "../../features/ai/agent/utils";
import { WI_EXTENSION_ID } from "../../features/ai/constants";
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';
import { InlineMappingExecutor } from '../../features/ai/executors/datamapper/InlineMappingExecutor';
Expand Down Expand Up @@ -699,4 +702,24 @@ export class AiPanelRpcManager implements AIPanelAPI {
console.log(">>> active temp project path", projectPath);
return projectPath;
}

async getUsage(): Promise<UsageResponse | undefined> {
const loginMethod = await getLoginMethod();
if (loginMethod !== LoginMethod.BI_INTEL) {
return undefined;
}
try {
const url = BACKEND_URL + LLM_API_BASE_PATH + "/usage";
const response = await fetchWithAuth(url, { method: "GET" });
if (response && response.ok) {
const data = await response.json();
return data as UsageResponse;
}
console.error("Failed to fetch usage: ", response?.status, response?.statusText);
return undefined;
} catch (error) {
console.error("Failed to fetch usage:", error);
return undefined;
}
}
}
21 changes: 21 additions & 0 deletions workspaces/ballerina/ballerina-extension/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ export function isWSL(): boolean {
);
}

/**
* Wraps a file path in double quotes if it contains spaces,
* so it can be safely used in shell command strings.
* Handles already-quoted paths and escapes embedded double quotes.
*/
export function quoteShellPath(filePath: string): string {
// Strip existing surrounding quotes to normalize
let normalized = filePath;
if (normalized.length >= 2 && normalized.startsWith('"') && normalized.endsWith('"')) {
normalized = normalized.slice(1, -1);
}

if (!normalized.includes(' ')) {
return normalized;
}

// Escape any embedded double quotes
const escaped = normalized.replace(/"/g, '\\"');
return `"${escaped}"`;
}

export function isSupportedVersion(ballerinaExtInstance: BallerinaExtension, supportedRelease: VERSION,
supportedVersion: number): boolean {
const ballerinaVersion: string = ballerinaExtInstance.ballerinaVersion.toLocaleLowerCase();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { extension } from "../BalExtensionContext";
import { RPCLayer } from "../RPCLayer";
import { VisualizerWebview } from "../views/visualizer/webview";
import { debug } from "./logger";
import { quoteShellPath } from "./config";

const PROGRESS_COMPLETE = 100;

Expand Down Expand Up @@ -53,7 +54,7 @@ export async function pullMigrationTool(migrationToolName: string, version: stri
}

const ballerinaCmd = extension.ballerinaExtInstance.getBallerinaCmd();
const command = `${ballerinaCmd} tool pull ${migrationToolName}:${version}`;
const command = `${quoteShellPath(ballerinaCmd)} tool pull ${migrationToolName}:${version}`;
debug(`Executing migration tool pull command: ${command}`);

// 2. This function now returns a promise that wraps the exec lifecycle
Expand Down
Loading
Loading