Skip to content

Commit 57e4b88

Browse files
committed
refactor(cli): simplify bash command handling - remove AI context injection
- Remove userMessage from bash history (UI-only now, not sent to LLM) - Remove pendingToolResults for bash commands - Consolidate bash message building into bash-messages.ts - Simplify router.ts by using shared helper functions
1 parent d44092c commit 57e4b88

File tree

3 files changed

+140
-136
lines changed

3 files changed

+140
-136
lines changed

cli/src/commands/router.ts

Lines changed: 42 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,13 @@ import {
1515
} from './router-utils'
1616
import { useChatStore } from '../state/chat-store'
1717
import { getSystemMessage, getUserMessage } from '../utils/message-history'
18+
import {
19+
buildBashHistoryMessages,
20+
createRunTerminalToolResult,
21+
} from '../utils/bash-messages'
1822

19-
import type { ToolMessage } from '@codebuff/common/types/messages/codebuff-message'
20-
import type { ToolResultOutput } from '@codebuff/common/types/messages/content-part'
21-
import type { ContentBlock } from '../types/chat'
2223
import type { PendingBashMessage } from '../state/chat-store'
2324

24-
/**
25-
* Create a tool result output structure for terminal command results.
26-
*/
27-
function createToolResultOutput(params: {
28-
command: string
29-
cwd: string
30-
stdout: string | null
31-
stderr: string | null
32-
exitCode: number
33-
errorMessage?: string
34-
}): ToolResultOutput[] {
35-
const { command, cwd, stdout, stderr, exitCode, errorMessage } = params
36-
if (errorMessage) {
37-
return [
38-
{
39-
type: 'json' as const,
40-
value: { command, startingCwd: cwd, errorMessage },
41-
},
42-
]
43-
}
44-
return [
45-
{
46-
type: 'json' as const,
47-
value: {
48-
command,
49-
startingCwd: cwd,
50-
stdout: stdout || null,
51-
stderr: stderr || null,
52-
exitCode,
53-
},
54-
},
55-
]
56-
}
57-
5825
/**
5926
* Execute a bash command.
6027
* When ghost=false: adds directly to chat history with placeholder output that updates.
@@ -89,21 +56,14 @@ function executeBashCommand(
8956
cwd: commandCwd,
9057
})
9158
} else {
92-
// Direct mode: add to chat history with placeholder
93-
const resultBlock: ContentBlock = {
94-
type: 'tool',
95-
toolName: 'run_terminal_command',
59+
// Direct mode: add to chat history with placeholder output (user + assistant)
60+
const { assistantMessage } = buildBashHistoryMessages({
61+
command,
62+
cwd: commandCwd,
9663
toolCallId: id,
97-
input: { command },
9864
output: '...',
99-
}
100-
options.setMessages((prev) => [
101-
...prev,
102-
{
103-
...getUserMessage([resultBlock]),
104-
metadata: { bashCwd: commandCwd },
105-
},
106-
])
65+
})
66+
options.setMessages((prev) => [...prev, assistantMessage])
10767
}
10868

10969
runTerminalCommand({
@@ -117,8 +77,6 @@ function executeBashCommand(
11777
const stdout = 'stdout' in value ? value.stdout || '' : ''
11878
const stderr = 'stderr' in value ? value.stderr || '' : ''
11979
const exitCode = 'exitCode' in value ? value.exitCode ?? 0 : 0
120-
const rawOutput = stdout + stderr
121-
const output = rawOutput || '(no output)'
12280

12381
if (options.ghost) {
12482
options.updatePendingBashMessage(id, {
@@ -128,7 +86,7 @@ function executeBashCommand(
12886
isRunning: false,
12987
})
13088
} else {
131-
const toolResultOutput = createToolResultOutput({
89+
const toolResultOutput = createRunTerminalToolResult({
13290
command,
13391
cwd: commandCwd,
13492
stdout: stdout || null,
@@ -140,25 +98,17 @@ function executeBashCommand(
14098
options.setMessages((prev) =>
14199
prev.map((msg) => {
142100
if (!msg.blocks) return msg
143-
return {
144-
...msg,
145-
blocks: msg.blocks.map((block) =>
146-
'toolCallId' in block && block.toolCallId === id
147-
? { ...block, output: outputJson }
148-
: block,
149-
),
150-
}
101+
let didUpdate = false
102+
const blocks = msg.blocks.map((block) => {
103+
if ('toolCallId' in block && block.toolCallId === id) {
104+
didUpdate = true
105+
return { ...block, output: outputJson }
106+
}
107+
return block
108+
})
109+
return didUpdate ? { ...msg, blocks, isComplete: true } : msg
151110
}),
152111
)
153-
154-
// Add to pending tool results so AI can see this in the next run
155-
const toolMessage: ToolMessage = {
156-
role: 'tool',
157-
toolCallId: id,
158-
toolName: 'run_terminal_command',
159-
content: toolResultOutput,
160-
}
161-
useChatStore.getState().addPendingToolResult(toolMessage)
162112
}
163113
})
164114
.catch((error) => {
@@ -174,7 +124,7 @@ function executeBashCommand(
174124
isRunning: false,
175125
})
176126
} else {
177-
const errorToolResultOutput = createToolResultOutput({
127+
const errorToolResultOutput = createRunTerminalToolResult({
178128
command,
179129
cwd: commandCwd,
180130
stdout: null,
@@ -187,31 +137,24 @@ function executeBashCommand(
187137
options.setMessages((prev) =>
188138
prev.map((msg) => {
189139
if (!msg.blocks) return msg
190-
return {
191-
...msg,
192-
blocks: msg.blocks.map((block) =>
193-
'toolCallId' in block && block.toolCallId === id
194-
? { ...block, output: errorOutputJson }
195-
: block,
196-
),
197-
}
140+
let didUpdate = false
141+
const blocks = msg.blocks.map((block) => {
142+
if ('toolCallId' in block && block.toolCallId === id) {
143+
didUpdate = true
144+
return { ...block, output: errorOutputJson }
145+
}
146+
return block
147+
})
148+
return didUpdate ? { ...msg, blocks, isComplete: true } : msg
198149
}),
199150
)
200-
201-
const errorToolMessage: ToolMessage = {
202-
role: 'tool',
203-
toolCallId: id,
204-
toolName: 'run_terminal_command',
205-
content: errorToolResultOutput,
206-
}
207-
useChatStore.getState().addPendingToolResult(errorToolMessage)
208151
}
209152
})
210153
}
211154

212155
/**
213156
* Add a completed bash command result to the chat message history.
214-
* Also adds to pendingToolResults so the AI can see it in the next run.
157+
* Note: This is UI-only; we no longer send these commands to the AI context.
215158
*/
216159
export function addBashMessageToHistory(params: {
217160
command: string
@@ -222,38 +165,24 @@ export function addBashMessageToHistory(params: {
222165
setMessages: RouterParams['setMessages']
223166
}) {
224167
const { command, stdout, stderr, exitCode, cwd, setMessages } = params
225-
const outputText = stdout || stderr || '(no output)'
226-
const toolCallId = crypto.randomUUID()
227-
const resultBlock: ContentBlock = {
228-
type: 'tool',
229-
toolName: 'run_terminal_command',
230-
toolCallId,
231-
input: { command },
232-
output: outputText,
233-
}
234-
235-
setMessages((prev) => [
236-
...prev,
237-
{
238-
...getUserMessage([resultBlock]),
239-
metadata: { bashCwd: cwd },
240-
},
241-
])
242-
243-
const toolResultOutput = createToolResultOutput({
168+
const toolResultOutput = createRunTerminalToolResult({
244169
command,
245170
cwd,
246171
stdout: stdout || null,
247172
stderr: stderr ?? null,
248173
exitCode,
249174
})
250-
const toolMessage: ToolMessage = {
251-
role: 'tool',
175+
const toolCallId = crypto.randomUUID()
176+
const outputJson = JSON.stringify(toolResultOutput)
177+
const { assistantMessage } = buildBashHistoryMessages({
178+
command,
179+
cwd,
252180
toolCallId,
253-
toolName: 'run_terminal_command',
254-
content: toolResultOutput,
255-
}
256-
useChatStore.getState().addPendingToolResult(toolMessage)
181+
output: outputJson,
182+
isComplete: true,
183+
})
184+
185+
setMessages((prev) => [...prev, assistantMessage])
257186
}
258187

259188
export async function routeUserPrompt(

cli/src/hooks/use-send-message.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import { formatTimestamp } from '../utils/helpers'
2121
import { loadAgentDefinitions } from '../utils/load-agent-definitions'
2222

2323
import { logger } from '../utils/logger'
24+
import {
25+
buildBashHistoryMessages,
26+
createRunTerminalToolResult,
27+
} from '../utils/bash-messages'
2428
import { getUserMessage } from '../utils/message-history'
2529
import { NETWORK_ERROR_ID } from '../utils/validation-error-helpers'
2630
import {
@@ -447,34 +451,36 @@ export const useSendMessage = ({
447451
setHasReceivedPlanResponse(false)
448452
}
449453

450-
// Include any pending bash messages in context before sending
451-
// This ensures the LLM can reference terminal commands run during streaming
454+
// Include any pending bash messages in the UI before sending
455+
// (we no longer send these commands to the LLM context)
452456
const { pendingBashMessages, clearPendingBashMessages } =
453457
useChatStore.getState()
454458
if (pendingBashMessages.length > 0) {
455459
// Convert pending bash messages to chat messages and add to history
456460
applyMessageUpdate((prev) => {
457-
const bashMessages: ChatMessage[] = pendingBashMessages.flatMap(
458-
(bash) => [
459-
getUserMessage(`!${bash.command}`),
460-
{
461-
id: `bash-result-${Date.now()}-${Math.random().toString(16).slice(2)}`,
462-
variant: 'ai' as const,
463-
content: '',
464-
blocks: [
465-
{
466-
type: 'tool' as const,
467-
toolCallId: crypto.randomUUID(),
468-
toolName: 'run_terminal_command' as const,
469-
input: { command: bash.command },
470-
output: bash.stdout || bash.stderr || '',
471-
},
472-
],
473-
timestamp: formatTimestamp(),
474-
isComplete: true,
475-
},
476-
],
477-
)
461+
const bashMessages: ChatMessage[] = []
462+
463+
for (const bash of pendingBashMessages) {
464+
const toolCallId = crypto.randomUUID()
465+
const cwd = bash.cwd || process.cwd()
466+
const toolResultOutput = createRunTerminalToolResult({
467+
command: bash.command,
468+
cwd,
469+
stdout: bash.stdout || null,
470+
stderr: bash.stderr || null,
471+
exitCode: bash.exitCode,
472+
})
473+
const outputJson = JSON.stringify(toolResultOutput)
474+
const { assistantMessage } = buildBashHistoryMessages({
475+
command: bash.command,
476+
cwd,
477+
toolCallId,
478+
output: outputJson,
479+
isComplete: true,
480+
})
481+
482+
bashMessages.push(assistantMessage)
483+
}
478484
return [...prev, ...bashMessages]
479485
})
480486
clearPendingBashMessages()

cli/src/utils/bash-messages.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { formatTimestamp } from './helpers'
2+
3+
import type { ToolResultOutput } from '@codebuff/common/types/messages/content-part'
4+
import type { ChatMessage, ContentBlock } from '../types/chat'
5+
6+
export function createRunTerminalToolResult(params: {
7+
command: string
8+
cwd: string
9+
stdout: string | null
10+
stderr: string | null
11+
exitCode: number
12+
errorMessage?: string
13+
}): ToolResultOutput[] {
14+
const { command, cwd, stdout, stderr, exitCode, errorMessage } = params
15+
if (errorMessage) {
16+
return [
17+
{
18+
type: 'json' as const,
19+
value: { command, startingCwd: cwd, errorMessage },
20+
},
21+
]
22+
}
23+
return [
24+
{
25+
type: 'json' as const,
26+
value: {
27+
command,
28+
startingCwd: cwd,
29+
stdout: stdout || null,
30+
stderr: stderr || null,
31+
exitCode,
32+
},
33+
},
34+
]
35+
}
36+
37+
export function buildBashHistoryMessages(params: {
38+
command: string
39+
cwd: string
40+
toolCallId?: string
41+
output?: string
42+
isComplete?: boolean
43+
}): {
44+
assistantMessage: ChatMessage
45+
toolCallId: string
46+
} {
47+
const { command, cwd, output = '...', isComplete = false } = params
48+
const toolCallId = params.toolCallId ?? crypto.randomUUID()
49+
50+
const toolBlock: ContentBlock = {
51+
type: 'tool',
52+
toolName: 'run_terminal_command',
53+
toolCallId,
54+
input: { command },
55+
output,
56+
}
57+
58+
const assistantMessage: ChatMessage = {
59+
id: `bash-result-${toolCallId}`,
60+
variant: 'ai',
61+
content: '',
62+
blocks: [toolBlock],
63+
timestamp: formatTimestamp(),
64+
isComplete,
65+
metadata: { bashCwd: cwd },
66+
}
67+
68+
return { assistantMessage, toolCallId }
69+
}

0 commit comments

Comments
 (0)