Skip to content

Commit 491a611

Browse files
committed
refactor:整理代码,outputChannel相关
1 parent f405fd8 commit 491a611

File tree

3 files changed

+346
-3
lines changed

3 files changed

+346
-3
lines changed

prompt/agent.txt

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,3 +871,336 @@ agent的数据结构有
871871
理解我的设计,你觉得有没有不合理的地方,能否改进?帮我完善他。
872872
给出关键代码的实现。
873873

874+
----------------------------------------
875+
876+
877+
以下是一个重构多文件工程的vscode插件的相关代码(typescript)
878+
879+
export async function queryCodeReDesign(
880+
cvbContent: string,
881+
userRequest: string,
882+
outputChannel: vscode.OutputChannel,
883+
abortSignal?: AbortSignal
884+
): Promise<string | null> {
885+
const requestContent = `
886+
【格式说明】
887+
- CVB 格式说明:${Cvb.getFormatDescription()}
888+
- TCVB 格式说明:${TCVB.getFormatDescription()}
889+
890+
【任务说明】
891+
请读取以下 CVB 格式代码,并根据需求修改代码。注意:
892+
1. 如果需求涉及“移动代码”,请务必修改原始代码,将代码重新封装到新位置,而非简单复制;
893+
2. 修改后的代码必须完整、可执行,不能有任何省略;
894+
3. 输出内容必须严格遵守 TCVB 格式(仅正文部分含 TCVB 标记,其他地方如有 TCVB 开始或结束符需转义),以确保后续合并正确;
895+
4. 注意不要将某文件的修改内容误认为是其他文件,请一条一条列出具体修改项及对应文件路径。
896+
897+
【输出要求】
898+
1. 先输出你对需求及相关代码的理解,请按层级缩进列出笔记,便于整理思路;
899+
2. 再输出详细的方案大纲,格式如下:
900+
需求理解:
901+
902+
查询资料:
903+
列出每个关键修改点所在的文件路径
904+
修改方案:
905+
文件路径1:
906+
描述修改点,避免用大块代码,注意只输出关键修改,不要太长, 不要加载无用的上下文。不要输出没有改动部分的代码
907+
文件路径2:
908+
描述修改点,同上
909+
910+
最后检查:
911+
对以上输出的方案大纲进行反思,重新阅读输入代码,结合以上方案大纲,逐条检查有没有和原文对不上的地方。检查方案是否完备、文件路径是否正确,设计思路是否无误,如有问题请提出修正意见
912+
3. 请确保输出中既包含错误部分的修正说明,又完整保留原有正确部分,不得遗漏任何内容;
913+
4. 用最小改动实现需求目的。
914+
915+
【输入部分】
916+
- 输入代码:${cvbContent}
917+
- 需求描述:${userRequest}
918+
919+
【最终输出】
920+
请先输出思路与方案大纲,最后汇总输出符合 TCVB 格式的精确代码。
921+
`;
922+
923+
return callDeepSeekApi(requestContent, undefined, outputChannel, true, '## END_TCVB', abortSignal); // 添加结束字符串
924+
}
925+
926+
export class Cvb {
927+
private m_recMetadata: Record<string, string>;
928+
private m_recFiles: Record<string, string>;
929+
930+
constructor(cvbContent?: string) {
931+
this.m_recMetadata = {};
932+
this.m_recFiles = {};
933+
if (cvbContent) {
934+
const { metadata, files } = this.parse(cvbContent);
935+
this.m_recMetadata = metadata;
936+
this.m_recFiles = files;
937+
}
938+
}
939+
940+
public static getFormatDescription(): string {
941+
return `
942+
CVB 格式介绍:
943+
- 文件以 "## BEGIN_CVB" 开头,以 "## END_CVB" 结尾。
944+
- 元数据部分以 "## META" 开头,以 "## END_META" 结尾,包含用户需求和时间戳。
945+
- 每个文件以 "## FILE:文件路径" 开头,紧接着是 Markdown 格式的代码块,包含文件内容。
946+
- 多个文件按顺序拼接在一起。
947+
`;
948+
}
949+
}
950+
951+
export class TCVB {
952+
private m_arrOperations: TcvbOperation[] = [];
953+
public static getFormatDescription(): string {
954+
return `
955+
TCVB 格式规范:
956+
957+
## BEGIN_TCVB
958+
[文件块1]
959+
[文件块2]
960+
...
961+
## END_TCVB
962+
963+
文件块格式:
964+
## FILE:<文件绝对路径>
965+
[操作1]
966+
[操作2]
967+
...
968+
969+
操作类型:
970+
971+
1. 全局替换操作(GLOBAL-REPLACE):
972+
## OPERATION:GLOBAL-REPLACE
973+
## OLD_CONTENT
974+
[markdown代码块:被全局替换的内容, 可以在需要被替换的文本前后包含一些上下文帮助精确替换,一般是上下各3行。不要太长,不要带太多不必要的上下文,因为输出越长就越可能出错导致匹配不上。总长度不要超过10行,尽量不要大块的替换代码,而是切成很多小块替换。]
975+
## NEW_CONTENT
976+
[markdown代码块:新内容]
977+
978+
2. 创建操作(CREATE):
979+
## OPERATION:CREATE
980+
[markdown代码块:直接跟正文内容,表示新文件的全部内容]
981+
982+
注意:
983+
1. 所有OPERATION操作以行为单位
984+
2. 一个'## FILE'下可以有多个'## OPERATION'
985+
3. 锚点为连续的多行内容:使用至少3行唯一文本作为锚点,用来标定范围,防止混淆(如果需要可以超过3行)
986+
4. [markdown代码块], 一定要用\`\`\` ... \`\`\` 包裹,仔细检查不要漏掉。
987+
5. 注意TCVB和CVB的区别。CVB是完整的内容,而TCVB是用来生成差量同步的,通过多个OPERATION去操作已有CVB合成新CVB
988+
6. 插入和删除操作都可以转化为替换操作
989+
7. 用来匹配的锚点必须和原文的格式完全一致,不能有缺失,不能丢弃注释。
990+
8. 注意不要丢失OPERATION而直接输出代码块
991+
9. 不要私自加入不必要的空行
992+
10.如果是在一个已有文件里插入大块代码,不应该用CREATE,而是用替换的方式插入
993+
`;
994+
}
995+
}
996+
}
997+
998+
999+
/**
1000+
* 调用 DeepSeek API
1001+
* @param userContent 用户输入内容,可以是字符串或字符串数组
1002+
* @param systemContent 系统提示内容
1003+
* @param outputChannel 输出通道,用于实时显示流式内容
1004+
* @param streamMode 是否启用流式模式
1005+
* @param endstring 结束字符串,用于检查输出是否包含特定字符串
1006+
* @param abortSignal 用于中断请求的信号
1007+
* @returns API 返回的完整内容
1008+
*/
1009+
export async function callDeepSeekApi(
1010+
userContent: string | {role:string, content: string}[], // 修改为支持 string 或 string[]
1011+
systemContent: string = 'You are a helpful assistant.',
1012+
outputChannel?: vscode.OutputChannel,
1013+
streamMode: boolean = true,
1014+
endstring?: string,
1015+
abortSignal?: AbortSignal
1016+
): Promise<string | null> {
1017+
const { modelName, apiBaseURL, apiKey } = getDeepSeekModelConfig();
1018+
const userStopException = 'operation stop by user';
1019+
1020+
if (!apiKey) {
1021+
vscode.window.showErrorMessage('DeepSeek API Key is not configured. Please set it in the settings.');
1022+
return null;
1023+
}
1024+
1025+
if (!modelName || !apiBaseURL) {
1026+
vscode.window.showErrorMessage('DeepSeek Model Name or API Base URL is not configured.');
1027+
return null;
1028+
}
1029+
1030+
try {
1031+
const openai = new OpenAI({
1032+
apiKey: apiKey,
1033+
baseURL: apiBaseURL,
1034+
});
1035+
1036+
if (outputChannel) {
1037+
outputChannel.clear();
1038+
outputChannel.show();
1039+
}
1040+
1041+
// 构造消息体
1042+
let messages_body: OpenAI.ChatCompletionMessageParam[] = [];
1043+
if (Array.isArray(userContent)) {
1044+
messages_body.push({ role: 'system', content: systemContent });
1045+
// 如果 userContent 是数组,按交替方式生成消息
1046+
for (let i = 0; i < userContent.length; i++) {
1047+
const role = (userContent[i].role === 'user') ? 'user' : 'assistant';
1048+
messages_body.push({ role, content: userContent[i].content });
1049+
}
1050+
} else {
1051+
// 如果是单个字符串,默认是 'user' 角色
1052+
messages_body = [
1053+
{ role: 'system', content: systemContent },
1054+
{ role: 'user', content: userContent },
1055+
];
1056+
}
1057+
1058+
let fullResponse = '';
1059+
let maxAttempts = 5;
1060+
let attempts = 0;
1061+
1062+
vscode.window.showInformationMessage('开始上传DeepSeek API');
1063+
1064+
while (attempts < maxAttempts) {
1065+
attempts++;
1066+
const response = await openai.chat.completions.create({
1067+
model: modelName,
1068+
messages: messages_body,
1069+
stream: streamMode,
1070+
max_tokens: 8192,
1071+
temperature: 0
1072+
});
1073+
let thinking = false;
1074+
1075+
vscode.window.showInformationMessage('DeepSeek API 正在处理...');
1076+
1077+
let chunkResponse = '';
1078+
let finishReason: string | null = null;
1079+
1080+
if (streamMode) {
1081+
for await (const chunk of response as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>) {
1082+
if (abortSignal?.aborted) {
1083+
throw new Error(userStopException);
1084+
}
1085+
const content = chunk.choices[0]?.delta?.content || '';
1086+
const delta = chunk.choices[0]?.delta;
1087+
const think = ('reasoning_content' in delta! && delta.reasoning_content) as string || "";
1088+
1089+
if (!thinking && chunkResponse.length === 0 && think.length > 0){
1090+
if (outputChannel) {
1091+
outputChannel.append("<think>");
1092+
}
1093+
thinking = true;
1094+
}
1095+
1096+
chunkResponse += content;
1097+
if (outputChannel) {
1098+
outputChannel.append(content + think);
1099+
}
1100+
1101+
if (thinking && content.length > 0){
1102+
thinking = false;
1103+
if (outputChannel) {
1104+
outputChannel.append("</think>");
1105+
}
1106+
}
1107+
1108+
finishReason = chunk.choices[0]?.finish_reason || null;
1109+
}
1110+
} else {
1111+
const completion = response as OpenAI.Chat.Completions.ChatCompletion;
1112+
chunkResponse = completion.choices[0].message.content || "";
1113+
finishReason = completion.choices[0].finish_reason || null;
1114+
if (outputChannel) {
1115+
outputChannel.append(chunkResponse);
1116+
}
1117+
}
1118+
1119+
// 累积完整响应
1120+
fullResponse += chunkResponse;
1121+
1122+
// 检查终止条件
1123+
const shouldContinue =
1124+
finishReason === 'length' ||
1125+
(endstring && !fullResponse.includes(endstring));
1126+
1127+
if (!shouldContinue) {break;};
1128+
1129+
if (abortSignal?.aborted) {
1130+
throw new Error(userStopException);
1131+
}
1132+
1133+
vscode.window.showWarningMessage('超过最大Token数,正在重试...');
1134+
1135+
// 准备下一次请求
1136+
messages_body.push(
1137+
{ role: 'assistant', content: fullResponse },
1138+
{ role: 'user', content: '你的输出被截断了,请继续输出剩余部分, 不需要```做起始,直接继续输出纯内容:' }
1139+
);
1140+
}
1141+
1142+
// 最终检查
1143+
if (endstring && !fullResponse.includes(endstring)) {
1144+
vscode.window.showWarningMessage('响应未包含结束标记');
1145+
}
1146+
1147+
messages_body.push({ role: 'assistant', content: fullResponse });
1148+
lastMessageBody = messages_body;
1149+
return fullResponse;
1150+
1151+
} catch (error) {
1152+
if (error instanceof Error && error.message === userStopException) {
1153+
vscode.window.showInformationMessage('operation stop by user');
1154+
return null;
1155+
}
1156+
vscode.window.showErrorMessage('API调用失败: ' + (error as Error).message);
1157+
return null;
1158+
}
1159+
}
1160+
1161+
我需要你设计一种通用的agent,来把上面的功能兼容到长上下文
1162+
也就是把调用api改为调用agent
1163+
1164+
agent的大致数据结构和设计思路如下:
1165+
1166+
我希望设计一个agent框架处理这个问题
1167+
把多个文件、长上下文的代码块切成很多块,逐个调用agent
1168+
1169+
agent的数据结构有
1170+
核心工作提示词,指导工作的方式
1171+
目标(用户输入的最终目标)
1172+
互通信息记忆(和目标有关的一些记忆,用来在多轮对话中长期存储的记忆,比如某个函数的潜规则)
1173+
笔记记忆(用来做长期记忆的笔记,用缩进表示层级结构)
1174+
问题列表(从当前输入和互动记忆、笔记记忆中都找不到线索的遗留问题,留待后续输入中寻找线索的问题列表,并标注是否已经被解决)
1175+
任务列表(列出为了达到目标设计的子任务清单,按执行顺序排序,其实就是一个调用栈,允许有层级结构,也就是某个任务下面可以拆成子任务,每个任务后面都有一个括号,里面标注是否已完成)
1176+
当前agent正在执行的子任务
1177+
输入数据:可能是代码块,如果输入完毕,会输入<InputFinish>
1178+
1179+
输出:
1180+
如果当前任务有子任务,就唤起一个新agent,设置他的子任务。直到所有子任务执行完,他才汇总返回
1181+
对以上每一个数据的修改,要求结构化输出,方便处理
1182+
比如根据新输入的块,修改记忆,问题列表添加新问题,或者标记问题已解答
1183+
还可以增删任务列表,修改任务列表的状态(是否已解决)
1184+
不要提出重复问题等
1185+
根据当前子任务,返回应该返回的值
1186+
1187+
可以看出,他是一个树状搜索的控制流
1188+
对于长上下文,他如果想通读一遍,他可以每次唤起一个子任务,读取下一个分块,而这是个tool,可以自动给下一个唤起的子任务装填下一个代码分块
1189+
1190+
理解我的设计,你觉得有没有不合理的地方,能否改进?帮我完善他。
1191+
给出关键代码的实现。
1192+
1193+
一些建议:
1194+
把CVB进行扩展 -> ECVB (Extend CVB)
1195+
把每个文件再加入切页,明确每个分页有一个压缩信息
1196+
每个文件有一个总的压缩信息
1197+
再引入一个Group,把相似功能的文件归类到一起,给一个总结(比如同名的头文件和cpp,或者一个子系统拆出来的MVC几个文件,其实是同一个功能的)
1198+
Group里有文件列表和压缩信息
1199+
压缩信息分两种
1200+
1.摘要,列出所有的类、变量、函数,以及他们的含义和作用分析
1201+
2.缩句,类似摘要,但是是代码连续的,可以让大模型帮忙生成,是一种缩句,把不重要的部分略去
1202+
压缩信息是为了方便大模型能用小窗口加载
1203+
1204+
ECVB提供接口设置和获取压缩信息,以及对应的metadata(文件路径和页码,或者对应的group等)
1205+
1206+
我想设计的就是一个通用行的agent,他甚至能把别的chain,workflow也消化成自己的

src/extension.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ export function clearCurrentOperationController() {
3434
}
3535
}
3636

37+
let currentOutputChannel: vscode.OutputChannel | null = null;
38+
export function getOutputChannel() : vscode.OutputChannel {
39+
if (currentOutputChannel) {
40+
return currentOutputChannel;
41+
}
42+
currentOutputChannel = vscode.window.createOutputChannel('CodeReDesign API Stream', 'markdown');
43+
return currentOutputChannel;
44+
}
45+
3746
export async function doUploadCommand(cvbFilePath: string, userPrompt: string, outputChannel: vscode.OutputChannel){
3847
const workspaceFolders = vscode.workspace.workspaceFolders;
3948
if (!workspaceFolders) {
@@ -152,7 +161,7 @@ export function activate(context: vscode.ExtensionContext) {
152161
hideWorkspaceFolder();
153162

154163
// 创建输出通道
155-
const outputChannel = vscode.window.createOutputChannel('CodeReDesign API Stream', 'markdown');
164+
const outputChannel = getOutputChannel();
156165

157166
// 注册命令:开始对话
158167
let startChatCommand = vscode.commands.registerCommand('codeReDesign.startChat', () => {

src/siderBar.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { applyCvbToWorkspace} from './cvbManager';
55
import { analyzeCode } from './deepseekApi';
66
import { getCurrentOperationController, resetCurrentOperationController, clearCurrentOperationController, doUploadCommand, saveAnalyzeCodeResult} from './extension';
77
import { showInputMultiLineBox } from './UIComponents';
8+
import {getOutputChannel} from './extension';
89

910
class ChatPreviewFileSystemProvider implements vscode.FileSystemProvider {
1011
private content: Uint8Array = new Uint8Array();
@@ -325,7 +326,7 @@ async function uploadThisCvb(filePath: string) {
325326
if (!userPrompt) {
326327
return;
327328
}
328-
const outputChannel = vscode.window.createOutputChannel('CodeReDesign API Stream');
329+
const outputChannel = getOutputChannel();
329330
doUploadCommand(filePath, userPrompt, outputChannel);
330331
}
331332

@@ -344,7 +345,7 @@ async function analyzeThisCvb(filePath: string) {
344345
}
345346

346347
const cvbContent = fs.readFileSync(filePath, 'utf-8');
347-
const outputChannel = vscode.window.createOutputChannel('CodeReDesign API Stream');
348+
const outputChannel = getOutputChannel();
348349

349350
resetCurrentOperationController();
350351

0 commit comments

Comments
 (0)