mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): normalize non-sidechain text-only array user output as user message (#409)
The CLI wraps array-content user messages as agent output because isExternalUserMessage rejects non-string content. On the web side, detect text-only arrays in non-sidechain user output and emit them as role:'user' so they display in the user lane. Also handle sidechain user messages with mixed array content (e.g. tool_result + text) by extracting text parts into a sidechain block. Closes #407's original scope on top of the #402 base. via [HAPI](https://hapi.run) Co-authored-by: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -319,4 +319,57 @@ describe('normalizeDecryptedMessage', () => {
|
||||
parentUUID: null
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes non-sidechain text-only array-content user output as user message', () => {
|
||||
const message = makeMessage({
|
||||
role: 'agent',
|
||||
content: {
|
||||
type: 'output',
|
||||
data: {
|
||||
type: 'user',
|
||||
uuid: 'u5',
|
||||
isSidechain: false,
|
||||
message: { content: [{ type: 'text', text: 'Regular user message' }] }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const normalized = normalizeDecryptedMessage(message)
|
||||
|
||||
expect(normalized).toMatchObject({
|
||||
role: 'user',
|
||||
isSidechain: false,
|
||||
content: { type: 'text', text: 'Regular user message' }
|
||||
})
|
||||
})
|
||||
|
||||
it('treats sidechain user output with mixed tool_result + text array as sidechain', () => {
|
||||
const message = makeMessage({
|
||||
role: 'agent',
|
||||
content: {
|
||||
type: 'output',
|
||||
data: {
|
||||
type: 'user',
|
||||
uuid: 'u6',
|
||||
isSidechain: true,
|
||||
message: { content: [
|
||||
{ type: 'tool_result', tool_use_id: 'tc-1', content: 'result' },
|
||||
{ type: 'text', text: 'Some subagent text' }
|
||||
] }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const normalized = normalizeDecryptedMessage(message)
|
||||
|
||||
expect(normalized).toMatchObject({
|
||||
role: 'agent',
|
||||
isSidechain: true,
|
||||
})
|
||||
if (normalized?.role !== 'agent') throw new Error('Expected agent')
|
||||
expect(normalized.content[0]).toMatchObject({
|
||||
type: 'sidechain',
|
||||
prompt: 'Some subagent text'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -159,6 +159,27 @@ function normalizeUserOutput(
|
||||
}
|
||||
}
|
||||
|
||||
// Non-sidechain array content that is all text blocks — these are real
|
||||
// user messages that the CLI wrapped as agent output because
|
||||
// isExternalUserMessage rejects array content. Emit as role:'user' so
|
||||
// they display in the user lane.
|
||||
if (!isSidechain && Array.isArray(messageContent)) {
|
||||
const textParts = messageContent
|
||||
.filter((b: unknown) => isObject(b) && b.type === 'text' && typeof b.text === 'string')
|
||||
.map((b: Record<string, unknown>) => b.text as string)
|
||||
if (textParts.length > 0 && textParts.length === messageContent.length) {
|
||||
return {
|
||||
id: messageId,
|
||||
localId,
|
||||
createdAt,
|
||||
role: 'user',
|
||||
isSidechain: false,
|
||||
content: { type: 'text', text: textParts.join('\n\n') },
|
||||
meta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blocks: NormalizedAgentContent[] = []
|
||||
|
||||
if (Array.isArray(messageContent)) {
|
||||
|
||||
Reference in New Issue
Block a user