From 4a3cb4f875d6c47200a317f482f56b3c0eb65d19 Mon Sep 17 00:00:00 2001 From: "mingholy.lmh" Date: Mon, 26 Jan 2026 19:51:54 +0800 Subject: [PATCH] fix: add name toolName metadata for ACP tool call messages --- packages/cli/src/acp-integration/schema.ts | 3 +++ .../session/HistoryReplayer.test.ts | 2 ++ .../cli/src/acp-integration/session/Session.ts | 6 +++++- .../session/emitters/ToolCallEmitter.test.ts | 17 ++++++++++++++++- .../session/emitters/ToolCallEmitter.ts | 9 ++++++++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/acp-integration/schema.ts b/packages/cli/src/acp-integration/schema.ts index 4278f0dd4..e25239485 100644 --- a/packages/cli/src/acp-integration/schema.ts +++ b/packages/cli/src/acp-integration/schema.ts @@ -366,6 +366,7 @@ export type Usage = z.infer; export const sessionUpdateMetaSchema = z.object({ usage: usageSchema.optional().nullable(), durationMs: z.number().optional().nullable(), + toolName: z.string().optional().nullable(), }); export type SessionUpdateMeta = z.infer; @@ -573,6 +574,7 @@ export const sessionUpdateSchema = z.union([ kind: toolKindSchema, locations: z.array(toolCallLocationSchema).optional(), rawInput: z.unknown().optional(), + _meta: sessionUpdateMetaSchema.optional().nullable(), sessionUpdate: z.literal('tool_call'), status: toolCallStatusSchema, title: z.string(), @@ -584,6 +586,7 @@ export const sessionUpdateSchema = z.union([ locations: z.array(toolCallLocationSchema).optional().nullable(), rawInput: z.unknown().optional(), rawOutput: z.unknown().optional(), + _meta: sessionUpdateMetaSchema.optional().nullable(), sessionUpdate: z.literal('tool_call_update'), status: toolCallStatusSchema.optional().nullable(), title: z.string().optional().nullable(), diff --git a/packages/cli/src/acp-integration/session/HistoryReplayer.test.ts b/packages/cli/src/acp-integration/session/HistoryReplayer.test.ts index c9cf65fb8..ef750f539 100644 --- a/packages/cli/src/acp-integration/session/HistoryReplayer.test.ts +++ b/packages/cli/src/acp-integration/session/HistoryReplayer.test.ts @@ -228,6 +228,7 @@ describe('HistoryReplayer', () => { status: 'in_progress', title: 'read_file', rawInput: { path: '/test.ts' }, + _meta: { toolName: 'read_file' }, }), ); }); @@ -280,6 +281,7 @@ describe('HistoryReplayer', () => { ], // resultDisplay is included as rawOutput rawOutput: 'File contents here', + _meta: { toolName: 'read_file' }, }); }); diff --git a/packages/cli/src/acp-integration/session/Session.ts b/packages/cli/src/acp-integration/session/Session.ts index 5348d78df..0ee4f8581 100644 --- a/packages/cli/src/acp-integration/session/Session.ts +++ b/packages/cli/src/acp-integration/session/Session.ts @@ -647,7 +647,11 @@ export class Session implements SessionContext { const error = e instanceof Error ? e : new Error(String(e)); // Use ToolCallEmitter for error handling - await this.toolCallEmitter.emitError(callId, error); + await this.toolCallEmitter.emitError( + callId, + fc.name ?? 'unknown_tool', + error, + ); // Record tool error for session management const errorParts = [ diff --git a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts index 4616b8592..9bfeb4fcb 100644 --- a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts +++ b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.test.ts @@ -77,6 +77,7 @@ describe('ToolCallEmitter', () => { locations: [], kind: 'other', rawInput: { arg1: 'value1' }, + _meta: { toolName: 'unknown_tool' }, }); }); @@ -100,6 +101,7 @@ describe('ToolCallEmitter', () => { locations: [{ path: '/test/file.ts', line: 10 }], kind: 'edit', rawInput: { path: '/test.ts' }, + _meta: { toolName: 'edit_file' }, }); }); @@ -123,6 +125,7 @@ describe('ToolCallEmitter', () => { expect(sendUpdateSpy).toHaveBeenCalledWith( expect.objectContaining({ rawInput: {}, + _meta: { toolName: 'test_tool' }, }), ); }); @@ -150,6 +153,7 @@ describe('ToolCallEmitter', () => { locations: [], // Fallback to empty kind: 'other', // Fallback to other rawInput: { invalid: true }, + _meta: { toolName: 'failing_tool' }, }); }); }); @@ -170,6 +174,7 @@ describe('ToolCallEmitter', () => { toolCallId: 'call-123', status: 'completed', rawOutput: 'Tool completed successfully', + _meta: { toolName: 'test_tool' }, }), ); }); @@ -193,6 +198,7 @@ describe('ToolCallEmitter', () => { content: { type: 'text', text: 'Something went wrong' }, }, ], + _meta: { toolName: 'test_tool' }, }); }); @@ -222,6 +228,7 @@ describe('ToolCallEmitter', () => { newText: 'new content', }, ], + _meta: { toolName: 'edit_file' }, }), ); }); @@ -247,6 +254,7 @@ describe('ToolCallEmitter', () => { }, ], rawOutput: 'raw output', + _meta: { toolName: 'test_tool' }, }), ); }); @@ -264,6 +272,7 @@ describe('ToolCallEmitter', () => { toolCallId: 'call-empty', status: 'completed', content: [], + _meta: { toolName: 'test_tool' }, }); }); @@ -343,7 +352,7 @@ describe('ToolCallEmitter', () => { it('should emit tool_call_update with failed status and error message', async () => { const error = new Error('Connection timeout'); - await emitter.emitError('call-123', error); + await emitter.emitError('call-123', 'test_tool', error); expect(sendUpdateSpy).toHaveBeenCalledWith({ sessionUpdate: 'tool_call_update', @@ -355,6 +364,7 @@ describe('ToolCallEmitter', () => { content: { type: 'text', text: 'Connection timeout' }, }, ], + _meta: { toolName: 'test_tool' }, }); }); }); @@ -498,6 +508,7 @@ describe('ToolCallEmitter', () => { }, ], rawOutput: { unknownField: 'value', nested: { data: 123 } }, + _meta: { toolName: 'test_tool' }, }), ); }); @@ -519,6 +530,7 @@ describe('ToolCallEmitter', () => { toolCallId: 'call-extra', status: 'completed', rawOutput: 'Result text', + _meta: { toolName: 'test_tool' }, }), ); }); @@ -533,6 +545,7 @@ describe('ToolCallEmitter', () => { const call = sendUpdateSpy.mock.calls[0][0]; expect(call.rawOutput).toBeUndefined(); + expect(call._meta).toEqual({ toolName: 'test_tool' }); }); }); @@ -623,6 +636,7 @@ describe('ToolCallEmitter', () => { content: { type: 'text', text: 'Text content from message' }, }, ], + _meta: { toolName: 'test_tool' }, }); }); @@ -654,6 +668,7 @@ describe('ToolCallEmitter', () => { }, ], rawOutput: 'raw result', + _meta: { toolName: 'test_tool' }, }), ); }); diff --git a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts index 9859ed78e..9ff3e34c8 100644 --- a/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts +++ b/packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts @@ -65,6 +65,7 @@ export class ToolCallEmitter extends BaseEmitter { locations, kind, rawInput: params.args ?? {}, + _meta: { toolName: params.toolName }, }); return true; @@ -120,6 +121,7 @@ export class ToolCallEmitter extends BaseEmitter { toolCallId: params.callId, status: params.success ? 'completed' : 'failed', content: contentArray, + _meta: { toolName: params.toolName }, }; // Add rawOutput from resultDisplay @@ -137,7 +139,11 @@ export class ToolCallEmitter extends BaseEmitter { * @param callId - The tool call ID * @param error - The error that occurred */ - async emitError(callId: string, error: Error): Promise { + async emitError( + callId: string, + toolName: string, + error: Error, + ): Promise { await this.sendUpdate({ sessionUpdate: 'tool_call_update', toolCallId: callId, @@ -145,6 +151,7 @@ export class ToolCallEmitter extends BaseEmitter { content: [ { type: 'content', content: { type: 'text', text: error.message } }, ], + _meta: { toolName }, }); }