Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ agentapi server -- goose
```

> [!NOTE]
> When using Codex, Gemini, Amp or CursorCLI, always specify the agent type explicitly (eg: `agentapi server --type=codex -- codex`), or message formatting may break.
> When using Codex, Opencode, Gemini, Amp or CursorCLI, always specify the agent type explicitly (eg: `agentapi server --type=codex -- codex`), or message formatting may break.

An OpenAPI schema is available in [openapi.json](openapi.json).

Expand Down
2 changes: 2 additions & 0 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
AgentTypeCursorAgent AgentType = msgfmt.AgentTypeCursorAgent
AgentTypeCursor AgentType = msgfmt.AgentTypeCursor
AgentTypeAuggie AgentType = msgfmt.AgentTypeAuggie
AgentTypeOpencode AgentType = msgfmt.AgentTypeOpencode
AgentTypeCustom AgentType = msgfmt.AgentTypeCustom
)

Expand All @@ -46,6 +47,7 @@ var agentTypeMap = map[AgentType]bool{
AgentTypeCursorAgent: true,
AgentTypeCursor: true,
AgentTypeAuggie: true,
AgentTypeOpencode: true,
AgentTypeCustom: true,
}

Expand Down
10 changes: 10 additions & 0 deletions cmd/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func TestParseAgentType(t *testing.T) {
agentTypeVar: "",
want: AgentTypeCursor,
},
{
firstArg: "opencode",
agentTypeVar: "",
want: AgentTypeOpencode,
},
{
firstArg: "auggie",
agentTypeVar: "",
Expand Down Expand Up @@ -97,6 +102,11 @@ func TestParseAgentType(t *testing.T) {
agentTypeVar: "gemini",
want: AgentTypeGemini,
},
{
firstArg: "claude",
agentTypeVar: "opencode",
want: AgentTypeOpencode,
},
{
firstArg: "claude",
agentTypeVar: "cursor-agent",
Expand Down
3 changes: 2 additions & 1 deletion lib/httpapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) {
return mf.FormatAgentMessage(config.AgentType, message, userInput)
}
conversation := st.NewConversation(ctx, st.ConversationConfig{
AgentIO: config.Process,
AgentType: config.AgentType,
AgentIO: config.Process,
GetTime: func() time.Time {
return time.Now()
},
Expand Down
19 changes: 19 additions & 0 deletions lib/msgfmt/message_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,22 @@ func removeCodexInputBox(msg string) string {
}
return strings.Join(lines, "\n")
}

func removeOpencodeMessageBox(msg string) string {
lines := strings.Split(msg, "\n")
// Check the last 3 lines for
//
// ┃ ┃
// ┃ > ┃
// ┃ ┃
// We only check for the first ┃ and then an empty line above it - as sometimes the full input block does not load within a snapshot,
// this leads to displaying a bunch of newlines.
for i := len(lines) - 1; i >= 1; i-- {
if strings.TrimSpace(lines[i-1]) == "" &&
strings.ReplaceAll(lines[i], " ", "") == "┃┃" {
lines = lines[:i-1]
break
}
}
return strings.Join(lines, "\n")
}
17 changes: 17 additions & 0 deletions lib/msgfmt/msgfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ func RemoveUserInput(msgRaw string, userInputRaw string, agentType AgentType) st
if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found {
lastUserInputLineIdx = idx
}
} else if agentType == AgentTypeOpencode {
// skip +2 lines after the input
// ┃ jkmr (08:46 PM) ┃
// ┃ ┃
if lastUserInputLineIdx+2 < len(msgLines) {
lastUserInputLineIdx += 2
}
}

return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n")
Expand Down Expand Up @@ -234,6 +241,7 @@ const (
AgentTypeCursorAgent AgentType = "cursor-agent"
AgentTypeCursor AgentType = "cursor"
AgentTypeAuggie AgentType = "auggie"
AgentTypeOpencode AgentType = "opencode"
AgentTypeCustom AgentType = "custom"
)

Expand All @@ -251,6 +259,13 @@ func formatCodexMessage(message string, userInput string) string {
return message
}

func formatOpencodeMessage(message string, userInput string) string {
message = RemoveUserInput(message, userInput, AgentTypeOpencode)
message = removeOpencodeMessageBox(message)
message = trimEmptyLines(message)
return message
}

func FormatAgentMessage(agentType AgentType, message string, userInput string) string {
switch agentType {
case AgentTypeClaude:
Expand All @@ -271,6 +286,8 @@ func FormatAgentMessage(agentType AgentType, message string, userInput string) s
return formatGenericMessage(message, userInput, agentType)
case AgentTypeAuggie:
return formatGenericMessage(message, userInput, agentType)
case AgentTypeOpencode:
return formatOpencodeMessage(message, userInput)
case AgentTypeCustom:
return formatGenericMessage(message, userInput, agentType)
default:
Expand Down
2 changes: 1 addition & 1 deletion lib/msgfmt/msgfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func TestTrimEmptyLines(t *testing.T) {

func TestFormatAgentMessage(t *testing.T) {
dir := "testdata/format"
agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeAmp, AgentTypeCodex, AgentTypeCursorAgent, AgentTypeCursor, AgentTypeAuggie, AgentTypeCustom}
agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeAmp, AgentTypeCodex, AgentTypeCursorAgent, AgentTypeCursor, AgentTypeAuggie, AgentTypeOpencode, AgentTypeCustom}
for _, agentType := range agentTypes {
t.Run(string(agentType), func(t *testing.T) {
cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType)))
Expand Down
12 changes: 12 additions & 0 deletions lib/msgfmt/testdata/format/opencode/first_message/expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
█▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀
█░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀
▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀
v0.6.8

/new new session ctrl+x n
/help show help ctrl+x h
/share share session ctrl+x s
/models list models ctrl+x m


Grok Code is free for a limited time
Loading