Skip to content
Merged
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
1,267 changes: 1,091 additions & 176 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 26 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"displayName": "NetApp FSx ONTAP",
"description": "",
"version": "1.0.5",
"version": "1.0.6",
"engines": {
"vscode": "^1.102.0"
},
Expand Down Expand Up @@ -38,6 +38,16 @@
"type": "string",
"default": "",
"description": "Company name"
},
"netapp-fsx-ontap.bedrockmodel": {
"type": "string",
"default": "",
"description": "Bedrock model"
},
"netapp-fsx-ontap.bedrockregion": {
"type": "string",
"default": "us-east-1",
"description": "Bedrock region"
}
}
},
Expand Down Expand Up @@ -106,6 +116,11 @@
"category": "NetApp FSx ONTAP",
"icon": "$(account)"
},
{
"command": "netapp-fsx-ontap.update-ontap-login-details",
"title": "Configure ONTAP connection details",
"category": "NetApp FSx ONTAP"
},
{
"command": "netapp-fsx-ontap.addSvm",
"title": "Add SVM",
Expand Down Expand Up @@ -200,14 +215,19 @@
"when": "viewItem == filesystem",
"group": "inline"
},
{
"command": "netapp-fsx-ontap.update-ontap-login-details",
"when": "viewItem == filesystem-withLoginDetails",
"group": "navigation"
},
{
"command": "netapp-fsx-ontap.addVolume-terraform",
"when": "viewItem == svm",
"group": "navigation"
},
{
"command": "netapp-fsx-ontap.addSvm-cloudformation",
"when": "viewItem == filesystem",
"when": "viewItem == filesystem || viewItem == filesystem-withLoginDetails",
"group": "navigation"
},
{
Expand All @@ -217,7 +237,7 @@
},
{
"command": "netapp-fsx-ontap.addSvm-cli",
"when": "viewItem == filesystem",
"when": "viewItem == filesystem || viewItem == filesystem-withLoginDetails",
"group": "navigation"
},
{
Expand All @@ -227,12 +247,12 @@
},
{
"command": "netapp-fsx-ontap.sshToFileSystem",
"when": "viewItem == filesystem",
"when": "viewItem == filesystem || viewItem == filesystem-withLoginDetails",
"group": "inline"
},
{
"command": "netapp-fsx-ontap.addSvm",
"when": "viewItem == filesystem",
"when": "viewItem == filesystem || viewItem == filesystem-withLoginDetails",
"group": "inline"
},
{
Expand Down Expand Up @@ -274,6 +294,7 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.948.0",
"@aws-sdk/client-cloudwatch": "^3.922.0",
"@aws-sdk/client-fsx": "^3.922.0",
"@aws-sdk/client-sts": "^3.922.0",
Expand Down
8 changes: 5 additions & 3 deletions src/FileSystemsTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export class FileSystemsTree implements vscode.TreeDataProvider<vscode.TreeItem>
} else {
if(element.contextValue === 'region') {
const fileSystems = await listFileSystems(element.id || 'us-east-1');
return fileSystems.map(fs => {
return Promise.all(fileSystems.map(async fs => {
const name = fs.Tags?.find(tag => tag.Key === 'Name')?.Value || fs.FileSystemId;
const fsItem = new FileSystemsItem(name || '', fs.FileSystemId || '', fs, element.id || 'us-east-1', vscode.TreeItemCollapsibleState.Collapsed);
const sshInfoStr = await state.context.secrets.get(`sshKey-${fs.FileSystemId}-${element.id || 'us-east-1'}`);
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secrets.get call is made inside a map operation for each filesystem, resulting in N sequential async calls. Consider batching these secret retrievals or caching them to improve performance when rendering many filesystems.

Copilot uses AI. Check for mistakes.
const fsItem = new FileSystemsItem(name || '', fs.FileSystemId || '', fs, element.id || 'us-east-1',
vscode.TreeItemCollapsibleState.Collapsed, !!sshInfoStr);
return fsItem;
});
}));
}
if(element.contextValue === 'filesystem') {
const e = element as FileSystemsItem;
Expand Down
11 changes: 4 additions & 7 deletions src/TreeItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@ export class FileSystemsItem extends vscode.TreeItem {
public readonly fs: FileSystem,
public readonly region: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly hasOntapLoginDetails: boolean,
public readonly command?: vscode.Command
) {
super(name, collapsibleState);

this.tooltip = `${this.name}`;
this.description = this.id;
this.iconPath = new vscode.ThemeIcon('cloud');
this.iconPath = this.hasOntapLoginDetails ? new vscode.ThemeIcon('pass-filled') : new vscode.ThemeIcon('cloud');
this.contextValue = this.hasOntapLoginDetails ? 'filesystem-withLoginDetails' : 'filesystem';
}

/*iconPath = {
light: path.join(__filename, '..', '..', 'resources', 'light', 'dependency.svg'),
dark: path.join(__filename, '..', '..', 'resources', 'dark', 'dependency.svg')
};*/

contextValue = 'filesystem';


}

Expand Down
29 changes: 29 additions & 0 deletions src/chat/modelFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as vscode from 'vscode';
import { CopilotModel } from './models/copilot';
import { BedrockModel } from './models/bedrock';

export type MessageType = {
role: 'user' | 'assistant' | 'system';
};

export interface Model {
sendMessage: (message: string, messageType: MessageType) => Promise<string>;
init: () => Promise<void>;
type: string;
}




export async function getModel(token: vscode.CancellationToken): Promise<Model> {
const bedrockModel = vscode.workspace.getConfiguration('netapp-fsx-ontap').get('bedrockmodel',"");
if (bedrockModel) {
const model = new BedrockModel(bedrockModel as string);
Comment on lines +19 to +21
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty string default for bedrockmodel is used as a boolean check on line 20. This is unclear - consider using a null/undefined default or explicitly checking for empty string to make the intent clearer.

Suggested change
const bedrockModel = vscode.workspace.getConfiguration('netapp-fsx-ontap').get('bedrockmodel',"");
if (bedrockModel) {
const model = new BedrockModel(bedrockModel as string);
const bedrockModel = vscode.workspace.getConfiguration('netapp-fsx-ontap').get<string>('bedrockmodel', undefined);
if (typeof bedrockModel === 'string' && bedrockModel.trim() !== "") {
const model = new BedrockModel(bedrockModel);

Copilot uses AI. Check for mistakes.
await model.init();
return model;
} else {
const model = new CopilotModel(token);
await model.init();
return model;
}
}
54 changes: 54 additions & 0 deletions src/chat/models/bedrock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { BedrockRuntimeClient, ConversationRole, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
import { MessageType, Model } from "../modelFactory";
import * as vscode from 'vscode';
import { state } from "../../state";

export class BedrockModel implements Model {
type: string = "bedrock";
client: BedrockRuntimeClient | null = null;
temperature = 0.7;
maxTokens = 4096;

constructor(private modelName: string) {}

async init(): Promise<void> {
const bedrockRegion = vscode.workspace.getConfiguration('netapp-fsx-ontap').get('bedrockregion',"us-east-1");
try {
this.client = new BedrockRuntimeClient({
region: bedrockRegion as string,
profile: state.currentProfile
});
console.log(`Initialized Bedrock client for model: ${this.modelName} in region: ${bedrockRegion}`);
} catch (error) {
console.error("Failed to initialize Bedrock client:", error);
throw error;
}

}

async sendMessage(message: string, messageType: MessageType): Promise<string> {
const content = [{
role: ConversationRole.USER,
Comment on lines +30 to +31
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The messageType parameter is not being used. The role is hardcoded to ConversationRole.USER, but the function receives a messageType parameter that should determine whether the role is 'user' or 'assistant'. This inconsistency means all messages will be sent as user messages regardless of the intended role.

Suggested change
const content = [{
role: ConversationRole.USER,
// Map MessageType to ConversationRole
const role = messageType === "user" ? ConversationRole.USER : ConversationRole.ASSISTANT;
const content = [{
role: role,

Copilot uses AI. Check for mistakes.
content: [{ text: message }],
}];

const command = new ConverseCommand({
modelId: this.modelName,
messages: content,

inferenceConfig: {
maxTokens: this.maxTokens,
temperature: this.temperature,
},
});

if(!this.client) {
throw new Error("Bedrock client not initialized");
}

const response = await this.client.send(command);
return response.output?.message?.content?.map(c => c.text).join("") || "";


}
}
45 changes: 45 additions & 0 deletions src/chat/models/copilot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as vscode from 'vscode';
import { MessageType, Model } from "../modelFactory";


export class CopilotModel implements Model {
type: string = "copilot";
chatModel: vscode.LanguageModelChat | null = null;

constructor(private token: vscode.CancellationToken) {}

async getWorkingLanguageModel(): Promise<vscode.LanguageModelChat | null> {
const chatModels = await vscode.lm.selectChatModels();

for (const model of chatModels) {
try {
// Test the model with a simple request
const testMessage = vscode.LanguageModelChatMessage.Assistant('Hello');
await model.sendRequest([testMessage], {}, new vscode.CancellationTokenSource().token);
return model;
Comment on lines +18 to +19
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new CancellationTokenSource is created but never disposed. This could lead to resource leaks. Consider storing the token source in a variable and calling dispose() after the request, or reuse the existing token passed to the constructor.

Suggested change
await model.sendRequest([testMessage], {}, new vscode.CancellationTokenSource().token);
return model;
const cts = new vscode.CancellationTokenSource();
try {
await model.sendRequest([testMessage], {}, cts.token);
return model;
} finally {
cts.dispose();
}

Copilot uses AI. Check for mistakes.
} catch (error) {
console.warn(`Model ${model.vendor}/${model.name} failed test:`, error);
continue;
}
}

return null;
}

async init(): Promise<void> {
this.chatModel = await this.getWorkingLanguageModel();
console.log(`Using language model: ${this.chatModel?.vendor}/${this.chatModel?.name}`);
}

async sendMessage(message: string, messageType: MessageType): Promise<string> {
const messageObj = messageType.role === 'user' ? vscode.LanguageModelChatMessage.User(message)
: vscode.LanguageModelChatMessage.Assistant(message);

const request = await this.chatModel?.sendRequest([messageObj], {}, this.token);
let response = '';
for await (const fragment of request?.text || []) {
response = response + fragment;
}
return response;
}
}
4 changes: 3 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ export async function sshToFileSystem(item: any) {
await SSHService.sshToFileSystem(item.id, item.name, item.region, item.fs.OntapConfiguration.Endpoints.Management.IpAddresses[0]);
}

export async function addOntapLoginDetails(fileSystem: any) {

export async function addOntapLoginDetails(fileSystem: any, refreshFunc: () => void) {
try{
const connectionDetails = await SSHService.promptForConnectionDetails(fileSystem.fs.OntapConfiguration.Endpoints.Management.DNSName ,
fileSystem.id, fileSystem.fs.OntapConfiguration.Endpoints.Management.IpAddresses[0], true);
await executeOntapCommands(fileSystem.fs, ['volume show'], connectionDetails);
await state.context.secrets.store(`sshKey-${fileSystem.id}-${fileSystem.region}`, JSON.stringify(connectionDetails));
vscode.window.showInformationMessage(`ONTAP login details for file system ${fileSystem.id} added successfully.`);
refreshFunc();
} catch (error: any) {
vscode.window.showErrorMessage(`Error adding ONTAP login details: ${error.message}`);
}
Expand Down
Loading