fex:尝试修复流式显示工具调用不穿插显示的问题

This commit is contained in:
XiaoFeng
2025-12-17 10:03:40 +08:00
parent c21ad95963
commit 6c5d470bad
3 changed files with 305 additions and 41 deletions

View File

@ -9,6 +9,20 @@ import { userInteractionManager } from './userInteraction';
import { getConfig } from '../config/settings';
import type { DialogRequest, ToolCallRequest, AskUserEvent } from '../types/api';
/**
* 消息段落类型
*/
export interface MessageSegment {
type: 'text' | 'tool' | 'question';
content?: string;
toolName?: string;
toolStatus?: 'running' | 'success' | 'error';
toolResult?: string;
askId?: string;
question?: string;
options?: string[];
}
/**
* 对话回调接口
*/
@ -23,8 +37,8 @@ export interface DialogCallbacks {
onToolError?: (toolName: string, error: string) => void;
/** 显示问题ask_user */
onQuestion?: (askId: string, question: string, options: string[]) => void;
/** 对话完成 */
onComplete?: () => void;
/** 对话完成,返回所有段落 */
onComplete?: (segments: MessageSegment[]) => void;
/** 错误 */
onError?: (message: string) => void;
/** 通知消息 */
@ -40,12 +54,62 @@ export class DialogSession {
private toolContext: ToolExecutorContext;
private accumulatedText = '';
private isActive = false;
private segments: MessageSegment[] = [];
private currentTextSegment: MessageSegment | null = null;
constructor(extensionPath: string) {
this.taskId = generateTaskId();
this.toolContext = createToolExecutorContext(extensionPath);
}
/**
* 添加文本到当前文本段落
*/
private appendText(text: string): void {
if (!this.currentTextSegment) {
this.currentTextSegment = { type: 'text', content: '' };
this.segments.push(this.currentTextSegment);
}
this.currentTextSegment.content = (this.currentTextSegment.content || '') + text;
}
/**
* 结束当前文本段落
*/
private finalizeTextSegment(): void {
this.currentTextSegment = null;
}
/**
* 添加工具段落
*/
private addToolSegment(toolName: string, status: 'running' | 'success' | 'error', result?: string): MessageSegment {
this.finalizeTextSegment();
const segment: MessageSegment = {
type: 'tool',
toolName,
toolStatus: status,
toolResult: result
};
this.segments.push(segment);
return segment;
}
/**
* 更新工具段落状态
*/
private updateToolSegment(toolName: string, status: 'success' | 'error', result?: string): void {
// 找到最后一个匹配的工具段落
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'tool' && seg.toolName === toolName && seg.toolStatus === 'running') {
seg.toolStatus = status;
seg.toolResult = result;
break;
}
}
}
/**
* 获取任务ID
*/
@ -74,6 +138,8 @@ export class DialogSession {
this.isActive = true;
this.accumulatedText = '';
this.segments = [];
this.currentTextSegment = null;
const config = getConfig();
const request: DialogRequest = {
@ -86,34 +152,64 @@ export class DialogSession {
const sseCallbacks: SSECallbacks = {
onTextDelta: (data) => {
this.accumulatedText += data.text;
this.appendText(data.text);
console.log('[DialogSession] onTextDelta, 累积文本长度:', this.accumulatedText.length);
callbacks.onText?.(this.accumulatedText, true);
},
onToolCall: async (data: ToolCallRequest) => {
callbacks.onToolStart?.(data.params.name);
const toolName = data.params.name;
console.log('[DialogSession] onToolCall:', toolName);
// 检查是否已经有相同的工具段落(可能由 onToolStart 添加)
const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop();
if (lastToolSegment && lastToolSegment.toolName === toolName && lastToolSegment.toolStatus === 'running') {
console.log('[DialogSession] onToolCall: 跳过重复的工具段落:', toolName);
} else {
this.addToolSegment(toolName, 'running');
}
// 注意:不在这里调用 callbacks.onToolStart避免与 onToolStart 事件重复
try {
await executeToolCall(data, this.toolContext);
callbacks.onToolComplete?.(data.params.name, '执行完成');
this.updateToolSegment(toolName, 'success', '执行完成');
// 也不调用 callbacks.onToolComplete避免重复
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '未知错误';
callbacks.onToolError?.(data.params.name, errorMsg);
this.updateToolSegment(toolName, 'error', errorMsg);
callbacks.onToolError?.(toolName, errorMsg);
}
},
onToolStart: (data) => {
console.log('[DialogSession] onToolStart:', data.tool_name);
// 检查是否已经有相同的工具段落(可能由 onToolCall 添加)
const lastToolSegment = this.segments.filter(s => s.type === 'tool').pop();
if (lastToolSegment && lastToolSegment.toolName === data.tool_name && lastToolSegment.toolStatus === 'running') {
console.log('[DialogSession] 跳过重复的工具段落:', data.tool_name);
} else {
this.addToolSegment(data.tool_name, 'running');
}
console.log('[DialogSession] segments 数量:', this.segments.length);
callbacks.onToolStart?.(data.tool_name);
},
onToolComplete: (data) => {
this.updateToolSegment(data.tool_name, 'success', data.result);
callbacks.onToolComplete?.(data.tool_name, data.result);
},
onToolError: (data) => {
this.updateToolSegment(data.tool_name, 'error', data.error);
callbacks.onToolError?.(data.tool_name, data.error);
},
onAskUser: async (data: AskUserEvent) => {
this.finalizeTextSegment();
this.segments.push({
type: 'question',
askId: data.askId,
question: data.question,
options: data.options
});
callbacks.onQuestion?.(data.askId, data.question, data.options);
try {
await userInteractionManager.handleAskUser(data, this.taskId);
@ -124,11 +220,9 @@ export class DialogSession {
onComplete: (data) => {
this.isActive = false;
// 发送最终文本(非流式)
if (this.accumulatedText) {
callbacks.onText?.(this.accumulatedText, false);
}
callbacks.onComplete?.();
this.finalizeTextSegment();
// 发送所有段落
callbacks.onComplete?.(this.segments);
},
onError: (data) => {