fix: delay tool blocks until text fully streamed
This commit is contained in:
parent
6d330b1388
commit
55af5b52c6
@ -37,6 +37,32 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
ignoreThinking: false
|
ignoreThinking: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pendingToolEvents: Array<{ event: string; data: any; handler: () => void }> = [];
|
||||||
|
|
||||||
|
const hasPendingStreamingText = () =>
|
||||||
|
!!streamingState.buffer.length ||
|
||||||
|
!!streamingState.pendingCompleteContent ||
|
||||||
|
streamingState.timer !== null ||
|
||||||
|
streamingState.completionTimer !== null;
|
||||||
|
|
||||||
|
const resetPendingToolEvents = () => {
|
||||||
|
pendingToolEvents.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const flushPendingToolEvents = () => {
|
||||||
|
if (!pendingToolEvents.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const queue = pendingToolEvents.splice(0);
|
||||||
|
queue.forEach((item) => {
|
||||||
|
try {
|
||||||
|
item.handler();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('延后工具事件处理失败:', item.event, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const snapshotStreamingState = () => ({
|
const snapshotStreamingState = () => ({
|
||||||
bufferLength: streamingState.buffer.length,
|
bufferLength: streamingState.buffer.length,
|
||||||
timerActive: streamingState.timer !== null,
|
timerActive: streamingState.timer !== null,
|
||||||
@ -297,6 +323,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
streamingState.activeMessageIndex = null;
|
streamingState.activeMessageIndex = null;
|
||||||
streamingState.activeTextAction = null;
|
streamingState.activeTextAction = null;
|
||||||
markStreamingIdleIfPossible('finalizeStreamingText');
|
markStreamingIdleIfPossible('finalizeStreamingText');
|
||||||
|
flushPendingToolEvents();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -326,6 +353,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
streamingState.renderedText = '';
|
streamingState.renderedText = '';
|
||||||
streamingState.activeMessageIndex = null;
|
streamingState.activeMessageIndex = null;
|
||||||
streamingState.activeTextAction = null;
|
streamingState.activeTextAction = null;
|
||||||
|
resetPendingToolEvents();
|
||||||
logStreamingDebug('resetStreamingBuffer', snapshotStreamingState());
|
logStreamingDebug('resetStreamingBuffer', snapshotStreamingState());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -658,6 +686,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
socketLog('AI消息开始');
|
socketLog('AI消息开始');
|
||||||
logStreamingDebug('socket:ai_message_start');
|
logStreamingDebug('socket:ai_message_start');
|
||||||
finalizeStreamingText({ force: true });
|
finalizeStreamingText({ force: true });
|
||||||
|
flushPendingToolEvents();
|
||||||
resetStreamingBuffer();
|
resetStreamingBuffer();
|
||||||
ctx.monitorResetSpeech();
|
ctx.monitorResetSpeech();
|
||||||
ctx.cleanupStaleToolActions();
|
ctx.cleanupStaleToolActions();
|
||||||
@ -807,34 +836,41 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id);
|
socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const msg = ctx.chatEnsureAssistantMessage();
|
const handler = () => {
|
||||||
if (!msg) {
|
const msg = ctx.chatEnsureAssistantMessage();
|
||||||
return;
|
if (!msg) {
|
||||||
}
|
return;
|
||||||
const action = {
|
}
|
||||||
id: data.id,
|
const action = {
|
||||||
type: 'tool',
|
|
||||||
tool: {
|
|
||||||
id: data.id,
|
id: data.id,
|
||||||
name: data.name,
|
type: 'tool',
|
||||||
arguments: {},
|
tool: {
|
||||||
argumentSnapshot: null,
|
id: data.id,
|
||||||
argumentLabel: '',
|
name: data.name,
|
||||||
status: 'preparing',
|
arguments: {},
|
||||||
result: null,
|
argumentSnapshot: null,
|
||||||
message: data.message || `准备调用 ${data.name}...`
|
argumentLabel: '',
|
||||||
},
|
status: 'preparing',
|
||||||
timestamp: Date.now()
|
result: null,
|
||||||
|
message: data.message || `准备调用 ${data.name}...`
|
||||||
|
},
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
msg.actions.push(action);
|
||||||
|
ctx.preparingTools.set(data.id, action);
|
||||||
|
ctx.toolRegisterAction(action, data.id);
|
||||||
|
ctx.toolTrackAction(data.name, action);
|
||||||
|
ctx.$forceUpdate();
|
||||||
|
ctx.conditionalScrollToBottom();
|
||||||
|
if (ctx.monitorPreviewTool) {
|
||||||
|
ctx.monitorPreviewTool(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
msg.actions.push(action);
|
|
||||||
ctx.preparingTools.set(data.id, action);
|
if (hasPendingStreamingText()) {
|
||||||
ctx.toolRegisterAction(action, data.id);
|
pendingToolEvents.push({ event: 'tool_preparing', data, handler });
|
||||||
ctx.toolTrackAction(data.name, action);
|
} else {
|
||||||
ctx.$forceUpdate();
|
handler();
|
||||||
ctx.conditionalScrollToBottom();
|
|
||||||
// 虚拟显示器:在模型检测到工具时立即展示“正在XX”预览
|
|
||||||
if (ctx.monitorPreviewTool) {
|
|
||||||
ctx.monitorPreviewTool(data);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -868,46 +904,54 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
socketLog('跳过tool_start(对话不匹配)', data.conversation_id);
|
socketLog('跳过tool_start(对话不匹配)', data.conversation_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let action = null;
|
const handler = () => {
|
||||||
if (data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
|
let action = null;
|
||||||
action = ctx.preparingTools.get(data.preparing_id);
|
if (data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
|
||||||
ctx.preparingTools.delete(data.preparing_id);
|
action = ctx.preparingTools.get(data.preparing_id);
|
||||||
} else {
|
ctx.preparingTools.delete(data.preparing_id);
|
||||||
action = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
|
} else {
|
||||||
}
|
action = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
|
||||||
if (!action) {
|
|
||||||
const msg = ctx.chatEnsureAssistantMessage();
|
|
||||||
if (!msg) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
action = {
|
if (!action) {
|
||||||
id: data.id,
|
const msg = ctx.chatEnsureAssistantMessage();
|
||||||
type: 'tool',
|
if (!msg) {
|
||||||
tool: {
|
return;
|
||||||
id: data.id,
|
}
|
||||||
name: data.name,
|
action = {
|
||||||
arguments: {},
|
id: data.id,
|
||||||
argumentSnapshot: null,
|
type: 'tool',
|
||||||
argumentLabel: '',
|
tool: {
|
||||||
status: 'running',
|
id: data.id,
|
||||||
result: null
|
name: data.name,
|
||||||
},
|
arguments: {},
|
||||||
timestamp: Date.now()
|
argumentSnapshot: null,
|
||||||
};
|
argumentLabel: '',
|
||||||
msg.actions.push(action);
|
status: 'running',
|
||||||
|
result: null
|
||||||
|
},
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
msg.actions.push(action);
|
||||||
|
}
|
||||||
|
action.tool.status = 'running';
|
||||||
|
action.tool.arguments = data.arguments;
|
||||||
|
action.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
||||||
|
action.tool.argumentLabel = ctx.buildToolLabel(action.tool.argumentSnapshot);
|
||||||
|
action.tool.message = null;
|
||||||
|
action.tool.id = data.id;
|
||||||
|
action.tool.executionId = data.id;
|
||||||
|
ctx.toolRegisterAction(action, data.id);
|
||||||
|
ctx.toolTrackAction(data.name, action);
|
||||||
|
ctx.$forceUpdate();
|
||||||
|
ctx.conditionalScrollToBottom();
|
||||||
|
ctx.monitorQueueTool(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasPendingStreamingText()) {
|
||||||
|
pendingToolEvents.push({ event: 'tool_start', data, handler });
|
||||||
|
} else {
|
||||||
|
handler();
|
||||||
}
|
}
|
||||||
action.tool.status = 'running';
|
|
||||||
action.tool.arguments = data.arguments;
|
|
||||||
action.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
|
||||||
action.tool.argumentLabel = ctx.buildToolLabel(action.tool.argumentSnapshot);
|
|
||||||
action.tool.message = null;
|
|
||||||
action.tool.id = data.id;
|
|
||||||
action.tool.executionId = data.id;
|
|
||||||
ctx.toolRegisterAction(action, data.id);
|
|
||||||
ctx.toolTrackAction(data.name, action);
|
|
||||||
ctx.$forceUpdate();
|
|
||||||
ctx.conditionalScrollToBottom();
|
|
||||||
ctx.monitorQueueTool(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新action(工具完成)
|
// 更新action(工具完成)
|
||||||
@ -917,82 +961,89 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
socketLog('跳过update_action(对话不匹配)', data.conversation_id);
|
socketLog('跳过update_action(对话不匹配)', data.conversation_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let targetAction = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
|
const handler = () => {
|
||||||
if (!targetAction && data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
|
let targetAction = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
|
||||||
targetAction = ctx.preparingTools.get(data.preparing_id);
|
if (!targetAction && data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
|
||||||
}
|
targetAction = ctx.preparingTools.get(data.preparing_id);
|
||||||
if (!targetAction) {
|
}
|
||||||
outer: for (const message of ctx.messages) {
|
if (!targetAction) {
|
||||||
if (!message.actions) continue;
|
outer: for (const message of ctx.messages) {
|
||||||
for (const action of message.actions) {
|
if (!message.actions) continue;
|
||||||
if (action.type !== 'tool') continue;
|
for (const action of message.actions) {
|
||||||
const matchByExecution = action.tool.executionId && action.tool.executionId === data.id;
|
if (action.type !== 'tool') continue;
|
||||||
const matchByToolId = action.tool.id === data.id;
|
const matchByExecution = action.tool.executionId && action.tool.executionId === data.id;
|
||||||
const matchByPreparingId = action.id === data.preparing_id;
|
const matchByToolId = action.tool.id === data.id;
|
||||||
if (matchByExecution || matchByToolId || matchByPreparingId) {
|
const matchByPreparingId = action.id === data.preparing_id;
|
||||||
targetAction = action;
|
if (matchByExecution || matchByToolId || matchByPreparingId) {
|
||||||
break outer;
|
targetAction = action;
|
||||||
|
break outer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (targetAction) {
|
||||||
if (targetAction) {
|
if (data.status) {
|
||||||
if (data.status) {
|
targetAction.tool.status = data.status;
|
||||||
targetAction.tool.status = data.status;
|
|
||||||
}
|
|
||||||
if (data.result !== undefined) {
|
|
||||||
targetAction.tool.result = data.result;
|
|
||||||
}
|
|
||||||
if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) {
|
|
||||||
const eggPromise = ctx.handleEasterEggPayload(data.result);
|
|
||||||
if (eggPromise && typeof eggPromise.catch === 'function') {
|
|
||||||
eggPromise.catch((error) => {
|
|
||||||
console.warn('彩蛋处理异常:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
if (data.result !== undefined) {
|
||||||
if (data.message !== undefined) {
|
targetAction.tool.result = data.result;
|
||||||
targetAction.tool.message = data.message;
|
|
||||||
}
|
|
||||||
if (data.awaiting_content) {
|
|
||||||
targetAction.tool.awaiting_content = true;
|
|
||||||
} else if (data.status === 'completed') {
|
|
||||||
targetAction.tool.awaiting_content = false;
|
|
||||||
}
|
|
||||||
if (!targetAction.tool.executionId && (data.execution_id || data.id)) {
|
|
||||||
targetAction.tool.executionId = data.execution_id || data.id;
|
|
||||||
}
|
|
||||||
if (data.arguments) {
|
|
||||||
targetAction.tool.arguments = data.arguments;
|
|
||||||
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
|
||||||
targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot);
|
|
||||||
}
|
|
||||||
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
|
|
||||||
if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) {
|
|
||||||
ctx.toolUnregisterAction(targetAction);
|
|
||||||
if (data.id) {
|
|
||||||
ctx.preparingTools.delete(data.id);
|
|
||||||
}
|
}
|
||||||
if (data.preparing_id) {
|
if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) {
|
||||||
ctx.preparingTools.delete(data.preparing_id);
|
const eggPromise = ctx.handleEasterEggPayload(data.result);
|
||||||
|
if (eggPromise && typeof eggPromise.catch === 'function') {
|
||||||
|
eggPromise.catch((error) => {
|
||||||
|
console.warn('彩蛋处理异常:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (data.message !== undefined) {
|
||||||
|
targetAction.tool.message = data.message;
|
||||||
|
}
|
||||||
|
if (data.awaiting_content) {
|
||||||
|
targetAction.tool.awaiting_content = true;
|
||||||
|
} else if (data.status === 'completed') {
|
||||||
|
targetAction.tool.awaiting_content = false;
|
||||||
|
}
|
||||||
|
if (!targetAction.tool.executionId && (data.execution_id || data.id)) {
|
||||||
|
targetAction.tool.executionId = data.execution_id || data.id;
|
||||||
|
}
|
||||||
|
if (data.arguments) {
|
||||||
|
targetAction.tool.arguments = data.arguments;
|
||||||
|
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
||||||
|
targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot);
|
||||||
|
}
|
||||||
|
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
|
||||||
|
if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) {
|
||||||
|
ctx.toolUnregisterAction(targetAction);
|
||||||
|
if (data.id) {
|
||||||
|
ctx.preparingTools.delete(data.id);
|
||||||
|
}
|
||||||
|
if (data.preparing_id) {
|
||||||
|
ctx.preparingTools.delete(data.preparing_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.$forceUpdate();
|
||||||
|
ctx.conditionalScrollToBottom();
|
||||||
}
|
}
|
||||||
ctx.$forceUpdate();
|
|
||||||
ctx.conditionalScrollToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关键修复:每个工具完成后都更新当前上下文Token
|
if (data.status === 'completed') {
|
||||||
if (data.status === 'completed') {
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
ctx.updateCurrentContextTokens();
|
||||||
ctx.updateCurrentContextTokens();
|
}, 500);
|
||||||
}, 500);
|
try {
|
||||||
try {
|
ctx.fileFetchTree();
|
||||||
ctx.fileFetchTree();
|
} catch (error) {
|
||||||
} catch (error) {
|
console.warn('刷新文件树失败', error);
|
||||||
console.warn('刷新文件树失败', error);
|
}
|
||||||
}
|
}
|
||||||
|
ctx.monitorResolveTool(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasPendingStreamingText()) {
|
||||||
|
pendingToolEvents.push({ event: 'update_action', data, handler });
|
||||||
|
} else {
|
||||||
|
handler();
|
||||||
}
|
}
|
||||||
ctx.monitorResolveTool(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.socket.on('append_payload', (data) => {
|
ctx.socket.on('append_payload', (data) => {
|
||||||
@ -1046,6 +1097,8 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
socketLog('任务完成', data);
|
socketLog('任务完成', data);
|
||||||
ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true });
|
ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true });
|
||||||
|
|
||||||
|
resetPendingToolEvents();
|
||||||
|
|
||||||
// 任务完成后立即更新Token统计(关键修复)
|
// 任务完成后立即更新Token统计(关键修复)
|
||||||
if (ctx.currentConversationId) {
|
if (ctx.currentConversationId) {
|
||||||
ctx.updateCurrentContextTokens();
|
ctx.updateCurrentContextTokens();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user