chore: Remove unused endpoint "/get_symbol_defines_in_selected_code" and related code

This commit is contained in:
bobo.yang 2024-07-16 07:57:13 +08:00
parent 3fa139ff91
commit dad1f10513
4 changed files with 0 additions and 905 deletions

View File

@ -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<string> {
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<string> {
if (!activeEditor) {
return "";
}
const document = activeEditor.document;
return document.getText();
}
// TODO: 这里还有用吗如果没用就把这里以及相关代码删除如果有用就用DevChatClient替换这里对DevChat的使用
async function getUndefinedSymbols(content: string): Promise<string[] | undefined> {
// 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<string[]> {
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<string> = new Set();
let hasPushedSymbols: Set<string> = 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.Location[] | vscode.LocationLink[]>(
'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.DocumentSymbol[]>(
'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;
},
};

View File

@ -1,6 +1,4 @@
import * as vscode from "vscode";
import { applyCodeWithDiff, applyEditCodeWithDiff } from "../../handler/diffHandler"; import { applyCodeWithDiff, applyEditCodeWithDiff } from "../../handler/diffHandler";
import { getSymbolDefines } from "../../context/contextRefDefs";
export namespace UnofficialEndpoints { export namespace UnofficialEndpoints {
@ -13,16 +11,6 @@ export namespace UnofficialEndpoints {
return true; 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) { export async function runCode(code: string) {
// run code // run code
// delcare use vscode // delcare use vscode

View File

@ -81,10 +81,6 @@ const functionRegistry: any = {
/** /**
* Unofficial endpoints * Unofficial endpoints
*/ */
"/get_symbol_defines_in_selected_code": {
keys: [],
handler: UnofficialEndpoints.getSymbolDefinesInSelectedCode,
},
"/run_code": { "/run_code": {
keys: ["code"], keys: ["code"],
handler: UnofficialEndpoints.runCode, handler: UnofficialEndpoints.runCode,

View File

@ -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<string[]> {
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<string[]> {
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<ChatResponse> {
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<LogEntry[]> {
try {
// build log data
const llmModelData = await ApiKeyManager.llmModel();
const contextContentList = await this.loadContextsFromFiles(contexts);
const contextWithRoleList = contextContentList.map(content => {
return {
"role": "system",
"content": `<context>${content}</context>`
};
});
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<boolean> {
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<LogEntry[]> {
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<string[]> {
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<any[]> {
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<string> {
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<TopicEntry[]> {
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;