diff --git a/src/action/actionManager.ts b/src/action/actionManager.ts index cc177d1..31c4b6b 100644 --- a/src/action/actionManager.ts +++ b/src/action/actionManager.ts @@ -1,9 +1,12 @@ -import { Action, CustomActions } from './customAction'; +import fs from 'fs'; + +import { Action, CustomActions, getActionInstruction } from './customAction'; import { CommandResult } from '../util/commonUtil'; import { logger } from '../util/logger'; - +import { SymbolRefAction } from './symbolRefAction'; +import { SymbolDefAction } from './symbolDefAction'; // extend Action @@ -29,7 +32,7 @@ export class CommandRunAction implements Action { async handlerAction(args: {[key: string]: any}): Promise { try { const commandData = JSON.parse(args.content); - const result = await ActionManager.getInstance().applyCommandAction(commandData.command, commandData.args); + const result = await ActionManager.getInstance().applyCommandAction(commandData.name, commandData.arguments); return result; } catch (error) { logger.channel()?.error('Failed to parse code file content: ' + error); @@ -51,6 +54,9 @@ export default class ActionManager { } ActionManager.instance.registerAction(new CommandRunAction()); + ActionManager.instance.registerAction(new SymbolRefAction()); + ActionManager.instance.registerAction(new SymbolDefAction()); + return ActionManager.instance; } @@ -160,4 +166,23 @@ export default class ActionManager { this.registerAction(chatAction); } } + + public actionInstruction(): string { + let functionsDefList = [] + for (const action of this.actions) { + functionsDefList.push(getActionInstruction(action)); + } + + // return as json string + return JSON.stringify(functionsDefList, null, 4); + } + + public saveActionInstructionFile(tarFile: string): void { + try { + fs.writeFileSync(tarFile, this.actionInstruction()); + } catch (error) { + logger.channel()?.error(`Failed to save action instruction file: ${error}`); + logger.channel()?.show(); + } + } } \ No newline at end of file diff --git a/src/action/customAction.ts b/src/action/customAction.ts index 394a616..1fdeaa3 100644 --- a/src/action/customAction.ts +++ b/src/action/customAction.ts @@ -15,6 +15,27 @@ export interface Action { handlerAction: (args: { [key: string]: string }) => Promise; } +// generate instruction for action +export function getActionInstruction(action: Action): any { + const actionSchema = { + name: action.name, + description: action.description, + parameters: { + type: "object", + properties: action.args.reduce((obj: any, arg: any) => { + obj[arg.name] = { + type: arg.type, + description: arg.description + }; + return obj; + }, {}), + required: action.args.filter((arg: any) => arg.required).map((arg: any) => arg.name) + } + }; + + return actionSchema; +} + export class CustomActions { private static instance: CustomActions | null = null; private actions: Action[] = []; @@ -29,44 +50,6 @@ export class CustomActions { return CustomActions.instance; } - public actionInstruction(): string { - let instruction = 'As an AI bot, you replay user with "command" block, don\'t replay any other words.\n' + - '"command" block is like this:\n' + - '``` command\n' + - '{\n' + - ' "command": "xxx",\n' + - ' "args" {\n' + - ' "var name": "xxx"\n' + - ' }\n' + - '}\n' + - '```\n' + - 'You can split task into small sub tasks, after each command I will give you the result of command executed. so, the next command can depend pre command\'s output.\n' + - '\n' + - 'Supported commands are:\n'; - let index = 1; - for (const action of this.actions) { - instruction += String(index) + ". " + this.getActionInstruction(action.name) + "\n"; - index += 1; - } - - instruction += 'Restriction for output:\n' + - '1. Only reponse "command" block.\n' + - '2. Don\'t include any other text exclude command.\n' + - '3. Only supported extension commands can be used to complete the response.\n' + - '4. When update file, old_content must include at least three lines.'; - - return instruction; - } - - public saveActionInstructionFile(tarFile: string): void { - try { - fs.writeFileSync(tarFile, this.actionInstruction()); - } catch (error) { - logger.channel()?.error(`Failed to save action instruction file: ${error}`); - logger.channel()?.show(); - } - } - public parseActions(workflowsDir: string): void { this.actions = []; @@ -162,23 +145,4 @@ export class CustomActions { public getActions(): Action[] { return this.actions; } - - // generate instruction for action - public getActionInstruction(actionName: string): string { - const action = this.actions.find(action => action.name.trim() === actionName.trim()); - if (!action) { - return ''; - } - - let instruction = `${action.name}: ${action.description}\n`; - // if args is not undefined and has values, then visit args - if (action.args !== undefined && action.args.length > 0) { - instruction += `Args:\n`; - for (const arg of action.args) { - instruction += ` name: ${arg.name} type: (${arg.type}) description: ${arg.description}\n`; - } - } - - return instruction; - } } \ No newline at end of file diff --git a/src/action/symbolDefAction.ts b/src/action/symbolDefAction.ts new file mode 100644 index 0000000..288a1ef --- /dev/null +++ b/src/action/symbolDefAction.ts @@ -0,0 +1,40 @@ + +import { Action, CustomActions } from './customAction'; + +import { CommandResult } from '../util/commonUtil'; +import { logger } from '../util/logger'; + + +export class SymbolDefAction implements Action { + name: string; + description: string; + type: string[]; + action: string; + handler: string[]; + args: { "name": string, "description": string, "type": string, "as"?: string, "from": string }[]; + + constructor() { + this.name = 'symbol_def'; + this.description = 'Retrieve the definition information related to the symbol'; + this.type = ['symbol']; + this.action = 'symbol_def'; + this.handler = []; + this.args = [ + {"name": "symbol", "description": "The symbol variable specifies the symbol for which definition information is to be retrieved.", "type": "string", "from": "content.content.symbol"}, + ]; + } + + async handlerAction(args: {[key: string]: any}): Promise { + try { + const symbolName = args.symbol; + + // get reference information + + return {exitCode: 0, stdout: "", stderr: ""}; + } catch (error) { + logger.channel()?.error(`${this.name} handle error: ${error}`); + logger.channel()?.show(); + return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`}; + } + } +}; \ No newline at end of file diff --git a/src/action/symbolRefAction.ts b/src/action/symbolRefAction.ts new file mode 100644 index 0000000..e29ba5c --- /dev/null +++ b/src/action/symbolRefAction.ts @@ -0,0 +1,40 @@ + +import { Action, CustomActions } from './customAction'; + +import { CommandResult } from '../util/commonUtil'; +import { logger } from '../util/logger'; + + +export class SymbolRefAction implements Action { + name: string; + description: string; + type: string[]; + action: string; + handler: string[]; + args: { "name": string, "description": string, "type": string, "as"?: string, "from": string }[]; + + constructor() { + this.name = 'symbol_ref'; + this.description = 'Retrieve the reference information related to the symbol'; + this.type = ['symbol']; + this.action = 'symbol_ref'; + this.handler = []; + this.args = [ + {"name": "symbol", "description": "The symbol variable specifies the symbol for which reference information is to be retrieved.", "type": "string", "from": "content.content.symbol"}, + ]; + } + + async handlerAction(args: {[key: string]: any}): Promise { + try { + const symbolName = args.symbol; + + // get reference information + + return {exitCode: 0, stdout: "", stderr: ""}; + } catch (error) { + logger.channel()?.error(`${this.name} handle error: ${error}`); + logger.channel()?.show(); + return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`}; + } + } +}; \ No newline at end of file diff --git a/src/handler/codeFileApply.ts b/src/handler/codeFileApply.ts index 48e2882..75d5505 100644 --- a/src/handler/codeFileApply.ts +++ b/src/handler/codeFileApply.ts @@ -1,6 +1,18 @@ import * as vscode from 'vscode'; import { regInMessage, regOutMessage } from '../util/reg_messages'; +import ActionManager from '../action/actionManager'; +import { MessageHandler } from './messageHandler'; +import { sendMessage } from './sendMessage'; +import { logger } from '../util/logger'; +function compressText(text: string, maxLength: number): string { + if (text.length <= maxLength) { + return text; + } + + const halfLength = Math.floor(maxLength / 2); + return text.slice(0, halfLength) + " ... " + text.slice(-halfLength); +} async function replaceFileContent(uri: vscode.Uri, newContent: string) { try { @@ -61,8 +73,22 @@ export async function applyCodeFile(text: string, fileName: string): Promise { - await applyCodeFile(message.content, message.fileName); - return; + // await applyCodeFile(message.content, message.fileName); + // return; + + try { + const result = await ActionManager.getInstance().applyAction("command_run", { "command": "", "fileName": message.fileName, "content": message.content }); + + // send error message to devchat + const commandObj = JSON.parse(message.content) + const newMessage = `{"exit_code": ${result.exitCode}, stdout: ${result.stdout}, stderr: ${compressText(result.stderr, 100)}}`; + MessageHandler.sendMessage(panel, { "command": "sendMessageSystem" }); + sendMessage({command: 'sendMessage', text: newMessage}, panel, commandObj.name); + + } catch (error) { + logger.channel()?.error('Failed to parse code file content: ' + error); + logger.channel()?.show(); + } } diff --git a/src/handler/sendMessage.ts b/src/handler/sendMessage.ts index 85c804c..6be6fec 100644 --- a/src/handler/sendMessage.ts +++ b/src/handler/sendMessage.ts @@ -15,12 +15,12 @@ regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', dat // return message: // { command: 'receiveMessage', text: 'xxxx', hash: 'xxx', user: 'xxx', date: 'xxx'} // { command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', date: 'xxx'} -export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise { - _lastMessage = message; +export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView, function_name: string|undefined = undefined): Promise { + _lastMessage = [message, function_name]; - const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => { + const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => { MessageHandler.sendMessage(panel, data, false); - }); + }, function_name); if (responseMessage) { MessageHandler.sendMessage(panel, responseMessage); } @@ -31,7 +31,7 @@ regInMessage({command: 'regeneration'}); export async function regeneration(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise { // call sendMessage to send last message again if (_lastMessage) { - sendMessage(_lastMessage, panel); + sendMessage(_lastMessage[0], panel, _lastMessage[1]); } } diff --git a/src/handler/sendMessageBase.ts b/src/handler/sendMessageBase.ts index ca4b2d0..50436dc 100644 --- a/src/handler/sendMessageBase.ts +++ b/src/handler/sendMessageBase.ts @@ -102,6 +102,7 @@ export async function parseMessageAndSetOptions(message: any, chatOptions: any): if (parsedMessage.reference.length > 0) { chatOptions.reference = parsedMessage.reference; } + return parsedMessage; } @@ -134,14 +135,17 @@ export async function handlerResponseText(partialDataText: string, chatResponse: return undefined; } } + + if (chatResponse.finish_reason === "function_call") { + return '\n```command\n' + responseText + '\n```\n'; + } return responseText; } // 重构后的sendMessage函数 -export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> { +export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void, function_name: string| undefined = undefined): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> { userStop = false; - const chatOptions: any = {}; const parsedMessage = await parseMessageAndSetOptions(message, chatOptions); @@ -150,9 +154,18 @@ export async function sendMessageBase(message: any, handlePartialData: (data: { } logger.channel()?.info(`parent hash: ${chatOptions.parent}`); + chatOptions.functions = "./.chat/functions.json"; + if (function_name) { + chatOptions.function_name = function_name; + chatOptions.role = "function"; + } + let partialDataText = ''; const onData = (partialResponse: ChatResponse) => { partialDataText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg"); + if (partialResponse.finish_reason === "function_call") { + partialDataText = '\n```command\n' + partialDataText + '\n```\n'; + } handlePartialData({ command: 'receiveMessagePartial', text: partialDataText!, user: partialResponse.user, date: partialResponse.date }); }; diff --git a/src/panel/devchatView.ts b/src/panel/devchatView.ts index 5b59be4..d5822fd 100644 --- a/src/panel/devchatView.ts +++ b/src/panel/devchatView.ts @@ -39,8 +39,8 @@ export class DevChatViewProvider implements vscode.WebviewViewProvider { ChatContextManager.getInstance().loadCustomContexts(workflowsDir); ActionManager.getInstance().loadCustomActions(workflowsDir); - const actionInstrucFile = path.join(UiUtilWrapper.workspaceFoldersFirstPath()!, './.chat/action.instr'); - CustomActions.getInstance().saveActionInstructionFile( actionInstrucFile); + const actionInstrucFile = path.join(UiUtilWrapper.workspaceFoldersFirstPath()!, './.chat/functions.json'); + ActionManager.getInstance().saveActionInstructionFile( actionInstrucFile); } this._view = webviewView; diff --git a/src/toolwrapper/devchat.ts b/src/toolwrapper/devchat.ts index 0dcc57c..6eccff3 100644 --- a/src/toolwrapper/devchat.ts +++ b/src/toolwrapper/devchat.ts @@ -19,6 +19,9 @@ export interface ChatOptions { parent?: string; reference?: string[]; header?: string[]; + functions?: string; + role?: string; + function_name?: string; context?: string[]; } @@ -46,6 +49,7 @@ export interface ChatResponse { user: string; date: string; response: string; + finish_reason: string; isError: boolean; } @@ -80,6 +84,18 @@ class DevChat { } } + if (options.functions) { + args.push("-f", options.functions); + } + + if (options.role) { + args.push("-R", options.role); + } + + if (options.function_name) { + args.push("-n", options.function_name); + } + if (options.parent) { args.push("-p", options.parent); } @@ -96,6 +112,7 @@ class DevChat { user: "", date: "", response: "", + finish_reason: "", isError: isPartial ? false : true, }; } @@ -116,16 +133,27 @@ class DevChat { } } + let finishReasonLine = ""; + for (let i = responseLines.length - 1; i >= 0; i--) { + if (responseLines[i].startsWith("finish_reason:")) { + finishReasonLine = responseLines[i]; + responseLines.splice(i, 1); + break; + } + } + if (!promptHashLine) { return { "prompt-hash": "", user: user, date: date, response: responseLines.join("\n"), + finish_reason: "", isError: isPartial ? false : true, }; } + const finishReason = finishReasonLine.split(" ")[1]; const promptHash = promptHashLine.split(" ")[1]; const response = responseLines.join("\n"); @@ -134,6 +162,7 @@ class DevChat { user, date, response, + finish_reason: finishReason, isError: false, }; } @@ -218,6 +247,7 @@ class DevChat { user: "", date: "", response: stderr, + finish_reason: "", isError: true, }; } @@ -230,6 +260,7 @@ class DevChat { user: "", date: "", response: `Error: ${error.stderr}\nExit code: ${error.code}`, + finish_reason: "", isError: true, }; } diff --git a/workflows/auto_command/action/run_shell_file/_setting_.json b/workflows/auto_command/action/run_shell_file/_setting_.json index 4ce7170..ebb8e64 100644 --- a/workflows/auto_command/action/run_shell_file/_setting_.json +++ b/workflows/auto_command/action/run_shell_file/_setting_.json @@ -1,5 +1,5 @@ { - "name": "run_shell", + "name": "run_shell_file", "description": "run shell script", "type": [ "shell" diff --git a/workflows/default/command/action/_setting_.json b/workflows/default/command/action/_setting_.json deleted file mode 100644 index 2ab6005..0000000 --- a/workflows/default/command/action/_setting_.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "pattern": "auto", - "description": "Generate commands for the bot to execute", - "message": "", - "default": false, - "instructions": ["./chat/action.instr"] -} \ No newline at end of file