Skip to content

Commit 212f6a7

Browse files
committed
best of n: number the duplicate models
1 parent f080223 commit 212f6a7

File tree

3 files changed

+130
-46
lines changed

3 files changed

+130
-46
lines changed

cli/src/components/message-block.tsx

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
1-
import { TextAttributes } from '@opentui/core'
21
import { pluralize } from '@codebuff/common/util/string'
2+
import { TextAttributes } from '@opentui/core'
33
import React, { memo, useCallback, useMemo, type ReactNode } from 'react'
44

5-
import {
6-
shouldRenderAsSimpleText,
7-
isImplementorAgent,
8-
getImplementorDisplayName,
9-
} from '../utils/constants'
105
import { AgentBranchItem } from './agent-branch-item'
116
import { ElapsedTimer } from './elapsed-timer'
127
import { FeedbackIconButton } from './feedback-icon-button'
138
import { useTheme } from '../hooks/use-theme'
149
import { useWhyDidYouUpdateById } from '../hooks/use-why-did-you-update'
15-
import { isTextBlock, isToolBlock } from '../types/chat'
16-
import { type MarkdownPalette } from '../utils/markdown-renderer'
1710
import {
1811
useFeedbackStore,
1912
selectIsFeedbackOpenForMessage,
2013
selectHasSubmittedFeedback,
2114
selectMessageFeedbackCategory,
2215
} from '../state/feedback-store'
16+
import { isTextBlock, isToolBlock } from '../types/chat'
17+
import { shouldRenderAsSimpleText } from '../utils/constants'
18+
import {
19+
isImplementorAgent,
20+
getImplementorDisplayName,
21+
getImplementorIndex,
22+
} from '../utils/implementor-helpers'
23+
import { type MarkdownPalette } from '../utils/markdown-renderer'
24+
import { AgentListBranch } from './blocks/agent-list-branch'
25+
import { ContentWithMarkdown } from './blocks/content-with-markdown'
26+
import { ThinkingBlock } from './blocks/thinking-block'
27+
import { ToolBranch } from './blocks/tool-branch'
28+
import { PlanBox } from './renderers/plan-box'
2329

2430
import type {
2531
ContentBlock,
@@ -28,12 +34,6 @@ import type {
2834
AgentContentBlock,
2935
} from '../types/chat'
3036
import type { ThemeColor } from '../types/theme-system'
31-
import { ThinkingBlock } from './blocks/thinking-block'
32-
import { ContentWithMarkdown } from './blocks/content-with-markdown'
33-
import { ToolBranch } from './blocks/tool-branch'
34-
import { PlanBox } from './renderers/plan-box'
35-
import { AgentListBranch } from './blocks/agent-list-branch'
36-
import { BULLET_CHAR } from '../utils/strings'
3737

3838
interface MessageBlockProps {
3939
messageId: string
@@ -378,6 +378,24 @@ const AgentBody = memo(
378378
const nestedBlocks = agentBlock.blocks ?? []
379379
const nodes: React.ReactNode[] = []
380380

381+
// Pre-calculate numbering for all implementor siblings
382+
const implementorIndexMap = new Map<string, number>()
383+
nestedBlocks
384+
.filter(
385+
(block): block is AgentContentBlock =>
386+
block.type === 'agent' && isImplementorAgent(block.agentType),
387+
)
388+
.forEach((block) => {
389+
const index = getImplementorIndex(
390+
block.agentId,
391+
block.agentType,
392+
nestedBlocks,
393+
)
394+
if (index !== undefined) {
395+
implementorIndexMap.set(block.agentId, index)
396+
}
397+
})
398+
381399
const getAgentMarkdownOptions = useCallback(
382400
(indent: number) => {
383401
const indentationOffset = indent * 2
@@ -537,6 +555,7 @@ const AgentBody = memo(
537555

538556
case 'agent': {
539557
const agentBlock = nestedBlock as AgentContentBlock
558+
const numbering = implementorIndexMap.get(agentBlock.agentId)
540559
nodes.push(
541560
<AgentBranchWrapper
542561
key={`${keyPrefix}-agent-${nestedIdx}`}
@@ -549,6 +568,7 @@ const AgentBody = memo(
549568
onToggleCollapsed={onToggleCollapsed}
550569
onBuildFast={onBuildFast}
551570
onBuildMax={onBuildMax}
571+
implementorIndex={numbering}
552572
/>,
553573
)
554574
nestedIdx++
@@ -571,6 +591,7 @@ interface AgentBranchWrapperProps {
571591
onToggleCollapsed: (id: string) => void
572592
onBuildFast: () => void
573593
onBuildMax: () => void
594+
implementorIndex?: number
574595
}
575596

576597
const AgentBranchWrapper = memo(
@@ -584,6 +605,7 @@ const AgentBranchWrapper = memo(
584605
onToggleCollapsed,
585606
onBuildFast,
586607
onBuildMax,
608+
implementorIndex,
587609
}: AgentBranchWrapperProps) => {
588610
const theme = useTheme()
589611

@@ -627,7 +649,10 @@ const AgentBranchWrapper = memo(
627649
streamingAgents.has(agentBlock.agentId)
628650
const isComplete = agentBlock.status === 'complete'
629651
const isFailed = agentBlock.status === 'failed'
630-
const displayName = getImplementorDisplayName(agentBlock.agentType)
652+
const displayName = getImplementorDisplayName(
653+
agentBlock.agentType,
654+
implementorIndex,
655+
)
631656
const statusIndicator = isStreaming
632657
? '●'
633658
: isFailed
@@ -799,6 +824,7 @@ interface SingleBlockProps {
799824
onToggleCollapsed: (id: string) => void
800825
onBuildFast: () => void
801826
onBuildMax: () => void
827+
implementorIndex?: number
802828
}
803829

804830
const SingleBlock = memo(
@@ -817,6 +843,7 @@ const SingleBlock = memo(
817843
onToggleCollapsed,
818844
onBuildFast,
819845
onBuildMax,
846+
implementorIndex,
820847
}: SingleBlockProps): ReactNode => {
821848
const theme = useTheme()
822849
const codeBlockWidth = Math.max(10, availableWidth - 8)
@@ -913,6 +940,7 @@ const SingleBlock = memo(
913940
onToggleCollapsed={onToggleCollapsed}
914941
onBuildFast={onBuildFast}
915942
onBuildMax={onBuildMax}
943+
implementorIndex={implementorIndex}
916944
/>
917945
)
918946
}
@@ -965,6 +993,24 @@ const BlocksRenderer = memo(
965993
onBuildMax,
966994
}: BlocksRendererProps) => {
967995
const nodes: React.ReactNode[] = []
996+
997+
// Pre-calculate numbering for all implementor siblings at the top level
998+
const topLevelImplementorIndexMap = new Map<string, number>()
999+
sourceBlocks
1000+
.filter(
1001+
(block): block is AgentContentBlock =>
1002+
block.type === 'agent' && isImplementorAgent(block.agentType),
1003+
)
1004+
.forEach((block) => {
1005+
const index = getImplementorIndex(
1006+
block.agentId,
1007+
block.agentType,
1008+
sourceBlocks,
1009+
)
1010+
if (index !== undefined) {
1011+
topLevelImplementorIndexMap.set(block.agentId, index)
1012+
}
1013+
})
9681014
for (let i = 0; i < sourceBlocks.length; ) {
9691015
const block = sourceBlocks[i]
9701016
// Handle reasoning text blocks
@@ -1045,6 +1091,10 @@ const BlocksRenderer = memo(
10451091
continue
10461092
}
10471093

1094+
const numbering =
1095+
block.type === 'agent'
1096+
? topLevelImplementorIndexMap.get(block.agentId)
1097+
: undefined
10481098
nodes.push(
10491099
<SingleBlock
10501100
key={`${messageId}-block-${i}`}
@@ -1062,6 +1112,7 @@ const BlocksRenderer = memo(
10621112
onToggleCollapsed={onToggleCollapsed}
10631113
onBuildFast={onBuildFast}
10641114
onBuildMax={onBuildMax}
1115+
implementorIndex={numbering}
10651116
/>,
10661117
)
10671118
i++

cli/src/utils/constants.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,37 +40,6 @@ export const shouldRenderAsSimpleText = (agentType: string): boolean => {
4040
)
4141
}
4242

43-
// Agent IDs that should render as simple tool calls (custom UI)
44-
export const IMPLEMENTOR_AGENT_IDS = [
45-
'editor-implementor',
46-
'editor-implementor-gemini',
47-
'editor-implementor-gpt-5',
48-
] as const
49-
50-
/**
51-
* Check if an agent is an implementor that should render as a simple tool call
52-
*/
53-
export const isImplementorAgent = (agentType: string): boolean => {
54-
return IMPLEMENTOR_AGENT_IDS.some((implementorId) =>
55-
agentType.includes(implementorId),
56-
)
57-
}
58-
59-
/**
60-
* Get the display name for an implementor agent
61-
*/
62-
export const getImplementorDisplayName = (agentType: string): string => {
63-
if (agentType.includes('editor-implementor-gemini')) {
64-
return 'Gemini'
65-
}
66-
if (agentType.includes('editor-implementor-gpt-5')) {
67-
return 'GPT-5'
68-
}
69-
if (agentType.includes('editor-implementor')) {
70-
return 'Sonnet'
71-
}
72-
return 'Implementor'
73-
}
7443

7544
/**
7645
* The parent agent ID for all root-level agents
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { AgentContentBlock, ContentBlock } from '../types/chat'
2+
3+
export const IMPLEMENTOR_AGENT_IDS = [
4+
'editor-implementor',
5+
'editor-implementor-gemini',
6+
'editor-implementor-gpt-5',
7+
] as const
8+
9+
/**
10+
* Check if an agent is an implementor that should render as a simple tool call
11+
*/
12+
export const isImplementorAgent = (agentType: string): boolean => {
13+
return IMPLEMENTOR_AGENT_IDS.some((implementorId) =>
14+
agentType.includes(implementorId),
15+
)
16+
}
17+
18+
/**
19+
* Get the display name for an implementor agent
20+
*/
21+
export const getImplementorDisplayName = (
22+
agentType: string,
23+
index?: number,
24+
): string => {
25+
let baseName = 'Implementor'
26+
if (agentType.includes('editor-implementor-gemini')) {
27+
baseName = 'Gemini'
28+
} else if (agentType.includes('editor-implementor-gpt-5')) {
29+
baseName = 'GPT-5'
30+
} else if (agentType.includes('editor-implementor')) {
31+
baseName = 'Sonnet'
32+
}
33+
34+
// Only add numbering if index is provided
35+
if (index !== undefined) {
36+
return `${baseName} #${index + 1}`
37+
}
38+
39+
return baseName
40+
}
41+
42+
/**
43+
* Calculate implementor numbering for siblings by comparing agent types directly
44+
* Returns the index if there are multiple of the same type, undefined otherwise
45+
*/
46+
export const getImplementorIndex = (
47+
currentAgentId: string,
48+
currentAgentType: string,
49+
siblingBlocks: ContentBlock[],
50+
): number | undefined => {
51+
if (!isImplementorAgent(currentAgentType)) return undefined
52+
53+
// Find all siblings with the same agent type
54+
const sameTypeImplementors = siblingBlocks.filter(
55+
(block): block is AgentContentBlock =>
56+
block.type === 'agent' && block.agentType === currentAgentType,
57+
)
58+
59+
if (sameTypeImplementors.length <= 1) return undefined
60+
61+
return sameTypeImplementors.findIndex(
62+
(block) => block.agentId === currentAgentId,
63+
)
64+
}

0 commit comments

Comments
 (0)