Skip to content

Commit 7a72a49

Browse files
authored
feat: support opencode (#79)
1 parent 0876f7d commit 7a72a49

File tree

18 files changed

+3093
-24
lines changed

18 files changed

+3093
-24
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ agentapi server -- goose
6565
```
6666

6767
> [!NOTE]
68-
> When using Codex, Gemini, Amp or CursorCLI, always specify the agent type explicitly (eg: `agentapi server --type=codex -- codex`), or message formatting may break.
68+
> 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.
6969
7070
An OpenAPI schema is available in [openapi.json](openapi.json).
7171

cmd/server/server.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ import (
2323
type AgentType = msgfmt.AgentType
2424

2525
const (
26-
AgentTypeClaude AgentType = msgfmt.AgentTypeClaude
27-
AgentTypeGoose AgentType = msgfmt.AgentTypeGoose
28-
AgentTypeAider AgentType = msgfmt.AgentTypeAider
29-
AgentTypeCodex AgentType = msgfmt.AgentTypeCodex
30-
AgentTypeGemini AgentType = msgfmt.AgentTypeGemini
31-
AgentTypeAmp AgentType = msgfmt.AgentTypeAmp
32-
AgentTypeCursor AgentType = msgfmt.AgentTypeCursor
33-
AgentTypeAuggie AgentType = msgfmt.AgentTypeAuggie
34-
AgentTypeAmazonQ AgentType = msgfmt.AgentTypeAmazonQ
35-
AgentTypeCustom AgentType = msgfmt.AgentTypeCustom
26+
AgentTypeClaude AgentType = msgfmt.AgentTypeClaude
27+
AgentTypeGoose AgentType = msgfmt.AgentTypeGoose
28+
AgentTypeAider AgentType = msgfmt.AgentTypeAider
29+
AgentTypeCodex AgentType = msgfmt.AgentTypeCodex
30+
AgentTypeGemini AgentType = msgfmt.AgentTypeGemini
31+
AgentTypeAmp AgentType = msgfmt.AgentTypeAmp
32+
AgentTypeCursor AgentType = msgfmt.AgentTypeCursor
33+
AgentTypeAuggie AgentType = msgfmt.AgentTypeAuggie
34+
AgentTypeAmazonQ AgentType = msgfmt.AgentTypeAmazonQ
35+
AgentTypeOpencode AgentType = msgfmt.AgentTypeOpencode
36+
AgentTypeCustom AgentType = msgfmt.AgentTypeCustom
3637
)
3738

3839
// agentTypeAliases contains the mapping of possible input agent type strings to their canonical AgentType values
@@ -48,6 +49,7 @@ var agentTypeAliases = map[string]AgentType{
4849
"cursor-agent": AgentTypeCursor,
4950
"q": AgentTypeAmazonQ,
5051
"amazonq": AgentTypeAmazonQ,
52+
"opencode": AgentTypeOpencode,
5153
"custom": AgentTypeCustom,
5254
}
5355

cmd/server/server_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ func TestParseAgentType(t *testing.T) {
6767
agentTypeVar: "",
6868
want: AgentTypeAmazonQ,
6969
},
70+
{
71+
firstArg: "opencode",
72+
agentTypeVar: "",
73+
want: AgentTypeOpencode,
74+
},
7075
{
7176
firstArg: "auggie",
7277
agentTypeVar: "",
@@ -117,6 +122,11 @@ func TestParseAgentType(t *testing.T) {
117122
agentTypeVar: "q",
118123
want: AgentTypeAmazonQ,
119124
},
125+
{
126+
firstArg: "claude",
127+
agentTypeVar: "opencode",
128+
want: AgentTypeOpencode,
129+
},
120130
{
121131
firstArg: "claude",
122132
agentTypeVar: "cursor-agent",

lib/httpapi/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ func NewServer(ctx context.Context, config ServerConfig) (*Server, error) {
196196
return mf.FormatAgentMessage(config.AgentType, message, userInput)
197197
}
198198
conversation := st.NewConversation(ctx, st.ConversationConfig{
199-
AgentIO: config.Process,
199+
AgentType: config.AgentType,
200+
AgentIO: config.Process,
200201
GetTime: func() time.Time {
201202
return time.Now()
202203
},

lib/msgfmt/message_box.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,22 @@ func removeCodexInputBox(msg string) string {
6060
}
6161
return strings.Join(lines, "\n")
6262
}
63+
64+
func removeOpencodeMessageBox(msg string) string {
65+
lines := strings.Split(msg, "\n")
66+
// Check the last 3 lines for
67+
//
68+
// ┃ ┃
69+
// ┃ > ┃
70+
// ┃ ┃
71+
// 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,
72+
// this leads to displaying a bunch of newlines.
73+
for i := len(lines) - 1; i >= 1; i-- {
74+
if strings.TrimSpace(lines[i-1]) == "" &&
75+
strings.ReplaceAll(lines[i], " ", "") == "┃┃" {
76+
lines = lines[:i-1]
77+
break
78+
}
79+
}
80+
return strings.Join(lines, "\n")
81+
}

lib/msgfmt/msgfmt.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ func RemoveUserInput(msgRaw string, userInputRaw string, agentType AgentType) st
196196
if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found {
197197
lastUserInputLineIdx = idx
198198
}
199+
} else if agentType == AgentTypeOpencode {
200+
// skip +2 lines after the input
201+
// ┃ jkmr (08:46 PM) ┃
202+
// ┃ ┃
203+
if lastUserInputLineIdx+2 < len(msgLines) {
204+
lastUserInputLineIdx += 2
205+
}
199206
}
200207

201208
return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n")
@@ -234,6 +241,7 @@ const (
234241
AgentTypeCursor AgentType = "cursor"
235242
AgentTypeAuggie AgentType = "auggie"
236243
AgentTypeAmazonQ AgentType = "amazonq"
244+
AgentTypeOpencode AgentType = "opencode"
237245
AgentTypeCustom AgentType = "custom"
238246
)
239247

@@ -251,6 +259,13 @@ func formatCodexMessage(message string, userInput string) string {
251259
return message
252260
}
253261

262+
func formatOpencodeMessage(message string, userInput string) string {
263+
message = RemoveUserInput(message, userInput, AgentTypeOpencode)
264+
message = removeOpencodeMessageBox(message)
265+
message = trimEmptyLines(message)
266+
return message
267+
}
268+
254269
func FormatAgentMessage(agentType AgentType, message string, userInput string) string {
255270
switch agentType {
256271
case AgentTypeClaude:
@@ -271,6 +286,8 @@ func FormatAgentMessage(agentType AgentType, message string, userInput string) s
271286
return formatGenericMessage(message, userInput, agentType)
272287
case AgentTypeAmazonQ:
273288
return formatGenericMessage(message, userInput, agentType)
289+
case AgentTypeOpencode:
290+
return formatOpencodeMessage(message, userInput)
274291
case AgentTypeCustom:
275292
return formatGenericMessage(message, userInput, agentType)
276293
default:

lib/msgfmt/msgfmt_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func TestTrimEmptyLines(t *testing.T) {
218218

219219
func TestFormatAgentMessage(t *testing.T) {
220220
dir := "testdata/format"
221-
agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeAmp, AgentTypeCodex, AgentTypeCursor, AgentTypeAuggie, AgentTypeAmazonQ, AgentTypeCustom}
221+
agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeAmp, AgentTypeCodex, AgentTypeCursor, AgentTypeAuggie, AgentTypeAmazonQ, AgentTypeOpencode, AgentTypeCustom}
222222
for _, agentType := range agentTypes {
223223
t.Run(string(agentType), func(t *testing.T) {
224224
cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType)))
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
█▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀
2+
█░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀
3+
▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀
4+
v0.6.8
5+
6+
/new new session ctrl+x n
7+
/help show help ctrl+x h
8+
/share share session ctrl+x s
9+
/models list models ctrl+x m
10+
11+
12+
Grok Code is free for a limited time

0 commit comments

Comments
 (0)