Skip to content

Commit fddcc03

Browse files
authored
[codex] Summarize collapsed code searcher (#645)
1 parent 03a335e commit fddcc03

4 files changed

Lines changed: 172 additions & 27 deletions

File tree

cli/src/components/blocks/agent-branch-wrapper.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ import {
2323
processBlocks,
2424
type BlockProcessorHandlers,
2525
} from '../../utils/block-processor'
26-
import { shouldRenderAsSimpleText, isMultiPromptEditor } from '../../utils/constants'
26+
import { getCodeSearcherCollapsedPreview } from '../../utils/code-search-summary'
27+
import {
28+
shouldRenderAsSimpleText,
29+
isMultiPromptEditor,
30+
} from '../../utils/constants'
2731
import {
2832
isImplementorAgent,
2933
getImplementorIndex,
@@ -65,6 +69,11 @@ function getCollapsedPreview(
6569
}
6670
}
6771

72+
const codeSearcherPreview = getCodeSearcherCollapsedPreview(agentBlock)
73+
if (codeSearcherPreview) {
74+
return codeSearcherPreview
75+
}
76+
6877
// Default preview: use the displayed prompt or first line of text content.
6978
const displayPrompt = getAgentDisplayPrompt(agentBlock)
7079
if (displayPrompt) {
@@ -357,8 +366,12 @@ export const AgentBranchWrapper = memo(
357366
b.type === 'tool' && b.toolName === 'set_output',
358367
)
359368
// set_output wraps data in a 'data' property, so we need to access input.data
360-
const outputData = (setOutputBlock?.input as { data?: Record<string, unknown> })?.data
361-
const implementationId = outputData?.implementationId as string | undefined
369+
const outputData = (
370+
setOutputBlock?.input as { data?: Record<string, unknown> }
371+
)?.data
372+
const implementationId = outputData?.implementationId as
373+
| string
374+
| undefined
362375
if (implementationId) {
363376
const letterIndex = implementationId.charCodeAt(0) - 65
364377
const implementors = siblingBlocks.filter(

cli/src/components/tools/code-search.tsx

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22

33
import { SimpleToolCallItem } from './tool-call-item'
44
import { defineToolComponent } from './types'
5+
import { countCodeSearchResults } from '../../utils/code-search-summary'
56

67
import type { ToolRenderConfig } from './types'
78

@@ -18,30 +19,7 @@ export const CodeSearchComponent = defineToolComponent({
1819
const pattern = input?.pattern ?? ''
1920
const cwd = input?.cwd ?? ''
2021

21-
// Count results from output
22-
let totalResults = 0
23-
24-
if (toolBlock.output && typeof toolBlock.output === 'string') {
25-
const lines = toolBlock.output.split('\n')
26-
const matchCountLine = lines.find((line) =>
27-
/^Found \d+ matches?$/.test(line.trim()),
28-
)
29-
const parsedTotalResults = matchCountLine
30-
?.trim()
31-
.match(/^Found (\d+) matches?$/)?.[1]
32-
33-
if (parsedTotalResults !== undefined) {
34-
totalResults = Number(parsedTotalResults)
35-
} else {
36-
for (const line of lines) {
37-
const trimmed = line.trim()
38-
39-
if (/^(?:Line\s+)?\d+:/.test(trimmed)) {
40-
totalResults++
41-
}
42-
}
43-
}
44-
}
22+
const totalResults = countCodeSearchResults(toolBlock.output)
4523

4624
// Build single-line summary
4725
let summary = ''
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, expect, test } from 'bun:test'
2+
3+
import {
4+
countCodeSearchResults,
5+
getCodeSearcherCollapsedPreview,
6+
} from '../code-search-summary'
7+
8+
import type { AgentContentBlock, ToolContentBlock } from '../../types/chat'
9+
10+
const createCodeSearchToolBlock = (
11+
output: string,
12+
id = 'tool-1',
13+
): ToolContentBlock => ({
14+
type: 'tool',
15+
toolCallId: id,
16+
toolName: 'code_search',
17+
input: { pattern: 'MODEL_ID' },
18+
output,
19+
})
20+
21+
const createCodeSearcherBlock = (
22+
options: Partial<AgentContentBlock> = {},
23+
): AgentContentBlock => ({
24+
type: 'agent',
25+
agentId: 'agent-1',
26+
agentName: 'code-searcher',
27+
agentType: 'code-searcher',
28+
content: '',
29+
status: 'complete',
30+
params: {
31+
searchQueries: [
32+
{ pattern: 'FREEBUFF_MODEL_SELECTOR_MODELS' },
33+
{ pattern: 'FREEBUFF_MODEL_SELECTOR_MODEL_IDS' },
34+
{ pattern: 'DEFAULT_FREEBUFF_MODEL_ID' },
35+
],
36+
},
37+
blocks: [],
38+
...options,
39+
})
40+
41+
describe('code search summary helpers', () => {
42+
test('counts formatted code search matches from stdout', () => {
43+
expect(
44+
countCodeSearchResults(`stdout: |-
45+
Found 2 matches
46+
./message-block-helpers.ts:
47+
Line 13: export const getAgentBaseName = (type: string): string => {
48+
Line 196: getAgentBaseName(options.agentType ?? '') === 'code-searcher'`),
49+
).toBe(2)
50+
})
51+
52+
test('summarizes collapsed code-searcher searches and results', () => {
53+
const agentBlock = createCodeSearcherBlock({
54+
blocks: [
55+
createCodeSearchToolBlock('Found 7 matches', 'tool-1'),
56+
createCodeSearchToolBlock('Found 2 matches', 'tool-2'),
57+
createCodeSearchToolBlock('Found 7 matches', 'tool-3'),
58+
],
59+
})
60+
61+
expect(getCodeSearcherCollapsedPreview(agentBlock)).toBe(
62+
'3 searches · 16 results',
63+
)
64+
})
65+
66+
test('shows search count before tool outputs arrive', () => {
67+
expect(getCodeSearcherCollapsedPreview(createCodeSearcherBlock())).toBe(
68+
'3 searches',
69+
)
70+
})
71+
72+
test('handles singular labels', () => {
73+
const agentBlock = createCodeSearcherBlock({
74+
params: {
75+
searchQueries: [{ pattern: 'DEFAULT_FREEBUFF_MODEL_ID' }],
76+
},
77+
blocks: [createCodeSearchToolBlock('Found 1 match')],
78+
})
79+
80+
expect(getCodeSearcherCollapsedPreview(agentBlock)).toBe(
81+
'1 search · 1 result',
82+
)
83+
})
84+
})
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { getAgentBaseName } from './message-block-helpers'
2+
3+
import type {
4+
AgentContentBlock,
5+
ContentBlock,
6+
ToolContentBlock,
7+
} from '../types/chat'
8+
9+
export function countCodeSearchResults(output?: string): number {
10+
if (!output) {
11+
return 0
12+
}
13+
14+
const lines = output.split('\n')
15+
const matchCountLine = lines.find((line) =>
16+
/^Found \d+ match(?:es)?$/.test(line.trim()),
17+
)
18+
const parsedTotalResults = matchCountLine
19+
?.trim()
20+
.match(/^Found (\d+) match(?:es)?$/)?.[1]
21+
22+
if (parsedTotalResults !== undefined) {
23+
return Number(parsedTotalResults)
24+
}
25+
26+
return lines.reduce((total, line) => {
27+
const trimmed = line.trim()
28+
return /^(?:Line\s+)?\d+:/.test(trimmed) ? total + 1 : total
29+
}, 0)
30+
}
31+
32+
const pluralize = (count: number, singular: string, plural = `${singular}s`) =>
33+
`${count} ${count === 1 ? singular : plural}`
34+
35+
const isCodeSearchToolBlock = (
36+
block: ContentBlock,
37+
): block is ToolContentBlock =>
38+
block.type === 'tool' && block.toolName === 'code_search'
39+
40+
export function getCodeSearcherCollapsedPreview(
41+
agentBlock: AgentContentBlock,
42+
): string | undefined {
43+
if (getAgentBaseName(agentBlock.agentType) !== 'code-searcher') {
44+
return undefined
45+
}
46+
47+
const toolBlocks = (agentBlock.blocks ?? []).filter(isCodeSearchToolBlock)
48+
const searchQueries = Array.isArray(agentBlock.params?.searchQueries)
49+
? agentBlock.params.searchQueries
50+
: []
51+
const searchCount = searchQueries.length || toolBlocks.length
52+
53+
if (searchCount === 0) {
54+
return undefined
55+
}
56+
57+
const completedToolBlocks = toolBlocks.filter((block) => block.output)
58+
const searchLabel = pluralize(searchCount, 'search', 'searches')
59+
60+
if (completedToolBlocks.length === 0) {
61+
return searchLabel
62+
}
63+
64+
const totalResults = completedToolBlocks.reduce(
65+
(total, block) => total + countCodeSearchResults(block.output),
66+
0,
67+
)
68+
69+
return `${searchLabel} · ${pluralize(totalResults, 'result')}`
70+
}

0 commit comments

Comments
 (0)