Skip to content

Commit aaa5b57

Browse files
committed
Don't add a user tool call message for add_message in handleSteps
1 parent eb9ca65 commit aaa5b57

File tree

2 files changed

+86
-15
lines changed

2 files changed

+86
-15
lines changed

backend/src/__tests__/run-programmatic-step.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,74 @@ describe('runProgrammaticStep', () => {
199199
})
200200

201201
describe('tool execution', () => {
202+
it('should not add tool call message for add_message tool', async () => {
203+
const mockGenerator = (function* () {
204+
yield {
205+
toolName: 'add_message',
206+
args: { role: 'user', content: 'Hello world' },
207+
}
208+
yield { toolName: 'read_files', args: { paths: ['test.txt'] } }
209+
yield { toolName: 'end_turn', args: {} }
210+
})() as StepGenerator
211+
212+
mockTemplate.handleSteps = () => mockGenerator
213+
mockTemplate.toolNames = ['add_message', 'read_files', 'end_turn']
214+
215+
// Track chunks sent via sendSubagentChunk
216+
const sentChunks: string[] = []
217+
const originalSendAction =
218+
require('../websockets/websocket-action').sendAction
219+
const sendActionSpy = spyOn(
220+
require('../websockets/websocket-action'),
221+
'sendAction'
222+
).mockImplementation((ws: any, action: any) => {
223+
if (action.type === 'subagent-response-chunk') {
224+
sentChunks.push(action.chunk)
225+
}
226+
})
227+
228+
const result = await runProgrammaticStep(mockAgentState, mockParams)
229+
230+
// Verify add_message tool was executed
231+
expect(executeToolCallSpy).toHaveBeenCalledWith(
232+
expect.objectContaining({
233+
toolName: 'add_message',
234+
args: { role: 'user', content: 'Hello world' },
235+
})
236+
)
237+
238+
// Verify read_files tool was executed
239+
expect(executeToolCallSpy).toHaveBeenCalledWith(
240+
expect.objectContaining({
241+
toolName: 'read_files',
242+
args: { paths: ['test.txt'] },
243+
})
244+
)
245+
246+
// Check that no tool call chunk was sent for add_message
247+
const addMessageToolCallChunk = sentChunks.find(
248+
(chunk) =>
249+
chunk.includes('add_message') && chunk.includes('Hello world')
250+
)
251+
expect(addMessageToolCallChunk).toBeUndefined()
252+
253+
// Check that tool call chunk WAS sent for read_files (normal behavior)
254+
const readFilesToolCallChunk = sentChunks.find(
255+
(chunk) => chunk.includes('read_files') && chunk.includes('test.txt')
256+
)
257+
expect(readFilesToolCallChunk).toBeDefined()
258+
259+
// Verify final message history doesn't contain add_message tool call
260+
const addMessageToolCallInHistory = result.agentState.messageHistory.find(
261+
(msg) =>
262+
typeof msg.content === 'string' &&
263+
msg.content.includes('add_message') &&
264+
msg.content.includes('Hello world')
265+
)
266+
expect(addMessageToolCallInHistory).toBeUndefined()
267+
268+
expect(result.endTurn).toBe(true)
269+
})
202270
it('should execute single tool call', async () => {
203271
const mockGenerator = (function* () {
204272
yield { toolName: 'read_files', args: { paths: ['test.txt'] } }

backend/src/run-programmatic-step.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,24 @@ export async function runProgrammaticStep(
182182
)
183183

184184
// Add user message with the tool call before executing it
185-
const toolCallString = getToolCallString(
186-
toolCall.toolName,
187-
toolCall.args,
188-
codebuffToolDefs[toolCall.toolName].endsAgentStep
189-
)
190-
state.messages.push({
191-
role: 'user' as const,
192-
content: asUserMessage(toolCallString),
193-
})
194-
state.sendSubagentChunk({
195-
userInputId,
196-
agentId: agentState.agentId,
197-
agentType: agentState.agentType!,
198-
chunk: toolCallString,
199-
})
185+
// Exception: don't add tool call message for add_message since it adds its own message
186+
if (toolCall.toolName !== 'add_message') {
187+
const toolCallString = getToolCallString(
188+
toolCall.toolName,
189+
toolCall.args,
190+
codebuffToolDefs[toolCall.toolName].endsAgentStep
191+
)
192+
state.messages.push({
193+
role: 'user' as const,
194+
content: asUserMessage(toolCallString),
195+
})
196+
state.sendSubagentChunk({
197+
userInputId,
198+
agentId: agentState.agentId,
199+
agentType: agentState.agentType!,
200+
chunk: toolCallString,
201+
})
202+
}
200203

201204
// Execute the tool synchronously and get the result immediately
202205
await executeToolCall({

0 commit comments

Comments
 (0)