Skip to content

Commit 5d1471d

Browse files
committed
refactor: extract test utilities and remove unused helpers
- Extract createMessage, createToolMessage, createMockLogger to test-utils.ts - Remove unused functions from implementor-helpers.ts (getLatestCommentary, countEdits, getEditedFiles, getEditedFileSnippets, getTotalStats) - Remove unused imports from implementor-row.tsx and message-block.tsx
1 parent 0bea455 commit 5d1471d

File tree

5 files changed

+81
-233
lines changed

5 files changed

+81
-233
lines changed

.agents/__tests__/context-pruner.test.ts

Lines changed: 11 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { describe, test, expect, beforeEach } from 'bun:test'
22

33
import contextPruner from '../context-pruner'
4+
import {
5+
createMessage,
6+
createTerminalToolMessage,
7+
createLargeToolMessage,
8+
createToolMessage,
9+
createMockLogger,
10+
} from './test-utils'
411

5-
import type { Message, ToolMessage } from '../types/util-types'
12+
import type { Message } from '../types/util-types'
613

714
describe('context-pruner handleSteps', () => {
815
let mockAgentState: any
@@ -13,62 +20,11 @@ describe('context-pruner handleSteps', () => {
1320
}
1421
})
1522

16-
const createMessage = (
17-
role: 'user' | 'assistant',
18-
content: string,
19-
): Message => ({
20-
role,
21-
content,
22-
})
23-
24-
const createTerminalToolMessage = (
25-
command: string,
26-
output: string,
27-
exitCode?: number,
28-
): ToolMessage => ({
29-
role: 'tool',
30-
toolCallId: 'test-id',
31-
toolName: 'run_terminal_command',
32-
content: [
33-
{
34-
type: 'json',
35-
value: {
36-
command,
37-
stdout: output,
38-
...(exitCode !== undefined && { exitCode }),
39-
},
40-
},
41-
],
42-
})
43-
44-
const createLargeToolMessage = (
45-
toolName: string,
46-
largeData: string,
47-
): ToolMessage => ({
48-
role: 'tool',
49-
toolCallId: 'test-id',
50-
toolName,
51-
content: [
52-
{
53-
type: 'json',
54-
value: {
55-
data: largeData,
56-
},
57-
},
58-
],
59-
})
60-
6123
const runHandleSteps = (messages: Message[]) => {
6224
mockAgentState.messageHistory = messages
63-
const mockLogger = {
64-
debug: () => {},
65-
info: () => {},
66-
warn: () => {},
67-
error: () => {},
68-
}
6925
const generator = contextPruner.handleSteps!({
7026
agentState: mockAgentState,
71-
logger: mockLogger,
27+
logger: createMockLogger(),
7228
})
7329
const results: any[] = []
7430
let result = generator.next()
@@ -299,43 +255,11 @@ describe('context-pruner edge cases', () => {
299255
}
300256
})
301257

302-
const createMessage = (
303-
role: 'user' | 'assistant',
304-
content: string,
305-
): Message => ({
306-
role,
307-
content,
308-
})
309-
310-
const createTerminalToolMessage = (
311-
command: string,
312-
output: string,
313-
): ToolMessage => ({
314-
role: 'tool',
315-
toolCallId: 'test-id',
316-
toolName: 'run_terminal_command',
317-
content: [
318-
{
319-
type: 'json',
320-
value: {
321-
command,
322-
stdout: output,
323-
},
324-
},
325-
],
326-
})
327-
328258
const runHandleSteps = (messages: Message[]) => {
329259
mockAgentState.messageHistory = messages
330-
const mockLogger = {
331-
debug: () => {},
332-
info: () => {},
333-
warn: () => {},
334-
error: () => {},
335-
}
336260
const generator = contextPruner.handleSteps!({
337261
agentState: mockAgentState,
338-
logger: mockLogger,
262+
logger: createMockLogger(),
339263
})
340264
const results: ReturnType<typeof generator.next>['value'][] = []
341265
let result = generator.next()
@@ -441,23 +365,6 @@ describe('context-pruner edge cases', () => {
441365
// Create content large enough to exceed 200k token limit to trigger pruning
442366
const largeContent = 'x'.repeat(150000)
443367

444-
const createToolMessage = (
445-
toolName: string,
446-
size: number,
447-
): ToolMessage => ({
448-
role: 'tool',
449-
toolCallId: 'test-id',
450-
toolName,
451-
content: [
452-
{
453-
type: 'json',
454-
value: {
455-
data: 'a'.repeat(size),
456-
},
457-
},
458-
],
459-
})
460-
461368
const messages = [
462369
createMessage('user', largeContent),
463370
createMessage('assistant', largeContent),
@@ -508,7 +415,7 @@ describe('context-pruner edge cases', () => {
508415
},
509416
]
510417

511-
testCases.forEach(({ content, shouldRemove }, index) => {
418+
testCases.forEach(({ content, shouldRemove }) => {
512419
const messages = [
513420
createMessage('user', 'Hello'),
514421
createMessage('assistant', content),

.agents/__tests__/test-utils.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { Message, ToolMessage } from '../types/util-types'
2+
3+
export const createMessage = (
4+
role: 'user' | 'assistant',
5+
content: string,
6+
): Message => ({
7+
role,
8+
content,
9+
})
10+
11+
export const createTerminalToolMessage = (
12+
command: string,
13+
output: string,
14+
exitCode?: number,
15+
): ToolMessage => ({
16+
role: 'tool',
17+
toolCallId: 'test-id',
18+
toolName: 'run_terminal_command',
19+
content: [
20+
{
21+
type: 'json',
22+
value: {
23+
command,
24+
stdout: output,
25+
...(exitCode !== undefined && { exitCode }),
26+
},
27+
},
28+
],
29+
})
30+
31+
export const createLargeToolMessage = (
32+
toolName: string,
33+
largeData: string,
34+
): ToolMessage => ({
35+
role: 'tool',
36+
toolCallId: 'test-id',
37+
toolName,
38+
content: [
39+
{
40+
type: 'json',
41+
value: {
42+
data: largeData,
43+
},
44+
},
45+
],
46+
})
47+
48+
export const createToolMessage = (
49+
toolName: string,
50+
size: number,
51+
): ToolMessage => ({
52+
role: 'tool',
53+
toolCallId: 'test-id',
54+
toolName,
55+
content: [
56+
{
57+
type: 'json',
58+
value: {
59+
data: 'a'.repeat(size),
60+
},
61+
},
62+
],
63+
})
64+
65+
export const createMockLogger = () => ({
66+
debug: () => {},
67+
info: () => {},
68+
warn: () => {},
69+
error: () => {},
70+
})

cli/src/components/implementor-row.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
getImplementorDisplayName,
1010
getImplementorIndex,
1111
getFileStatsFromBlocks,
12-
type TimelineItem,
1312
type FileStats,
1413
} from '../utils/implementor-helpers'
1514
import { computeSmartColumns } from '../utils/layout-helpers'

cli/src/components/message-block.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { pluralize } from '@codebuff/common/util/string'
21
import { TextAttributes } from '@opentui/core'
32
import React, { memo, useCallback, useMemo, useState, type ReactNode } from 'react'
43

@@ -64,8 +63,6 @@ interface MessageBlockProps {
6463
}) => void
6564
}
6665

67-
import { BORDER_CHARS } from '../utils/ui-constants'
68-
6966
export const MessageBlock: React.FC<MessageBlockProps> = ({
7067
messageId,
7168
blocks,

cli/src/utils/implementor-helpers.ts

Lines changed: 0 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type {
22
AgentContentBlock,
33
ContentBlock,
4-
TextContentBlock,
54
ToolContentBlock,
65
} from '../types/chat'
76

@@ -112,116 +111,6 @@ export function extractValueForKey(output: string, key: string): string | null {
112111
return null
113112
}
114113

115-
/**
116-
* Get the latest commentary (text block content) from agent blocks
117-
* Returns a single-line string with newlines replaced by spaces
118-
*/
119-
export function getLatestCommentary(
120-
blocks: ContentBlock[] | undefined,
121-
): string | undefined {
122-
if (!blocks || blocks.length === 0) return undefined
123-
124-
// Find the last text block that isn't reasoning
125-
for (let i = blocks.length - 1; i >= 0; i--) {
126-
const block = blocks[i]
127-
if (block.type === 'text' && block.textType !== 'reasoning') {
128-
// Replace newlines with spaces and collapse multiple spaces
129-
const content = block.content
130-
.replace(/\n+/g, ' ')
131-
.replace(/\s+/g, ' ')
132-
.trim()
133-
if (content) return content
134-
}
135-
}
136-
return undefined
137-
}
138-
139-
/**
140-
* Count edit operations (str_replace, write_file tools)
141-
*/
142-
export function countEdits(blocks: ContentBlock[] | undefined): number {
143-
if (!blocks || blocks.length === 0) return 0
144-
145-
return blocks.filter(
146-
(block) =>
147-
block.type === 'tool' &&
148-
EDIT_TOOL_NAMES.includes(block.toolName as (typeof EDIT_TOOL_NAMES)[number]),
149-
).length
150-
}
151-
152-
/**
153-
* Get list of unique file paths edited by the agent
154-
*/
155-
export function getEditedFiles(blocks: ContentBlock[] | undefined): string[] {
156-
if (!blocks || blocks.length === 0) return []
157-
158-
const files = new Set<string>()
159-
160-
for (const block of blocks) {
161-
if (
162-
block.type === 'tool' &&
163-
EDIT_TOOL_NAMES.includes(block.toolName as (typeof EDIT_TOOL_NAMES)[number])
164-
) {
165-
const filePath = extractFilePath(block)
166-
if (filePath) {
167-
files.add(filePath)
168-
}
169-
}
170-
}
171-
172-
return Array.from(files)
173-
}
174-
175-
export interface FileSnippet {
176-
path: string
177-
snippet?: string
178-
isCreate: boolean
179-
}
180-
181-
/**
182-
* Get list of files with content snippets
183-
*/
184-
export function getEditedFileSnippets(blocks: ContentBlock[] | undefined): FileSnippet[] {
185-
if (!blocks || blocks.length === 0) return []
186-
187-
const visited = new Set<string>()
188-
const results: FileSnippet[] = []
189-
190-
for (const block of blocks) {
191-
if (
192-
block.type === 'tool' &&
193-
EDIT_TOOL_NAMES.includes(block.toolName as (typeof EDIT_TOOL_NAMES)[number])
194-
) {
195-
const filePath = extractFilePath(block)
196-
if (filePath && !visited.has(filePath)) {
197-
visited.add(filePath)
198-
199-
const diff = extractDiff(block)
200-
let snippet: string | undefined
201-
if (diff) {
202-
// Extract first non-header line as preview
203-
const lines = diff.split('\n')
204-
const interestingLine = lines.find(l =>
205-
(l.startsWith('+') || l.startsWith('-')) &&
206-
!l.startsWith('+++') && !l.startsWith('---')
207-
)
208-
if (interestingLine) {
209-
snippet = interestingLine.trim().slice(0, 50)
210-
}
211-
}
212-
213-
results.push({
214-
path: filePath,
215-
isCreate: isCreateFile(block),
216-
snippet
217-
})
218-
}
219-
}
220-
}
221-
222-
return results
223-
}
224-
225114
/**
226115
* Extract file path from tool block
227116
*/
@@ -448,20 +337,6 @@ export function getFileStatsFromBlocks(blocks: ContentBlock[] | undefined): File
448337
return Array.from(fileMap.values())
449338
}
450339

451-
/**
452-
* Get total stats across all files
453-
*/
454-
export function getTotalStats(fileStats: FileStats[]): DiffStats {
455-
return fileStats.reduce(
456-
(acc, file) => ({
457-
linesAdded: acc.linesAdded + file.stats.linesAdded,
458-
linesRemoved: acc.linesRemoved + file.stats.linesRemoved,
459-
hunks: acc.hunks + file.stats.hunks,
460-
}),
461-
{ linesAdded: 0, linesRemoved: 0, hunks: 0 }
462-
)
463-
}
464-
465340
/**
466341
* Build an activity timeline from agent blocks
467342
* Interleaves commentary (text blocks) and edits (tool calls)

0 commit comments

Comments
 (0)