fix: 工具调用前刷新流式行缓冲 - 修复AI文本丢失

- lineBuffer/mdState提升为实例字段(工具事件回调可访问)
- 工具START事件先flushStreamLineBuffer()再渲染工具状态
- 修复: AI在工具调用前发送的文本(无\\n结尾)现在正确显示

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pull/1/head
liuzh 1 month ago
parent b2066c0cc7
commit d17beb8a85
  1. 45
      src/main/java/com/claudecode/repl/ReplSession.java

@ -60,6 +60,11 @@ public class ReplSession {
/** 流式输出换行跟踪:工具渲染和流式回调共享,保证缩进一致 */ /** 流式输出换行跟踪:工具渲染和流式回调共享,保证缩进一致 */
private volatile boolean streamNewLine = false; private volatile boolean streamNewLine = false;
/** 流式行缓冲:累积 token 到换行再 Markdown 渲染输出 */
private final StringBuilder streamLineBuffer = new StringBuilder();
/** 流式 Markdown 渲染状态:跨行追踪代码块 */
private MarkdownRenderer.StreamState streamMdState = new MarkdownRenderer.StreamState();
/** 当前活跃的 LineReader(JLine 模式下用于 AskUser 和权限确认) */ /** 当前活跃的 LineReader(JLine 模式下用于 AskUser 和权限确认) */
private volatile LineReader activeReader; private volatile LineReader activeReader;
/** 当前活跃的 Scanner(Scanner 模式下用于 AskUser 和权限确认) */ /** 当前活跃的 Scanner(Scanner 模式下用于 AskUser 和权限确认) */
@ -102,7 +107,8 @@ public class ReplSession {
switch (event.phase()) { switch (event.phase()) {
case START -> { case START -> {
spinner.stop(); spinner.stop();
// 如果前面的流式文本没有换行结尾,先换行 // 刷新行缓冲(AI 文本可能在工具调用前没有换行结尾)
flushStreamLineBuffer();
if (!streamNewLine) { if (!streamNewLine) {
out.println(); out.println();
} }
@ -333,13 +339,11 @@ public class ReplSession {
try { try {
spinner.start("Thinking..."); spinner.start("Thinking...");
streamNewLine = true; // spinner 停止后 onStreamStart 会打印 ● 前缀 streamNewLine = true; // spinner 停止后 onStreamStart 会打印 ● 前缀
streamLineBuffer.setLength(0); // 重置行缓冲
streamMdState = new MarkdownRenderer.StreamState(); // 重置 Markdown 状态
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 行缓冲:累积 token 直到换行,再用 MarkdownRenderer 渲染整行输出
StringBuilder lineBuffer = new StringBuilder();
MarkdownRenderer.StreamState mdState = new MarkdownRenderer.StreamState();
String response = agentLoop.runStreaming(input, token -> { String response = agentLoop.runStreaming(input, token -> {
for (int i = 0; i < token.length(); i++) { for (int i = 0; i < token.length(); i++) {
char c = token.charAt(i); char c = token.charAt(i);
@ -349,26 +353,19 @@ public class ReplSession {
out.print(" "); // 续行缩进(与 ● 后文本对齐) out.print(" "); // 续行缩进(与 ● 后文本对齐)
streamNewLine = false; streamNewLine = false;
} }
String rendered = markdownRenderer.renderStreamingLine(lineBuffer.toString(), mdState); String rendered = markdownRenderer.renderStreamingLine(streamLineBuffer.toString(), streamMdState);
out.println(rendered); out.println(rendered);
lineBuffer.setLength(0); streamLineBuffer.setLength(0);
streamNewLine = true; streamNewLine = true;
} else { } else {
lineBuffer.append(c); streamLineBuffer.append(c);
} }
} }
out.flush(); out.flush();
}); });
// 刷新残留缓冲(最后一行可能无 \n 结尾) // 刷新残留缓冲(最后一行可能无 \n 结尾)
if (!lineBuffer.isEmpty()) { flushStreamLineBuffer();
if (streamNewLine) {
out.print(" ");
streamNewLine = false;
}
String rendered = markdownRenderer.renderStreamingLine(lineBuffer.toString(), mdState);
out.print(rendered);
}
spinner.stop(); spinner.stop();
out.println(); // 流式输出结束后换行 out.println(); // 流式输出结束后换行
@ -392,6 +389,22 @@ public class ReplSession {
} }
} }
/**
* 刷新流式行缓冲 将未输出的缓冲内容渲染并打印
* 在工具调用前流式结束后调用防止 AI 文本丢失
*/
private void flushStreamLineBuffer() {
if (!streamLineBuffer.isEmpty()) {
if (streamNewLine) {
out.print(" ");
streamNewLine = false;
}
String rendered = markdownRenderer.renderStreamingLine(streamLineBuffer.toString(), streamMdState);
out.print(rendered);
streamLineBuffer.setLength(0);
}
}
/** 退出时保存对话历史 */ /** 退出时保存对话历史 */
private void saveConversation() { private void saveConversation() {
var history = agentLoop.getMessageHistory(); var history = agentLoop.getMessageHistory();

Loading…
Cancel
Save