From dad1f10513f845493ee1c3f10be541a4d841d82c Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Tue, 16 Jul 2024 07:57:13 +0800 Subject: [PATCH] chore: Remove unused endpoint "/get_symbol_defines_in_selected_code" and related code --- src/context/contextRefDefs.ts | 341 -------------- src/ide_services/endpoints/unofficial.ts | 12 - src/ide_services/services.ts | 4 - src/toolwrapper/devchat.ts | 548 ----------------------- 4 files changed, 905 deletions(-) delete mode 100644 src/context/contextRefDefs.ts delete mode 100644 src/toolwrapper/devchat.ts diff --git a/src/context/contextRefDefs.ts b/src/context/contextRefDefs.ts deleted file mode 100644 index 5c197f7..0000000 --- a/src/context/contextRefDefs.ts +++ /dev/null @@ -1,341 +0,0 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; - -import { ChatContext } from './contextManager'; - -import { logger } from '../util/logger'; -import { handleCodeSelected } from './contextCodeSelected'; -import DevChat, { ChatOptions } from '../toolwrapper/devchat'; -import { UiUtilWrapper } from '../util/uiUtil'; - - -async function getCurrentSelectText(activeEditor: vscode.TextEditor): Promise { - if (!activeEditor) { - return ""; - } - - const document = activeEditor.document; - const selection = activeEditor.selection; - const selectedText = document.getText(selection); - - return selectedText; -} - -// get full text in activeEditor -async function getFullText(activeEditor: vscode.TextEditor): Promise { - if (!activeEditor) { - return ""; - } - - const document = activeEditor.document; - return document.getText(); -} - -// TODO: 这里还有用吗?如果没用,就把这里以及相关代码删除;如果有用就用DevChatClient替换这里对DevChat的使用 -async function getUndefinedSymbols(content: string): Promise { - // run devchat prompt command - const devChat = new DevChat(); - const chatOptions: ChatOptions = {}; - - const onData = (partialResponse) => { }; - const newContent = ` - As a software developer skilled in code analysis, your goal is to examine and understand a provided code snippet. - The code may include various symbols, encompassing both variables and functions. - However, the snippet doesn't include the definitions of some of these symbols. - Now your specific task is to identify and list all such symbols whose definitions are missing but are essential for comprehending the entire code. - This will help in fully grasping the behavior and purpose of the code. Note that the code snippet in question could range from a few lines to a singular symbol. - - Response is json string, don't include any other output except the json object. The json object should be an array of strings, each string is a symbol name: - \`\`\`json - ["f1", "f2"] - \`\`\` - - During this process, you cannot invoke the GPT function. The code snippet is as follows: \n\`\`\`` + content + '```'; ; - - const chatResponse = await devChat.chat(newContent, chatOptions, onData, false); - if (chatResponse && chatResponse.response) { - logger.channel()?.info(chatResponse.response); - } - - // parse data in chatResponse.response - // data format as: - // ```json {data} ``` - // or [data] - // or plain text - // so, parse data between ```json and ``` or directly parse the array, or return an empty array - if (!chatResponse || !chatResponse.response) { - return []; - } - - let responseText = chatResponse.response.trim(); - let symbols: string[]; - - const indexBlock = responseText.indexOf('```'); - if (indexBlock !== -1) { - const indexJsonEnd = responseText.indexOf('```', indexBlock+3); - if (indexJsonEnd !== -1) { - responseText = responseText.substring(indexBlock, indexJsonEnd + 3); - } - } - - if (responseText.startsWith("```") && responseText.endsWith("```")) { - const index = responseText.indexOf('['); - responseText = responseText.substring(index, responseText.length - 3); - try { - symbols = JSON.parse(responseText); - } catch (error) { - return undefined; - } - } else { - try { - symbols = JSON.parse(responseText); - } catch (error) { - return undefined; - } - } - - logger.channel()?.info(`getUndefinedSymbols: ${chatResponse.response}`); - return symbols; -} - -function matchSymbolInline(line: string, symbol: string): number[] { - const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - - // Create a RegExp with the escaped symbol and word boundaries - const regex = new RegExp(`\\b${escapedSymbol}\\b`, 'gu'); - - // Find the match in the selected text - const matches = [...line.matchAll(regex)]; - - // If the symbol is found - if (matches.length === 0) { - return []; - } - - return matches.map((match) => match.index!); -} - -function getMatchedSymbolPositions(selectText: string, symbol: string): object[] { - const lines = selectText.split('\n'); - const positions: object[] = []; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const matchedPositions = matchSymbolInline(line, symbol); - for (const pos of matchedPositions) { - positions.push({ - line: i, - character: pos - }); - } - } - return positions; -} - -function isLocationInstance(loc: vscode.Location | vscode.LocationLink): boolean { - try { - return (loc as vscode.Location).uri !== undefined; - } catch (error) { - return false; - } -} - -async function handleCodeSelectedNoFile(fileSelected: string, codeSelected: string, startLine: number) { - const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath(); - const relativePath = path.relative(workspaceDir!, fileSelected); - - const data = { - path: relativePath, - startLine: startLine, - content: codeSelected - }; - return data; -} - -async function getSymbolDefine(symbolList: string[], activeEditor: vscode.TextEditor, toFile: boolean = true): Promise { - const document = activeEditor!.document; - let selection = activeEditor!.selection; - let selectedText = document!.getText(selection); - if (selectedText === "" && !toFile) { - selectedText = await getFullText(activeEditor); - selection = new vscode.Selection(0, 0, 0, 0); - } - - let contextList: any[] = []; - if (toFile) { - contextList = [await handleCodeSelectedNoFile(document.uri.fsPath, selectedText, selection.start.line)]; - } - let hasVisitedSymbols: Set = new Set(); - let hasPushedSymbols: Set = new Set(); - - // visit each symbol in symbolList, and get it's define - for (const symbol of symbolList) { - logger.channel()?.info(`handle symble: ${symbol} ...`); - // get symbol position in selectedText - // if selectedText is "abc2+abc", symbol is "abc", then symbolPosition is 5 not 0 - // because abc2 is not a symbol - const positions: any[] = getMatchedSymbolPositions(selectedText, symbol); - - for (const pos of positions) { - const symbolPosition = pos.character; - - // if symbol is like a.b.c, then split it to a, b, c - const symbolSplit = symbol.split("."); - let curPosition = 0; - for (const symbolSplitItem of symbolSplit) { - const symbolPositionNew = symbol.indexOf(symbolSplitItem, curPosition) + symbolPosition; - curPosition = symbolPositionNew - symbolPosition + symbolSplitItem.length; - const newPos = new vscode.Position(pos.line + selection.start.line, (pos.line > 0 ? 0 : selection.start.character) + symbolPositionNew); - logger.channel()?.info(`handle sub symble: ${symbolSplitItem} at ${newPos.line}:${newPos.character}`); - - try{ - // call vscode.executeDefinitionProvider - const refLocations = await vscode.commands.executeCommand( - 'vscode.executeDefinitionProvider', - document.uri, - newPos - ); - if (!refLocations) { - logger.channel()?.info(`no def location for ${symbolSplitItem} at ${newPos.line}:${newPos.character}`); - } - - // visit each refLocation, and get it's define - for (const refLocation of refLocations) { - let targetUri: vscode.Uri | undefined = undefined; - let targetRange: vscode.Range | undefined = undefined; - - if (isLocationInstance(refLocation)) { - const locLocation = refLocation as vscode.Location; - targetUri = locLocation.uri; - targetRange = locLocation.range; - } else { - const locLocationLink = refLocation as vscode.LocationLink; - targetUri = locLocationLink.targetUri; - targetRange = locLocationLink.targetRange; - } - if (!targetUri ||!targetRange) { - continue; - } - - logger.channel()?.info(`def location: ${targetUri.fsPath} ${targetRange.start.line}:${targetRange.start.character}-${targetRange.end.line}:${targetRange.end.character}`); - const refLocationString = targetUri.fsPath + "-" + targetRange.start.line + ":" + targetRange.start.character + "-" + targetRange.end.line + ":" + targetRange.end.character; - if (hasVisitedSymbols.has(refLocationString)) { - continue; - } - hasVisitedSymbols.add(refLocationString); - - // get defines in refLocation file - const symbolsT: vscode.DocumentSymbol[] = await vscode.commands.executeCommand( - 'vscode.executeDocumentSymbolProvider', - targetUri - ); - - let targetSymbol: any = undefined; - const visitFun = (symbol: vscode.DocumentSymbol) => { - if (targetRange!.start.isAfterOrEqual(symbol.range.start) && targetRange!.end.isBeforeOrEqual(symbol.range.end)) { - targetSymbol = symbol; - } - - if (targetRange!.start.isAfter(symbol.range.end) || targetRange!.end.isBefore(symbol.range.start)) { - return; - } - - for (const child of symbol.children) { - visitFun(child); - } - }; - for (const symbol of symbolsT) { - visitFun(symbol); - } - - if (targetSymbol !== undefined) { - logger.channel()?.info(`symbol define information: ${targetSymbol.name} at ${targetSymbol.location.uri.fsPath} ${targetSymbol.location.range.start.line}:${targetSymbol.location.range.start.character}-${targetSymbol.location.range.end.line}:${targetSymbol.location.range.end.character}`); - const defLocationString = targetSymbol.location.uri.fsPath + "-" + targetSymbol.location.range.start.line + ":" + targetSymbol.location.range.start.character + "-" + targetSymbol.location.range.end.line + ":" + targetSymbol.location.range.end.character; - if (hasPushedSymbols.has(defLocationString)) { - continue; - } - hasPushedSymbols.add(defLocationString); - - const documentNew = await vscode.workspace.openTextDocument(targetUri); - - if (targetSymbol.kind === vscode.SymbolKind.Variable) { - const renageNew = new vscode.Range(targetSymbol.range.start.line, 0, targetSymbol.range.end.line, 10000); - if (toFile) { - contextList.push(await handleCodeSelected(targetUri.fsPath, documentNew.getText(renageNew), targetSymbol.range.start.line)); - } else { - contextList.push(await handleCodeSelectedNoFile(targetUri.fsPath, documentNew.getText(renageNew), targetSymbol.range.start.line)); - } - } else { - if (toFile) { - contextList.push(await handleCodeSelected(targetUri.fsPath, documentNew.getText(targetSymbol.range), targetSymbol.range.start.line)); - } else { - contextList.push(await handleCodeSelectedNoFile(targetUri.fsPath, documentNew.getText(targetSymbol.range), targetSymbol.range.start.line)); - } - } - } - } - } catch (error) { - logger.channel()?.error(`getSymbolDefine error: ${error}`); - } - } - } - } - return contextList; -} - -export async function getSymbolDefines() { - const activeEditor = vscode.window.activeTextEditor; - - if (!activeEditor) { - logger.channel()?.error('No code selected!'); - logger.channel()?.show(); - return []; - } - - let selectedText = await getCurrentSelectText(activeEditor); - if (selectedText === "") { - selectedText = await getFullText(activeEditor); - } - if (selectedText === "") { - logger.channel()?.error(`No code selected! Current selected editor is: ${activeEditor.document.uri.fsPath}}`); - logger.channel()?.show(); - return []; - } - const symbolList = await getUndefinedSymbols(selectedText); - if (symbolList === undefined) { - logger.channel()?.error('Failed to get symbol list!'); - logger.channel()?.show(); - return []; - } - const contextList = await getSymbolDefine(symbolList, activeEditor, false); - return contextList; -} - -export const refDefsContext: ChatContext = { - name: 'symbol definitions', - description: 'find related definitions of classes, functions, etc. in selected code', - handler: async () => { - const activeEditor = vscode.window.activeTextEditor; - - if (!activeEditor) { - logger.channel()?.error('No code selected!'); - logger.channel()?.show(); - return []; - } - - const selectedText = await getCurrentSelectText(activeEditor); - if (selectedText === "") { - logger.channel()?.error(`No code selected! Current selected editor is: ${activeEditor.document.uri.fsPath}}`); - logger.channel()?.show(); - return []; - } - const symbolList = await getUndefinedSymbols(selectedText); - if (symbolList === undefined) { - logger.channel()?.error('Failed to get symbol list!'); - logger.channel()?.show(); - return []; - } - const contextList = await getSymbolDefine(symbolList, activeEditor); - - return contextList; - }, -}; diff --git a/src/ide_services/endpoints/unofficial.ts b/src/ide_services/endpoints/unofficial.ts index 3bf29a9..750b89e 100644 --- a/src/ide_services/endpoints/unofficial.ts +++ b/src/ide_services/endpoints/unofficial.ts @@ -1,6 +1,4 @@ -import * as vscode from "vscode"; import { applyCodeWithDiff, applyEditCodeWithDiff } from "../../handler/diffHandler"; -import { getSymbolDefines } from "../../context/contextRefDefs"; export namespace UnofficialEndpoints { @@ -13,16 +11,6 @@ export namespace UnofficialEndpoints { return true; } - export async function getSymbolDefinesInSelectedCode() { - // find needed symbol defines in current editor document - // return value is a list of symbol defines - // each define has three fileds: - // path: file path contain that symbol define - // startLine: start line in that file - // content: source code for symbol define - return getSymbolDefines(); - } - export async function runCode(code: string) { // run code // delcare use vscode diff --git a/src/ide_services/services.ts b/src/ide_services/services.ts index d23f28a..f70f81f 100644 --- a/src/ide_services/services.ts +++ b/src/ide_services/services.ts @@ -81,10 +81,6 @@ const functionRegistry: any = { /** * Unofficial endpoints */ - "/get_symbol_defines_in_selected_code": { - keys: [], - handler: UnofficialEndpoints.getSymbolDefinesInSelectedCode, - }, "/run_code": { keys: ["code"], handler: UnofficialEndpoints.runCode, diff --git a/src/toolwrapper/devchat.ts b/src/toolwrapper/devchat.ts deleted file mode 100644 index 4089c27..0000000 --- a/src/toolwrapper/devchat.ts +++ /dev/null @@ -1,548 +0,0 @@ - -// TODO: 确认所有DevChat()实例被替换后(contextRefDefs.js里),删除此文件 - -import * as dotenv from 'dotenv'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as os from 'os'; - -import { logger } from '../util/logger'; -import { CommandRun } from "../util/commonUtil"; -import { UiUtilWrapper } from '../util/uiUtil'; -import { ApiKeyManager } from '../util/apiKey'; -import { assertValue } from '../util/check'; -import { getFileContent } from '../util/commonUtil'; -import * as toml from '@iarna/toml'; -import { DevChatConfig } from '../util/config'; - -import { getMicromambaUrl } from '../util/python_installer/conda_url'; - -const readFileAsync = fs.promises.readFile; - -const envPath = path.join(__dirname, '..', '.env'); -dotenv.config({ path: envPath }); - -export interface ChatOptions { - parent?: string; - reference?: string[]; - header?: string[]; - context?: string[]; -} - -export interface LogOptions { - skip?: number; - maxCount?: number; - topic?: string; -} - -export interface LogEntry { - hash: string; - parent: string; - user: string; - date: string; - request: string; - response: string; - context: Array<{ - content: string; - role: string; - }>; -} - -export interface CommandEntry { - name: string; - description: string; - path: string; - // default value is -1, which means not recommended - recommend: number; -} - -export interface TopicEntry { - // eslint-disable-next-line @typescript-eslint/naming-convention - root_prompt: LogEntry; - // eslint-disable-next-line @typescript-eslint/naming-convention - latest_time: number; - hidden: boolean; - title: string | null; -} - -export interface ChatResponse { - // eslint-disable-next-line @typescript-eslint/naming-convention - "prompt-hash": string; - user: string; - date: string; - response: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - finish_reason: string; - isError: boolean; -} - - -class DevChat { - private commandRun: CommandRun; - - constructor() { - this.commandRun = new CommandRun(); - } - - private async loadContextsFromFiles(contexts: string[] | undefined): Promise { - if (!contexts) { - return []; - } - - const loadedContexts: string[] = []; - for (const context of contexts) { - const contextContent = await getFileContent(context); - if (!contextContent) { - continue; - } - - loadedContexts.push(contextContent); - } - return loadedContexts; - } - - private async buildArgs(options: ChatOptions): Promise { - let args = ["-m", "devchat", "route"]; - - if (options.reference) { - for (const reference of options.reference) { - args.push("-r", reference); - } - } - if (options.header) { - for (const header of options.header) { - args.push("-i", header); - } - } - if (options.context) { - for (const context of options.context) { - args.push("-c", context); - } - } - - if (options.parent) { - args.push("-p", options.parent); - } - - const llmModelData = await ApiKeyManager.llmModel(); - assertValue(!llmModelData || !llmModelData.model, 'You must select a LLM model to use for conversations'); - args.push("-m", llmModelData.model); - - const functionCalling = DevChatConfig.getInstance().get('enable_function_calling'); - if (functionCalling) { - args.push("-a"); - } - - return args; - } - - private buildLogArgs(options: LogOptions): string[] { - let args = ["-m", "devchat", "log"]; - - if (options.skip) { - args.push('--skip', `${options.skip}`); - } - if (options.maxCount) { - args.push('--max-count', `${options.maxCount}`); - } else { - const maxLogCount = DevChatConfig.getInstance().get('max_log_count'); - args.push('--max-count', `${maxLogCount}`); - } - - if (options.topic) { - args.push('--topic', `${options.topic}`); - } - - return args; - } - - private parseOutData(stdout: string, isPartial: boolean): ChatResponse { - const responseLines = stdout.trim().split("\n"); - - if (responseLines.length < 2) { - return this.createChatResponse("", "", "", "", !isPartial); - } - - const [userLine, remainingLines1] = this.extractLine(responseLines, "User: "); - const user = this.parseLine(userLine, /User: (.+)/); - - const [dateLine, remainingLines2] = this.extractLine(remainingLines1, "Date: "); - const date = this.parseLine(dateLine, /Date: (.+)/); - - const [promptHashLine, remainingLines3] = this.extractLine(remainingLines2, "prompt"); - const [finishReasonLine, remainingLines4] = this.extractLine(remainingLines3, "finish_reason:"); - - if (!promptHashLine) { - return this.createChatResponse("", user, date, remainingLines4.join("\n"), !isPartial); - } - - const finishReason = finishReasonLine.split(" ")[1]; - const promptHash = promptHashLine.split(" ")[1]; - const response = remainingLines4.join("\n"); - - return this.createChatResponse(promptHash, user, date, response, false, finishReason); - } - - private extractLine(lines: string[], startWith: string): [string, string[]] { - const index = lines.findIndex(line => line.startsWith(startWith)); - const extractedLine = index !== -1 ? lines.splice(index, 1)[0] : ""; - return [extractedLine, lines]; - } - - private parseLine(line: string, regex: RegExp): string { - return (line.match(regex)?.[1]) ?? ""; - } - - private createChatResponse(promptHash: string, user: string, date: string, response: string, isError: boolean, finishReason = ""): ChatResponse { - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - "prompt-hash": promptHash, - user, - date, - response, - // eslint-disable-next-line @typescript-eslint/naming-convention - finish_reason: finishReason, - isError, - }; - } - - private async runCommand(args: string[]): Promise<{code: number | null, stdout: string, stderr: string}> { - // build env variables for command - const envs = { - ...process.env, - // eslint-disable-next-line @typescript-eslint/naming-convention - "PYTHONUTF8":1, - // eslint-disable-next-line @typescript-eslint/naming-convention - "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages", - // eslint-disable-next-line @typescript-eslint/naming-convention - "DEVCHAT_PROXY": DevChatConfig.getInstance().get('DEVCHAT_PROXY') || "", - "MAMBA_BIN_PATH": getMicromambaUrl(), - }; - - const pythonApp = DevChatConfig.getInstance().get('python_for_chat') || "python3"; - - // run command - const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync( - pythonApp, - args, - { - maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB - cwd: UiUtilWrapper.workspaceFoldersFirstPath(), - env: envs - }, - undefined, undefined, undefined, undefined - ); - - return {code, stdout, stderr}; - } - - public input(data: string) { - this.commandRun?.write(data + "\n"); - } - - public stop() { - this.commandRun.stop(); - } - - async chat(content: string, options: ChatOptions = {}, onData: (data: ChatResponse) => void, saveToLog: boolean = true): Promise { - try { - // build args for devchat prompt command - const args = await this.buildArgs(options); - args.push("--"); - args.push(content); - - // build env variables for prompt command - const llmModelData = await ApiKeyManager.llmModel(); - assertValue(!llmModelData, "No valid llm model selected"); - const envs = { - ...process.env, - // eslint-disable-next-line @typescript-eslint/naming-convention - "PYTHONUTF8": 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - "command_python": DevChatConfig.getInstance().get('python_for_commands') || "", - // eslint-disable-next-line @typescript-eslint/naming-convention - "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages", - // eslint-disable-next-line @typescript-eslint/naming-convention - "OPENAI_API_KEY": llmModelData.api_key.trim(), - "DEVCHAT_UNIT_TESTS_USE_USER_MODEL": 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - ...llmModelData.api_base? { "OPENAI_API_BASE": llmModelData.api_base, "OPENAI_BASE_URL": llmModelData.api_base } : {}, - "DEVCHAT_PROXY": DevChatConfig.getInstance().get('DEVCHAT_PROXY') || "", - "MAMBA_BIN_PATH": getMicromambaUrl(), - }; - - // build process options - const spawnAsyncOptions = { - maxBuffer: 10 * 1024 * 1024, // Set maxBuffer to 10 MB - cwd: UiUtilWrapper.workspaceFoldersFirstPath(), - env: envs - }; - - logger.channel()?.info(`api_key: ${llmModelData.api_key.replace(/^(.{4})(.*)(.{4})$/, (_, first, middle, last) => first + middle.replace(/./g, '*') + last)}`); - logger.channel()?.info(`api_base: ${llmModelData.api_base}`); - - // run command - // handle stdout as steam mode - let receviedStdout = ""; - const onStdoutPartial = (stdout: string) => { - receviedStdout += stdout; - const data = this.parseOutData(receviedStdout, true); - onData(data); - }; - // run command - const pythonApp = DevChatConfig.getInstance().get('python_for_chat') || "python3"; - logger.channel()?.info(`Running devchat:${pythonApp} ${args.join(" ")}`); - const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(pythonApp, args, spawnAsyncOptions, onStdoutPartial, undefined, undefined, undefined); - // handle result - assertValue(code !== 0, stderr || "Command exited with error code"); - const responseData = this.parseOutData(stdout, false); - let promptHash = ""; - if (saveToLog) { - const logs = await this.logInsert(options.context, content, responseData.response, options.parent); - assertValue(!logs || !logs.length, "Failed to insert devchat log"); - promptHash = logs[0]["hash"]; - } - // return result - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - "prompt-hash": promptHash, - user: "", - date: "", - response: responseData.response, - // eslint-disable-next-line @typescript-eslint/naming-convention - finish_reason: "", - isError: false, - }; - } catch (error: any) { - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - "prompt-hash": "", - user: "", - date: "", - response: `Error: ${error.message}`, - // eslint-disable-next-line @typescript-eslint/naming-convention - finish_reason: "error", - isError: true, - }; - } - } - - async logInsert(contexts: string[] | undefined, request: string, response: string, parent: string | undefined): Promise { - try { - // build log data - const llmModelData = await ApiKeyManager.llmModel(); - const contextContentList = await this.loadContextsFromFiles(contexts); - const contextWithRoleList = contextContentList.map(content => { - return { - "role": "system", - "content": `${content}` - }; - }); - - let logData = { - "model": llmModelData?.model || "gpt-3.5-turbo", - "messages": [ - { - "role": "user", - "content": request - }, - { - "role": "assistant", - "content": response - }, - ...contextWithRoleList - ], - "timestamp": Math.floor(Date.now()/1000), - // eslint-disable-next-line @typescript-eslint/naming-convention - "request_tokens": 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - "response_tokens": 1, - ...parent? {"parent": parent} : {} - }; - const insertValue = JSON.stringify(logData); - let insertValueOrFile = insertValue; - if (insertValue.length > 4 * 1024) { - const tempDir = os.tmpdir(); - const tempFile = path.join(tempDir, 'devchat_log_insert.json'); - await fs.promises.writeFile(tempFile, insertValue); - insertValueOrFile = tempFile; - } - - // build args for log insert - const args = ["-m", "devchat", "log", "--insert", insertValueOrFile]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - assertValue(stdout.indexOf('Failed to insert log') >= 0, stdout); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - const logs = JSON.parse(stdout.trim()).reverse(); - for (const log of logs) { - log.response = log.responses[0]; - delete log.responses; - } - return logs; - } catch (error: any) { - logger.channel()?.error(`Failed to insert log: ${error.message}`); - logger.channel()?.show(); - return []; - } - } - - async delete(hash: string): Promise { - try { - // build args for log delete - const args = ["-m", "devchat", "log", "--delete", hash]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - assertValue(stdout.indexOf('Failed to delete prompt') >= 0, stdout); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - return true; - } catch (error: any) { - logger.channel()?.error(`Failed to delete log: ${error.message}`); - logger.channel()?.show(); - return false; - } - } - - async log(options: LogOptions = {}): Promise { - try { - const args = this.buildLogArgs(options); - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - const logs = JSON.parse(stdout.trim()).reverse(); - for (const log of logs) { - log.response = log.responses[0]; - delete log.responses; - } - return logs; - } catch (error: any) { - logger.channel()?.error(`Failed to get logs: ${error.message}`); - logger.channel()?.show(); - return []; - } - } - - async loadRecommendCommands(): Promise { - try { - const args = ["-m", "devchat", "workflow", "config", "--json"]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - let workflowConfig; - try { - workflowConfig = JSON.parse(stdout.trim()); - } catch (error) { - logger.channel()?.error('Failed to parse commands JSON:', error); - return []; - } - - return workflowConfig.recommend?.workflows || []; - } catch (error: any) { - logger.channel()?.error(`Error: ${error.message}`); - logger.channel()?.show(); - return []; - } - } - - async commands(): Promise { - try { - const args = ["-m", "devchat", "workflow", "list", "--json"]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - let commands; - try { - commands = JSON.parse(stdout.trim()); - } catch (error) { - logger.channel()?.error('Failed to parse commands JSON:', error); - return []; - } - - // 确保每个CommandEntry对象的recommend字段默认为-1 - const recommendCommands = await this.loadRecommendCommands(); - commands = commands.map((cmd: any) => ({ - ...cmd, - recommend: recommendCommands.indexOf(cmd.name), - })); - - return commands; - } catch (error: any) { - logger.channel()?.error(`Error: ${error.message}`); - logger.channel()?.show(); - return []; - } - } - - async updateSysCommand(): Promise { - try { - const args = ["-m", "devchat", "workflow", "update"]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - logger.channel()?.trace(`${stdout}`); - return stdout; - } catch (error: any) { - logger.channel()?.error(`Error: ${error.message}`); - logger.channel()?.show(); - return ""; - } - } - - async topics(): Promise { - try { - const args = ["-m", "devchat", "topic", "-l"]; - - const {code, stdout, stderr} = await this.runCommand(args); - - assertValue(code !== 0, stderr || `Command exited with ${code}`); - if (stderr.trim() !== "") { - logger.channel()?.warn(`${stderr}`); - } - - const topics = JSON.parse(stdout.trim()).reverse(); - for (const topic of topics) { - if (topic.root_prompt.responses) { - topic.root_prompt.response = topic.root_prompt.responses[0]; - delete topic.root_prompt.responses; - } - } - return topics; - } catch (error: any) { - logger.channel()?.error(`Error: ${error.message}`); - logger.channel()?.show(); - return []; - } - } -} - -export default DevChat;