feat(ui): 优化流式输出工具调用渲染

- 添加 tool_start/tool_end WebSocket 事件支持
- 流式消息复用 ChatMessage 组件渲染工具调用卡片
- 修复 AI SDK v5 格式兼容问题(input/output 字段)
- 修复会话恢复时 tool-result 格式错误
- 放宽 ToolState schema 中 input 字段类型为 unknown
This commit is contained in:
2025-12-15 17:35:39 +08:00
parent 865e0906b9
commit 3fd8fd98b8
12 changed files with 384 additions and 58 deletions
+55 -1
View File
@@ -67,11 +67,33 @@ interface SessionManagerConstructor {
new (): SessionManagerInstance;
}
/**
* 工具开始信息
*/
interface ToolStartInfo {
id: string;
toolName: string;
args: Record<string, unknown>;
}
/**
* 工具结束信息
*/
interface ToolEndInfo {
id: string;
status: 'completed' | 'error';
result?: unknown;
error?: string;
duration?: number;
}
/**
* Chat 选项接口
*/
interface ChatOptions {
onStream?: (chunk: string) => void;
onToolStart?: (info: ToolStartInfo) => void;
onToolEnd?: (info: ToolEndInfo) => void;
abortSignal?: AbortSignal;
}
@@ -397,7 +419,7 @@ export async function processMessage(sessionId: string, content: string): Promis
payload: { content: chunk },
});
// 检测工具调用
// 检测工具调用(向后兼容 - SSE 日志)
if (chunk.includes('[调用工具:')) {
const match = chunk.match(/\[调用工具: (.+?)\]/);
if (match) {
@@ -405,6 +427,38 @@ export async function processMessage(sessionId: string, content: string): Promis
}
}
},
onToolStart: (info) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
// 推送工具开始事件
broadcastToSession(sessionId, {
type: 'tool_start',
sessionId,
payload: {
id: info.id,
toolName: info.toolName,
arguments: info.args,
},
});
},
onToolEnd: (info) => {
// 检查是否已取消
if (abortController.signal.aborted) return;
// 推送工具结束事件
broadcastToSession(sessionId, {
type: 'tool_end',
sessionId,
payload: {
id: info.id,
status: info.status,
result: info.result,
error: info.error,
duration: info.duration,
},
});
},
abortSignal: abortController.signal,
});
+18
View File
@@ -110,6 +110,8 @@ export interface ServerMessage {
| 'chunk'
| 'tool_call'
| 'tool_result'
| 'tool_start' // 工具开始执行
| 'tool_end' // 工具执行完成
| 'done'
| 'cancelled'
| 'error'
@@ -119,6 +121,22 @@ export interface ServerMessage {
payload?: unknown;
}
// 工具开始事件 Payload
export interface ToolStartPayload {
id: string;
toolName: string;
arguments: Record<string, unknown>;
}
// 工具结束事件 Payload
export interface ToolEndPayload {
id: string;
status: 'completed' | 'error';
result?: unknown;
error?: string;
duration?: number;
}
// ============ Permission 相关 ============
export type PermissionType = 'bash' | 'file' | 'git' | 'web';