chore: Remove unused endpoint "/get_symbol_defines_in_selected_code" and related code
This commit is contained in:
parent
3fa139ff91
commit
dad1f10513
@ -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;
|
||||
},
|
||||
};
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user