Skip to content

Commit 17a4e57

Browse files
committed
For "last_message" output mode, return the entire assistant response, including tools and results
1 parent 6eff7d4 commit 17a4e57

File tree

6 files changed

+53
-15
lines changed

6 files changed

+53
-15
lines changed

common/src/types/session-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const AgentOutputSchema = z.discriminatedUnion('type', [
4848
}),
4949
z.object({
5050
type: z.literal('lastMessage'),
51-
value: z.any(),
51+
value: z.array(z.any()), // Array of assistant and tool messages from the last turn, including tool results
5252
}),
5353
z.object({
5454
type: z.literal('allMessages'),

packages/agent-runtime/src/__tests__/cost-aggregation.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe('Cost Aggregation System', () => {
159159
stepsRemaining: 10,
160160
creditsUsed: 75, // First subagent uses 75 credits
161161
},
162-
output: { type: 'lastMessage', value: 'Sub-agent 1 response' },
162+
output: { type: 'lastMessage', value: [assistantMessage('Sub-agent 1 response')] },
163163
})
164164
.mockResolvedValueOnce({
165165
agentState: {
@@ -169,7 +169,7 @@ describe('Cost Aggregation System', () => {
169169
stepsRemaining: 10,
170170
creditsUsed: 100, // Second subagent uses 100 credits
171171
},
172-
output: { type: 'lastMessage', value: 'Sub-agent 2 response' },
172+
output: { type: 'lastMessage', value: [assistantMessage('Sub-agent 2 response')] },
173173
})
174174

175175
const mockToolCall = {
@@ -223,7 +223,7 @@ describe('Cost Aggregation System', () => {
223223
stepsRemaining: 10,
224224
creditsUsed: 50, // Successful agent
225225
},
226-
output: { type: 'lastMessage', value: 'Successful response' },
226+
output: { type: 'lastMessage', value: [assistantMessage('Successful response')] },
227227
})
228228
.mockRejectedValueOnce(
229229
(() => {
@@ -370,7 +370,7 @@ describe('Cost Aggregation System', () => {
370370
stepsRemaining: 10,
371371
creditsUsed: subAgent1Cost,
372372
} as AgentState,
373-
output: { type: 'lastMessage', value: 'Sub-agent 1 response' },
373+
output: { type: 'lastMessage', value: [assistantMessage('Sub-agent 1 response')] },
374374
})
375375
.mockResolvedValueOnce({
376376
agentState: {
@@ -381,7 +381,7 @@ describe('Cost Aggregation System', () => {
381381
stepsRemaining: 10,
382382
creditsUsed: subAgent2Cost,
383383
} as AgentState,
384-
output: { type: 'lastMessage', value: 'Sub-agent 2 response' },
384+
output: { type: 'lastMessage', value: [assistantMessage('Sub-agent 2 response')] },
385385
})
386386

387387
const mockToolCall = {

packages/agent-runtime/src/__tests__/spawn-agents-message-history.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('Spawn Agents Message History', () => {
5252
assistantMessage('Mock agent response'),
5353
],
5454
},
55-
output: { type: 'lastMessage', value: 'Mock agent response' },
55+
output: { type: 'lastMessage', value: [assistantMessage('Mock agent response')] },
5656
}
5757
})
5858

packages/agent-runtime/src/__tests__/spawn-agents-permissions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('Spawn Agents Permissions', () => {
8585
...options.agentState,
8686
messageHistory: [assistantMessage('Mock agent response')],
8787
},
88-
output: { type: 'lastMessage', value: 'Mock agent response' },
88+
output: { type: 'lastMessage', value: [assistantMessage('Mock agent response')] },
8989
}
9090
})
9191
})

packages/agent-runtime/src/__tests__/subagent-streaming.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('Subagent Streaming', () => {
9696
...options.agentState,
9797
messageHistory: [assistantMessage('Test response from subagent')],
9898
},
99-
output: { type: 'lastMessage', value: 'Test response from subagent' },
99+
output: { type: 'lastMessage', value: [assistantMessage('Test response from subagent')] },
100100
}
101101
})
102102

packages/agent-runtime/src/util/agent-output.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,49 @@
11
import type { AgentTemplate } from '@codebuff/common/types/agent-template'
2-
import type { AssistantMessage } from '@codebuff/common/types/messages/codebuff-message'
2+
import type { Message } from '@codebuff/common/types/messages/codebuff-message'
33
import type {
44
AgentState,
55
AgentOutput,
66
} from '@codebuff/common/types/session-state'
77

8+
/**
9+
* Get the last assistant turn messages, which includes the last assistant message
10+
* and any subsequent tool messages that are responses to its tool calls.
11+
*/
12+
function getLastAssistantTurnMessages(messageHistory: Message[]): Message[] {
13+
// Find the index of the last assistant message
14+
let lastAssistantIndex = -1
15+
for (let i = messageHistory.length - 1; i >= 0; i--) {
16+
if (messageHistory[i].role === 'assistant') {
17+
lastAssistantIndex = i
18+
break
19+
}
20+
}
21+
22+
for (let i = lastAssistantIndex; i >= 0; i--) {
23+
if (messageHistory[i].role === 'assistant') {
24+
lastAssistantIndex = i
25+
} else break
26+
}
27+
28+
if (lastAssistantIndex === -1) {
29+
return []
30+
}
31+
32+
// Collect the assistant message and all subsequent tool messages
33+
const result: Message[] = []
34+
for (let i = lastAssistantIndex; i < messageHistory.length; i++) {
35+
const message = messageHistory[i]
36+
if (message.role === 'assistant' || message.role === 'tool') {
37+
result.push(message)
38+
} else {
39+
// Stop if we hit a user or system message
40+
break
41+
}
42+
}
43+
44+
return result
45+
}
46+
847
export function getAgentOutput(
948
agentState: AgentState,
1049
agentTemplate: AgentTemplate,
@@ -16,19 +55,18 @@ export function getAgentOutput(
1655
}
1756
}
1857
if (agentTemplate.outputMode === 'last_message') {
19-
const assistantMessages = agentState.messageHistory.filter(
20-
(message): message is AssistantMessage => message.role === 'assistant',
58+
const lastTurnMessages = getLastAssistantTurnMessages(
59+
agentState.messageHistory,
2160
)
22-
const lastAssistantMessage = assistantMessages[assistantMessages.length - 1]
23-
if (!lastAssistantMessage) {
61+
if (lastTurnMessages.length === 0) {
2462
return {
2563
type: 'error',
2664
message: 'No response from agent',
2765
}
2866
}
2967
return {
3068
type: 'lastMessage',
31-
value: lastAssistantMessage.content,
69+
value: lastTurnMessages,
3270
}
3371
}
3472
if (agentTemplate.outputMode === 'all_messages') {

0 commit comments

Comments
 (0)