Skip to content

Commit 9049936

Browse files
Refactor streaming architecture to fix agent content attribution
Implemented robust tool XML filtering and agent content routing to resolve issues with nested agent streaming where output was being lost, duplicated, or incorrectly attributed. Key improvements: - SDK: Rewrote tool XML filtering with proper state machines and partial tag matching to handle streaming text at buffer boundaries without data loss - Backend: Modified spawn-agents to route text chunks through sendSubagentChunk and forward print mode events with agent IDs for proper attribution - CLI: Enhanced agent content tracking with duplicate detection, proper content field synchronization, and comprehensive logging for debugging - Dev: Simplified development workflow by running CLI directly via bun - Cleanup: Removed duplicate ToolName export from SDK The new architecture ensures streaming content flows correctly through the agent hierarchy, preventing edge cases where tool XML tags could corrupt output or cause content to be misattributed to the wrong agent. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent 9d329aa commit 9049936

File tree

4 files changed

+276
-82
lines changed

4 files changed

+276
-82
lines changed

backend/src/tools/handlers/tool/spawn-agents.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,22 @@ export const handleSpawnAgents = ((
135135
isOnlyChild: agents.length === 1,
136136
parentSystemPrompt,
137137
onResponseChunk: (chunk: string | PrintModeEvent) => {
138-
if (agents.length === 1) {
139-
writeToClient(chunk)
140-
}
141-
if (typeof chunk !== 'string') {
138+
if (typeof chunk === 'string') {
139+
sendSubagentChunk({
140+
userInputId,
141+
agentId: subAgentState.agentId,
142+
agentType,
143+
chunk,
144+
prompt,
145+
})
142146
return
143147
}
144-
// Send subagent streaming chunks to client
145-
sendSubagentChunk({
146-
userInputId,
148+
149+
const eventWithAgent = {
150+
...chunk,
147151
agentId: subAgentState.agentId,
148-
agentType,
149-
chunk,
150-
prompt,
151-
})
152+
}
153+
writeToClient(eventWithAgent)
152154
},
153155
})
154156
return { ...result, agentType, agentName: agentTemplate.displayName }

cli/src/hooks/use-send-message.ts

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ export const useSendMessage = ({
157157
| { type: 'text'; content: string }
158158
| Extract<ContentBlock, { type: 'tool' }>,
159159
) => {
160+
const preview =
161+
update.type === 'text'
162+
? update.content.slice(0, 120)
163+
: JSON.stringify({ toolName: update.toolName }).slice(0, 120)
164+
logger.info('updateAgentContent invoked', {
165+
agentId,
166+
updateType: update.type,
167+
preview,
168+
})
160169
setMessages((prev) =>
161170
prev.map((msg) => {
162171
if (msg.id === aiMessageId && msg.blocks) {
@@ -168,18 +177,48 @@ export const useSendMessage = ({
168177
if (update.type === 'text' && update.content) {
169178
const lastBlock = agentBlocks[agentBlocks.length - 1]
170179
if (lastBlock && lastBlock.type === 'text') {
180+
if (update.content && lastBlock.content.endsWith(update.content)) {
181+
logger.info('Skipping duplicate agent text append', {
182+
agentId,
183+
preview,
184+
})
185+
return block
186+
}
171187
const updatedLastBlock: ContentBlock = {
172188
...lastBlock,
173189
content: lastBlock.content + update.content,
174190
}
191+
const updatedContent =
192+
(block.content ?? '') + update.content
193+
logger.info('Agent block text appended', {
194+
agentId,
195+
appendedLength: update.content.length,
196+
totalLength: updatedContent.length,
197+
})
175198
return {
176199
...block,
200+
content: updatedContent,
177201
blocks: [...agentBlocks.slice(0, -1), updatedLastBlock],
178202
}
179203
} else {
180-
return { ...block, blocks: [...agentBlocks, update] }
204+
const updatedContent =
205+
(block.content ?? '') + update.content
206+
logger.info('Agent block text started', {
207+
agentId,
208+
appendedLength: update.content.length,
209+
totalLength: updatedContent.length,
210+
})
211+
return {
212+
...block,
213+
content: updatedContent,
214+
blocks: [...agentBlocks, update],
215+
}
181216
}
182217
} else if (update.type === 'tool') {
218+
logger.info('Agent block tool appended', {
219+
agentId,
220+
toolName: update.toolName,
221+
})
183222
return { ...block, blocks: [...agentBlocks, update] }
184223
}
185224
}
@@ -348,35 +387,29 @@ export const useSendMessage = ({
348387
'',
349388
)
350389

390+
if (text.includes('<codebuff_tool_call>')) {
391+
logger.warn('Tool XML detected in text event post-filter', {
392+
agentId: event.agentId ?? 'root',
393+
textPreview: text.slice(0, 80),
394+
})
395+
}
396+
351397
if (!text) return
352398

353-
if (event.agentId) {
354-
logger.info('setMessages: text event with agentId', {
355-
agentId: event.agentId,
356-
textPreview: text.slice(0, 100),
357-
})
358-
setMessages((prev) =>
359-
prev.map((msg) => {
360-
if (msg.id === aiMessageId && msg.blocks) {
361-
const blocks = msg.blocks.map((block) => {
362-
if (
363-
block.type === 'agent' &&
364-
block.agentId === event.agentId
365-
) {
366-
return { ...block, content: block.content + text }
367-
}
368-
return block
369-
})
370-
return { ...msg, blocks }
371-
}
372-
return msg
373-
}),
374-
)
375-
return
376-
} else {
377-
logger.info('setMessages: text event without agentId', {
378-
textPreview: text.slice(0, 100),
379-
})
399+
if (event.agentId) {
400+
logger.info('setMessages: text event with agentId', {
401+
agentId: event.agentId,
402+
textPreview: text.slice(0, 100),
403+
})
404+
updateAgentContent(event.agentId, {
405+
type: 'text',
406+
content: text,
407+
})
408+
return
409+
} else {
410+
logger.info('setMessages: text event without agentId', {
411+
textPreview: text.slice(0, 100),
412+
})
380413
setMessages((prev) =>
381414
prev.map((msg) => {
382415
if (msg.id !== aiMessageId) {

sdk/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export type * from '../../common/src/types/messages/codebuff-message'
33
export type * from '../../common/src/types/messages/data-content'
44
export type * from '../../common/src/types/print-mode'
55
export type * from './run'
6-
export { type ToolName } from '../../common/src/tools/constants'
76
// Agent type exports
87
export type { AgentDefinition } from '../../common/src/templates/initial-agents-dir/types/agent-definition'
98
export type { ToolName } from '../../common/src/templates/initial-agents-dir/types/tools'

0 commit comments

Comments
 (0)