From 96866f36e548f052638172539506e078bbd723f8 Mon Sep 17 00:00:00 2001 From: ekko <152005280+EKKOLearnAI@users.noreply.github.com> Date: Tue, 12 May 2026 12:08:12 +0800 Subject: [PATCH] fix: emit tool.started immediately on function_call and add duration to tool.completed (#647) - Emit tool.started on response.output_item.added instead of .done - Track startedAt timestamp for each tool call to calculate duration - Include duration (2 decimal places) and error status in tool.completed - Fix response.completed fallback to emit tool.started before tool.completed - Update website license from MIT to BSL-1.1 Co-authored-by: Claude Opus 4.7 --- .../src/services/hermes/chat-run-socket.ts | 43 +++++++++++-------- packages/website/src/i18n/en.ts | 2 +- packages/website/src/i18n/zh.ts | 2 +- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/server/src/services/hermes/chat-run-socket.ts b/packages/server/src/services/hermes/chat-run-socket.ts index 31e4d9d..f7f8dcb 100644 --- a/packages/server/src/services/hermes/chat-run-socket.ts +++ b/packages/server/src/services/hermes/chat-run-socket.ts @@ -1101,8 +1101,21 @@ export class ChatRunSocket { if (item.type !== 'function_call') return null const callId = item.call_id || item.id if (!callId) return null - run.toolCalls.set(callId, responseFunctionCallToToolCall(item)) - return null + const toolCall = responseFunctionCallToToolCall(item) + run.toolCalls.set(callId, { ...toolCall, startedAt: Date.now() }) + return { + event: 'tool.started', + payload: { + event: 'tool.started', + run_id: run.responseId, + response_id: run.responseId, + tool_call_id: callId, + tool: toolCall.function.name, + name: toolCall.function.name, + arguments: toolCall.function.arguments, + preview: summarizeToolArguments(toolCall.function.arguments), + }, + } } if (eventType === 'response.output_item.done') { @@ -1111,7 +1124,8 @@ export class ChatRunSocket { const callId = item.call_id || item.id if (!callId) return null const toolCall = responseFunctionCallToToolCall(item) - run.toolCalls.set(callId, toolCall) + const existing = run.toolCalls.get(callId) + run.toolCalls.set(callId, { ...toolCall, startedAt: existing?.startedAt || Date.now() }) const key = `assistant:${callId}` if (!run.insertedKeys.has(key)) { @@ -1127,19 +1141,7 @@ export class ChatRunSocket { timestamp: now(), }) } - return { - event: 'tool.started', - payload: { - event: 'tool.started', - run_id: run.responseId, - response_id: run.responseId, - tool_call_id: callId, - tool: toolCall.function.name, - name: toolCall.function.name, - arguments: toolCall.function.arguments, - preview: summarizeToolArguments(toolCall.function.arguments), - }, - } + return null } if (item.type === 'function_call_output') { @@ -1147,7 +1149,11 @@ export class ChatRunSocket { if (!callId) return null const key = `tool:${callId}` const output = typeof item.output === 'string' ? item.output : JSON.stringify(item.output ?? '') - const toolName = run.toolCalls.get(callId)?.function?.name || null + const toolCallEntry = run.toolCalls.get(callId) + const toolName = toolCallEntry?.function?.name || null + const startedAt = toolCallEntry?.startedAt + const duration = startedAt ? Math.round((Date.now() - startedAt) / 10) / 100 : undefined + const hasError = typeof item.output === 'string' && item.output.startsWith('Error') if (!run.insertedKeys.has(key)) { run.insertedKeys.add(key) state.messages.push({ @@ -1171,6 +1177,8 @@ export class ChatRunSocket { tool: toolName, name: toolName, output, + duration, + error: hasError || undefined, }, } } @@ -1182,6 +1190,7 @@ export class ChatRunSocket { const output = Array.isArray(response.output) ? response.output : [] for (const item of output) { if (item.type === 'function_call') { + this.applyResponseStreamEvent(state, sessionId, runMarker, 'response.output_item.added', { item }) this.applyResponseStreamEvent(state, sessionId, runMarker, 'response.output_item.done', { item }) } else if (item.type === 'function_call_output') { this.applyResponseStreamEvent(state, sessionId, runMarker, 'response.output_item.done', { item }) diff --git a/packages/website/src/i18n/en.ts b/packages/website/src/i18n/en.ts index 08f7f1d..01852be 100644 --- a/packages/website/src/i18n/en.ts +++ b/packages/website/src/i18n/en.ts @@ -101,7 +101,7 @@ export default { }, footer: { description: 'Self-hosted AI chat dashboard for Hermes Agent.', - license: 'MIT License', + license: 'BSL-1.1 License', madeWith: 'Built with Vue 3, Naive UI, and TypeScript.', }, docs: { diff --git a/packages/website/src/i18n/zh.ts b/packages/website/src/i18n/zh.ts index f432ae8..a4458cd 100644 --- a/packages/website/src/i18n/zh.ts +++ b/packages/website/src/i18n/zh.ts @@ -101,7 +101,7 @@ export default { }, footer: { description: 'Hermes Agent 的自托管 AI 聊天仪表板。', - license: 'MIT 开源协议', + license: 'BSL-1.1 开源协议', madeWith: '使用 Vue 3、Naive UI 和 TypeScript 构建。', }, docs: {