60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
import type {
|
|
RenderableMessage,
|
|
SystemStopHookSummaryMessage,
|
|
} from '../types/message.js'
|
|
|
|
function isLabeledHookSummary(
|
|
msg: RenderableMessage,
|
|
): msg is SystemStopHookSummaryMessage {
|
|
return (
|
|
msg.type === 'system' &&
|
|
msg.subtype === 'stop_hook_summary' &&
|
|
msg.hookLabel !== undefined
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Collapses consecutive hook summary messages with the same hookLabel
|
|
* (e.g. PostToolUse) into a single summary. This happens when parallel
|
|
* tool calls each emit their own hook summary.
|
|
*/
|
|
export function collapseHookSummaries(
|
|
messages: RenderableMessage[],
|
|
): RenderableMessage[] {
|
|
const result: RenderableMessage[] = []
|
|
let i = 0
|
|
|
|
while (i < messages.length) {
|
|
const msg = messages[i]!
|
|
if (isLabeledHookSummary(msg)) {
|
|
const label = msg.hookLabel
|
|
const group: SystemStopHookSummaryMessage[] = []
|
|
while (i < messages.length) {
|
|
const next = messages[i]!
|
|
if (!isLabeledHookSummary(next) || next.hookLabel !== label) break
|
|
group.push(next)
|
|
i++
|
|
}
|
|
if (group.length === 1) {
|
|
result.push(msg)
|
|
} else {
|
|
result.push({
|
|
...msg,
|
|
hookCount: group.reduce((sum, m) => sum + m.hookCount, 0),
|
|
hookInfos: group.flatMap(m => m.hookInfos),
|
|
hookErrors: group.flatMap(m => m.hookErrors),
|
|
preventedContinuation: group.some(m => m.preventedContinuation),
|
|
hasOutput: group.some(m => m.hasOutput),
|
|
// Parallel tool calls' hooks overlap; max is closest to wall-clock.
|
|
totalDurationMs: Math.max(...group.map(m => m.totalDurationMs ?? 0)),
|
|
})
|
|
}
|
|
} else {
|
|
result.push(msg)
|
|
i++
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|