pull/5344/head
STetsing 3 months ago
parent 7adc82c4e1
commit 3055f30143
  1. 2
      apps/remix-ide/src/app/files/fileManager.ts
  2. 42
      apps/remix-ide/src/app/plugins/remixAIPlugin.tsx
  3. 2
      apps/remix-ide/src/app/tabs/locales/en/editor.json
  4. 183
      libs/remix-ai-core/src/agents/securityAgent.ts
  5. 3
      libs/remix-ai-core/src/index.ts
  6. 2
      libs/remix-ai-core/src/inferencers/remote/remoteInference.ts
  7. 9
      libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts
  8. 2
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx

@ -26,7 +26,7 @@ const profile = {
'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile',
'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath',
'saveCurrentFile', 'setBatchFiles', 'isGitRepo', 'isFile', 'isDirectory', 'hasGitSubmodule', 'copyFolderToJson', 'diff',
'hasGitSubmodules'
'hasGitSubmodules', 'getOpenedFiles'
],
kind: 'file-system'
}

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import { Plugin } from '@remixproject/engine';
import { RemixAITab, ChatApi } from '@remix-ui/remix-ai'
import React, { useCallback } from 'react';
import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent } from '@remix/remix-ai-core';
import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent, SecurityAgent} from '@remix/remix-ai-core';
import { CustomRemixApi } from '@remix-api'
type chatRequestBufferT<T> = {
@ -35,13 +35,14 @@ export class RemixAIPlugin extends ViewPlugin {
remoteInferencer:RemoteInferencer = null
isInferencing: boolean = false
chatRequestBuffer: chatRequestBufferT<any> = null
agent: CodeExplainAgent
codeExpAgent: CodeExplainAgent
securityAgent: SecurityAgent
useRemoteInferencer:boolean = false
constructor(inDesktop:boolean) {
super(profile)
this.isOnDesktop = inDesktop
this.agent = new CodeExplainAgent(this)
this.codeExpAgent = new CodeExplainAgent(this)
// user machine dont use ressource for remote inferencing
}
@ -57,6 +58,8 @@ export class RemixAIPlugin extends ViewPlugin {
this.useRemoteInferencer = true
this.initialize()
}
this.securityAgent = new SecurityAgent(this)
}
async initialize(model1?:IModel, model2?:IModel, remoteModel?:IRemoteModel, useRemote?:boolean){
@ -92,11 +95,6 @@ export class RemixAIPlugin extends ViewPlugin {
}
async code_generation(prompt: string): Promise<any> {
if (this.isInferencing) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
if (this.isOnDesktop && !this.useRemoteInferencer) {
return await this.call(this.remixDesktopPluginName, 'code_generation', prompt)
} else {
@ -113,12 +111,7 @@ export class RemixAIPlugin extends ViewPlugin {
}
async solidity_answer(prompt: string, params: IParams=GenerationParams): Promise<any> {
if (this.isInferencing) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
const newPrompt = await this.agent.chatCommand(prompt)
const newPrompt = await this.codeExpAgent.chatCommand(prompt)
let result
if (this.isOnDesktop && !this.useRemoteInferencer) {
result = await this.call(this.remixDesktopPluginName, 'solidity_answer', newPrompt)
@ -130,11 +123,6 @@ export class RemixAIPlugin extends ViewPlugin {
}
async code_explaining(prompt: string, context: string, params: IParams=GenerationParams): Promise<any> {
if (this.isInferencing) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
let result
if (this.isOnDesktop && !this.useRemoteInferencer) {
result = await this.call(this.remixDesktopPluginName, 'code_explaining', prompt, context, params)
@ -147,11 +135,6 @@ export class RemixAIPlugin extends ViewPlugin {
}
async error_explaining(prompt: string, context: string="", params: IParams=GenerationParams): Promise<any> {
if (this.isInferencing) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
let result
if (this.isOnDesktop && !this.useRemoteInferencer) {
result = await this.call(this.remixDesktopPluginName, 'error_explaining', prompt)
@ -163,22 +146,21 @@ export class RemixAIPlugin extends ViewPlugin {
}
async vulnerability_check(prompt: string, params: IParams=GenerationParams): Promise<any> {
if (this.isInferencing) {
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" })
return
}
let result
if (this.isOnDesktop && !this.useRemoteInferencer) {
result = await this.call(this.remixDesktopPluginName, 'vulnerability_check', prompt)
} else {
result = await this.remoteInferencer.vulnerability_check(prompt)
result = await this.remoteInferencer.vulnerability_check(prompt, params)
}
if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result })
return result
}
getVulnerabilityReport(file: string): any {
return this.securityAgent.getReport(file)
}
async code_insertion(msg_pfx: string, msg_sfx: string): Promise<any> {
if (this.isOnDesktop && !this.useRemoteInferencer) {
return await this.call(this.remixDesktopPluginName, 'code_insertion', msg_pfx, msg_sfx)

@ -28,7 +28,7 @@
"editor.explainFunctionByAI": "```\n{content}\n```\nExplain the function {currentFunction}",
"editor.explainFunctionByAISol": "```\n{content}\n```\nExplain the function {currentFunction}",
"editor.ExplainPipeMessage": "```\n {content}\n```\nExplain the snipped above",
"editor.PastedCodeSafety": "```\n {content}\n```\n\nReply in a short maner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?",
"editor.PastedCodeSafety": "```\n {content}\n```\n\nReply in a short manner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?",
"editor.executeFreeFunction": "Run a free function",
"editor.executeFreeFunction2": "Run the free function \"{name}\"",
"editor.toastText1": "This can only execute free function",

@ -1,24 +1,185 @@
// security checks
import * as fs from 'fs';
class SecurityAgent {
private codebase: string[]; // list of code base file
interface SecurityReport {
compiled: boolean;
vulnerabilities: string[];
isNotSafe: string;
fileName: string;
reportTimestamp: string;
recommendations: string[];
fileModifiedSinceLastReport: boolean;
hasPastedCode: boolean;
}
class WorkspaceWatcher {
private intervalId: NodeJS.Timeout | null = null;
public interval: number;
private task: () => void;
constructor(task: () => void, interval: number) {
this.task = task;
this.interval = interval;
}
start(): void {
if (this.intervalId === null) {
this.intervalId = setInterval(() => {
this.task();
}, this.interval);
}
}
stop(): void {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
isRunning(): boolean {
return this.intervalId !== null;
}
}
export class SecurityAgent {
public currentFile: string;
public openedFiles: any;
private basePlugin: any;
private watcher: WorkspaceWatcher;
public reports: SecurityReport[] = [];
constructor(plugin) {
this.basePlugin = plugin;
this.basePlugin.on('fileManager', 'fileAdded', (path) => { });
this.basePlugin.on('fileManager', 'fileChanged', (path) => { //this.modifiedFile(path)
});
this.basePlugin.on('fileManager', 'fileRemoved', (path) => { this.removeFileFromReport(path) });
this.basePlugin.on('fileManager', 'fileRenamed', (oldName, newName) => {
this.removeFileFromReport(oldName);
this.addFileToReport(newName);
});
this.basePlugin.on('solidity', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) });
this.basePlugin.on('vyper', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) });
this.basePlugin.on('hardhat', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) });
this.basePlugin.on('foundry', 'compilationFinished', async (fileName, source, languageVersion, data) => { this.onCompilationFinished(fileName) });
this.watcher = new WorkspaceWatcher(async () => {
try {
this.currentFile = await this.basePlugin.call('fileManager', 'getCurrentFile');
this.openedFiles = await this.basePlugin.call('fileManager', 'getOpenedFiles');
Object.keys(this.openedFiles).forEach(key => {
this.addFileToReport(this.openedFiles[key]);
});
} catch (error) {
// no file selected or opened currently
}
}, 10000);
this.watcher.start();
}
addFileToReport(file: string): void {
const report = this.reports.find((r) => r.fileName === file);
if (report) {
// nothing to do
} else {
this.reports.push({
compiled: false,
isNotSafe: 'No',
vulnerabilities: [],
fileName: file,
reportTimestamp: null,
recommendations: [],
fileModifiedSinceLastReport: false,
hasPastedCode: false
});
}
constructor(codebasePath: string) {
// git or fs
this.codebase = this.loadCodebase(codebasePath);
}
private loadCodebase(path: string): string[] {
const files = fs.readdirSync(path);
return files
.filter(file => file.endsWith('.ts'))
.flatMap(file => fs.readFileSync(`${path}/${file}`, 'utf-8').split('\n'));
async onCompilationFinished(file: string) {
let report = this.reports.find((r) => r.fileName === file);
if (report) {
report.compiled = true;
report.fileModifiedSinceLastReport = false;
} else {
report = {
compiled: true,
isNotSafe: 'No',
vulnerabilities: [],
fileName: file,
reportTimestamp: null,
recommendations: [],
fileModifiedSinceLastReport: false,
hasPastedCode: false
}
this.reports.push(report);
}
try {
this.processFile(file);
console.log('Checking for vulnerabilities after compilation', this.reports);
} catch (error) {
console.error('Error checking for vulnerabilities after compilation: ', error);
}
// check for security vulnerabilities
}
public update(currentFile, lineNumber){
removeFileFromReport(file: string): void {
const index = this.reports.findIndex((r) => r.fileName === file);
if (index !== -1) {
this.reports.splice(index, 1);
}
}
modifiedFile(file: string): void {
const report = this.reports.find((r) => r.fileName === file);
if (report) {
report.fileModifiedSinceLastReport = true;
}
}
async processFile(file: string) {
try {
const report = this.reports.find((r) => r.fileName === file);
if (report) { }
else {
this.reports.push({
compiled: false,
isNotSafe: 'No',
vulnerabilities: [],
fileName: file,
reportTimestamp: null,
recommendations: [],
fileModifiedSinceLastReport: false,
hasPastedCode: false
});
}
if (!report.reportTimestamp || report.fileModifiedSinceLastReport) {
const content = await this.basePlugin.call('fileManager', 'getFile', file);
const prompt = "```\n" + content + "\n```\n\nReply in a short manner: Does this code contain major security vulenerabilities leading to a scam or loss of funds?"
let result = await this.basePlugin.call('remixAI', 'vulnerability_check', prompt)
result = JSON.parse(result);
report.vulnerabilities = result.Reason;
report.recommendations = result.Suggestion;
report.isNotSafe = result.Answer;
report.reportTimestamp = new Date().toISOString();
}
} catch (error) {
console.error('Error processing file: ', error);
}
}
getReport(file: string): SecurityReport {
return this.reports.find((r) => r.fileName === file);
}
public getRecommendations(currentLine: string, numSuggestions: number = 3): string[] {

@ -21,4 +21,5 @@ export {
export * from './types/types'
export * from './helpers/streamHandler'
export * from './agents/codeExplainAgent'
export * from './agents/codeExplainAgent'
export * from './agents/securityAgent'

@ -12,7 +12,7 @@ export class RemoteInferencer implements ICompletions {
max_history = 7
model_op = RemoteBackendOPModel.CODELLAMA // default model operation change this to llama if necessary
event: EventEmitter
test_env=true
test_env=false
test_url="http://solcodertest.org"
constructor(apiUrl?:string, completionUrl?:string) {

@ -9,7 +9,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
props: EditorUIProps
monaco: any
completionEnabled: boolean
task: string
task: string = 'code_completion'
currentCompletion: any
private lastRequestTime: number = 0;
private readonly minRequestInterval: number = 200;
@ -60,13 +60,6 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli
endColumn: getTextAtLine(model.getLineCount()).length + 1,
});
if (!word.endsWith(' ') &&
!word.endsWith('.') &&
!word.endsWith('"') &&
!word.endsWith('(')) {
return;
}
try {
const split = word.split('\n')
if (split.length < 2) return

@ -697,7 +697,7 @@ export const EditorUI = (props: EditorUIProps) => {
</div>
),
}
// get the file name
const pastedCode = editor.getModel().getValueInRange(e.range)
const pastedCodePrompt = intl.formatMessage({ id: 'editor.PastedCodeSafety' }, { content:pastedCode })
props.plugin.call('remixAI', 'chatPipe', 'vulnerability_check', pastedCodePrompt)

Loading…
Cancel
Save