Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/httpapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) {
return mf.IsAgentReadyForInitialPrompt(config.AgentType, message)
}

formatToolCall := func(message string) (string, []string) {
return mf.FormatToolCall(config.AgentType, message)
}

conversation := st.NewConversation(ctx, st.ConversationConfig{
AgentType: config.AgentType,
AgentIO: config.Process,
Expand All @@ -243,6 +247,8 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) {
ScreenStabilityLength: 2 * time.Second,
FormatMessage: formatMessage,
ReadyForInitialPrompt: isAgentReadyForInitialPrompt,
FormatToolCall: formatToolCall,
Logger: logger,
}, config.InitialPrompt)
emitter := NewEventEmitter(1024)

Expand Down
82 changes: 82 additions & 0 deletions lib/msgfmt/format_tool_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package msgfmt

import (
"strings"
)

func removeClaudeReportTaskToolCall(msg string) (string, []string) {
msg = "\n" + msg // This handles the case where the message starts with a tool call

// Remove all tool calls that start with `● coder - coder_report_task (MCP)`
lines := strings.Split(msg, "\n")

toolCallStartIdx := -1

// Store all tool call start and end indices [[start, end], ...]
var toolCallIdxs [][]int

for i := 1; i < len(lines)-1; i++ {
prevLine := strings.TrimSpace(lines[i-1])
line := strings.TrimSpace(lines[i])
nextLine := strings.TrimSpace(lines[i+1])

if strings.Contains(line, "coder - coder_report_task (MCP)") {
toolCallStartIdx = i
} else if toolCallStartIdx != -1 && line == "\"message\": \"Thanks for reporting!\"" && nextLine == "}" && strings.HasSuffix(prevLine, "{") {
// Store [start, end] pair
toolCallIdxs = append(toolCallIdxs, []int{toolCallStartIdx, min(len(lines), i+2)})

// Reset to find the next tool call
toolCallStartIdx = -1
}
}

// If no tool calls found, return original message
if len(toolCallIdxs) == 0 {
return strings.TrimLeft(msg, "\n"), []string{}
}

toolCallMessages := make([]string, 0)

// Remove tool calls from the message
for i := len(toolCallIdxs) - 1; i >= 0; i-- {
idxPair := toolCallIdxs[i]
start, end := idxPair[0], idxPair[1]

toolCallMessages = append(toolCallMessages, strings.Join(lines[start:end], "\n"))

lines = append(lines[:start], lines[end:]...)
}
return strings.TrimLeft(strings.Join(lines, "\n"), "\n"), toolCallMessages
}

func FormatToolCall(agentType AgentType, message string) (string, []string) {
switch agentType {
case AgentTypeClaude:
return removeClaudeReportTaskToolCall(message)
case AgentTypeGoose:
return message, []string{}
case AgentTypeAider:
return message, []string{}
case AgentTypeCodex:
return message, []string{}
case AgentTypeGemini:
return message, []string{}
case AgentTypeCopilot:
return message, []string{}
case AgentTypeAmp:
return message, []string{}
case AgentTypeCursor:
return message, []string{}
case AgentTypeAuggie:
return message, []string{}
case AgentTypeAmazonQ:
return message, []string{}
case AgentTypeOpencode:
return message, []string{}
case AgentTypeCustom:
return message, []string{}
default:
return message, []string{}
}
}
50 changes: 0 additions & 50 deletions lib/msgfmt/message_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,53 +100,3 @@ func removeAmpMessageBox(msg string) string {
}
return formattedMsg
}

func removeClaudeReportTaskToolCall(msg string) string {
// Remove all tool calls that start with `● coder - coder_report_task (MCP)` till we encounter the next line starting with ●
lines := strings.Split(msg, "\n")

toolCallStartIdx := -1
newLineAfterToolCallIdx := -1

// Store all tool call start and end indices [[start, end], ...]
var toolCallIdxs [][]int

for i := 0; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])

if strings.HasPrefix(line, "● coder - coder_report_task (MCP)") {
toolCallStartIdx = i
} else if toolCallStartIdx != -1 && strings.HasPrefix(line, "●") {
// Store [start, end] pair
toolCallIdxs = append(toolCallIdxs, []int{toolCallStartIdx, i})

// Reset to find the next tool call
toolCallStartIdx = -1
newLineAfterToolCallIdx = -1
}
if len(line) == 0 && toolCallStartIdx != -1 && newLineAfterToolCallIdx == -1 {
newLineAfterToolCallIdx = i
}
}

// Handle the case where the last tool call goes till the end of the message
// And a failsafe when the next message is not prefixed with ●
if toolCallStartIdx != -1 && newLineAfterToolCallIdx != -1 {
toolCallIdxs = append(toolCallIdxs, []int{toolCallStartIdx, newLineAfterToolCallIdx})
}

// If no tool calls found, return original message
if len(toolCallIdxs) == 0 {
return msg
}

// Remove tool calls from the message
for i := len(toolCallIdxs) - 1; i >= 0; i-- {
idxPair := toolCallIdxs[i]
start, end := idxPair[0], idxPair[1]

lines = append(lines[:start], lines[end:]...)
}

return strings.Join(lines, "\n")
}
10 changes: 1 addition & 9 deletions lib/msgfmt/msgfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,6 @@ func formatGenericMessage(message string, userInput string, agentType AgentType)
return message
}

func formatClaudeMessage(message string, userInput string) string {
message = RemoveUserInput(message, userInput, AgentTypeClaude)
message = removeMessageBox(message)
message = removeClaudeReportTaskToolCall(message)
message = trimEmptyLines(message)
return message
}

func formatCodexMessage(message string, userInput string) string {
message = RemoveUserInput(message, userInput, AgentTypeCodex)
message = removeCodexInputBox(message)
Expand All @@ -286,7 +278,7 @@ func formatAmpMessage(message string, userInput string) string {
func FormatAgentMessage(agentType AgentType, message string, userInput string) string {
switch agentType {
case AgentTypeClaude:
return formatClaudeMessage(message, userInput)
return formatGenericMessage(message, userInput, agentType)
case AgentTypeGoose:
return formatGenericMessage(message, userInput, agentType)
case AgentTypeAider:
Expand Down
3 changes: 2 additions & 1 deletion lib/msgfmt/msgfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ func TestFormatAgentMessage(t *testing.T) {
assert.NoError(t, err)
expected, err := testdataDir.ReadFile(path.Join(dir, string(agentType), c.Name(), "expected.txt"))
assert.NoError(t, err)
assert.Equal(t, string(expected), FormatAgentMessage(agentType, string(msg), string(userInput)))
output, _ := FormatToolCall(agentType, FormatAgentMessage(agentType, string(msg), string(userInput)))
assert.Equal(t, string(expected), output)
})
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
● I'll build a snake game for you. Let me start by reporting my
progress and creating a task list.


● Now I'll create a complete snake game with HTML, CSS, and
JavaScript:

Expand All @@ -19,6 +20,8 @@
padding: 0;
… +334 lines (ctrl+o to expand)



● I've built a complete snake game for you! The game is saved
at /home/coder/snake-game.html.

Expand All @@ -36,4 +39,4 @@
How to play:
Open the HTML file in your web browser and use the arrow keys
to move the snake. Collect the red food to grow and increase
your score!
your score!
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
> Build a snake game

● coder - coder_report_task (MCP)(summary: "Snake game created
successfully at snake-game.html",
link: "file:///home/coder/snake-ga
me.html", state: "working")
⎿ {
"message": "Thanks for reporting!"
}

● I'll build a snake game for you. Let me start by reporting my
progress and creating a task list.

Expand Down
25 changes: 23 additions & 2 deletions lib/screentracker/conversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package screentracker
import (
"context"
"fmt"
"log/slog"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -43,6 +44,9 @@ type ConversationConfig struct {
SkipSendMessageStatusCheck bool
// ReadyForInitialPrompt detects whether the agent has initialized and is ready to accept the initial prompt
ReadyForInitialPrompt func(message string) bool
// FormatToolCall removes the coder report_task tool call from the agent message and also returns the array of removed tool calls
FormatToolCall func(message string) (string, []string)
Logger *slog.Logger
}

type ConversationRole string
Expand Down Expand Up @@ -82,6 +86,8 @@ type Conversation struct {
InitialPromptSent bool
// ReadyForInitialPrompt keeps track if the agent is ready to accept the initial prompt
ReadyForInitialPrompt bool
// toolCallMessageSet keeps track of the tool calls that have been detected & logged in the current agent message
toolCallMessageSet map[string]bool
}

type ConversationStatus string
Expand Down Expand Up @@ -115,8 +121,9 @@ func NewConversation(ctx context.Context, cfg ConversationConfig, initialPrompt
Time: cfg.GetTime(),
},
},
InitialPrompt: initialPrompt,
InitialPromptSent: len(initialPrompt) == 0,
InitialPrompt: initialPrompt,
InitialPromptSent: len(initialPrompt) == 0,
toolCallMessageSet: make(map[string]bool),
}
return c
}
Expand Down Expand Up @@ -205,9 +212,19 @@ func (c *Conversation) lastMessage(role ConversationRole) ConversationMessage {
func (c *Conversation) updateLastAgentMessage(screen string, timestamp time.Time) {
agentMessage := FindNewMessage(c.screenBeforeLastUserMessage, screen, c.cfg.AgentType)
lastUserMessage := c.lastMessage(ConversationRoleUser)
var toolCalls []string
if c.cfg.FormatMessage != nil {
agentMessage = c.cfg.FormatMessage(agentMessage, lastUserMessage.Message)
}
if c.cfg.FormatToolCall != nil {
agentMessage, toolCalls = c.cfg.FormatToolCall(agentMessage)
}
for _, toolCall := range toolCalls {
if c.toolCallMessageSet[toolCall] == false {
c.toolCallMessageSet[toolCall] = true
c.cfg.Logger.Info("Tool call detected", "toolCall", toolCall)
}
}
shouldCreateNewMessage := len(c.messages) == 0 || c.messages[len(c.messages)-1].Role == ConversationRoleUser
lastAgentMessage := c.lastMessage(ConversationRoleAgent)
if lastAgentMessage.Message == agentMessage {
Expand All @@ -220,6 +237,10 @@ func (c *Conversation) updateLastAgentMessage(screen string, timestamp time.Time
}
if shouldCreateNewMessage {
c.messages = append(c.messages, conversationMessage)

// Cleanup
c.toolCallMessageSet = make(map[string]bool)

} else {
c.messages[len(c.messages)-1] = conversationMessage
}
Expand Down