From 42e8de9bf8cea4cc256ca4d34f93bbcd5408d13e Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 12 Aug 2025 23:14:44 +0530 Subject: [PATCH 1/7] feat: add support for cursor cli --- README.md | 2 +- cmd/server/server.go | 2 + cmd/server/server_test.go | 10 ++++ lib/msgfmt/msgfmt.go | 25 ++++++++-- .../cursor/confirmation_box/expected.txt | 19 ++++++++ .../format/cursor/confirmation_box/msg.txt | 26 ++++++++++ .../format/cursor/confirmation_box/user.txt | 1 + .../format/cursor/first_message/expected.txt | 11 +++++ .../format/cursor/first_message/msg.txt | 19 ++++++++ .../format/cursor/first_message/user.txt | 0 .../cursor/multi-line-input/expected.txt | 24 ++++++++++ .../format/cursor/multi-line-input/msg.txt | 48 +++++++++++++++++++ .../format/cursor/multi-line-input/user.txt | 7 +++ .../format/cursor/second_message/expected.txt | 9 ++++ .../format/cursor/second_message/msg.txt | 27 +++++++++++ .../format/cursor/second_message/user.txt | 1 + .../format/cursor/thinking/expected.txt | 1 + .../testdata/format/cursor/thinking/msg.txt | 17 +++++++ .../testdata/format/cursor/thinking/user.txt | 1 + 19 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 lib/msgfmt/testdata/format/cursor/confirmation_box/expected.txt create mode 100644 lib/msgfmt/testdata/format/cursor/confirmation_box/msg.txt create mode 100644 lib/msgfmt/testdata/format/cursor/confirmation_box/user.txt create mode 100644 lib/msgfmt/testdata/format/cursor/first_message/expected.txt create mode 100644 lib/msgfmt/testdata/format/cursor/first_message/msg.txt create mode 100644 lib/msgfmt/testdata/format/cursor/first_message/user.txt create mode 100644 lib/msgfmt/testdata/format/cursor/multi-line-input/expected.txt create mode 100644 lib/msgfmt/testdata/format/cursor/multi-line-input/msg.txt create mode 100644 lib/msgfmt/testdata/format/cursor/multi-line-input/user.txt create mode 100644 lib/msgfmt/testdata/format/cursor/second_message/expected.txt create mode 100644 lib/msgfmt/testdata/format/cursor/second_message/msg.txt create mode 100644 lib/msgfmt/testdata/format/cursor/second_message/user.txt create mode 100644 lib/msgfmt/testdata/format/cursor/thinking/expected.txt create mode 100644 lib/msgfmt/testdata/format/cursor/thinking/msg.txt create mode 100644 lib/msgfmt/testdata/format/cursor/thinking/user.txt diff --git a/README.md b/README.md index e5a81a9..a5502f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AgentAPI -Control [Claude Code](https://github.com/anthropics/claude-code), [Goose](https://github.com/block/goose), [Aider](https://github.com/Aider-AI/aider), [Gemini](https://github.com/google-gemini/gemini-cli), [Sourcegraph Amp](https://github.com/sourcegraph/amp-cli) and [Codex](https://github.com/openai/codex) with an HTTP API. +Control [Claude Code](https://github.com/anthropics/claude-code), [Goose](https://github.com/block/goose), [Aider](https://github.com/Aider-AI/aider), [Gemini](https://github.com/google-gemini/gemini-cli), [Sourcegraph Amp](https://github.com/sourcegraph/amp-cli), [Codex](https://github.com/openai/codex) and [Cursor CLI](https://cursor.com/en/cli) with an HTTP API. ![agentapi-chat](https://github.com/user-attachments/assets/57032c9f-4146-4b66-b219-09e38ab7690d) diff --git a/cmd/server/server.go b/cmd/server/server.go index 93c0541..621bc09 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -29,6 +29,7 @@ const ( AgentTypeCodex AgentType = msgfmt.AgentTypeCodex AgentTypeGemini AgentType = msgfmt.AgentTypeGemini AgentTypeAmp AgentType = msgfmt.AgentTypeAmp + AgentTypeCursor AgentType = msgfmt.AgentTypeCursor AgentTypeCustom AgentType = msgfmt.AgentTypeCustom ) @@ -40,6 +41,7 @@ var agentTypeMap = map[AgentType]bool{ AgentTypeCodex: true, AgentTypeGemini: true, AgentTypeAmp: true, + AgentTypeCursor: true, AgentTypeCustom: true, } diff --git a/cmd/server/server_test.go b/cmd/server/server_test.go index c09ffb8..121eade 100644 --- a/cmd/server/server_test.go +++ b/cmd/server/server_test.go @@ -47,6 +47,11 @@ func TestParseAgentType(t *testing.T) { agentTypeVar: "", want: AgentTypeGemini, }, + { + firstArg: "cursor", + agentTypeVar: "", + want: AgentTypeCursor, + }, { firstArg: "amp", agentTypeVar: "", @@ -82,6 +87,11 @@ func TestParseAgentType(t *testing.T) { agentTypeVar: "gemini", want: AgentTypeGemini, }, + { + firstArg: "claude", + agentTypeVar: "cursor", + want: AgentTypeCursor, + }, { firstArg: "aider", agentTypeVar: "claude", diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index ca57dec..d4baf50 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -140,6 +140,22 @@ func findUserInputEndIdx(userInputStartIdx int, msg []rune, userInput []rune) in return msgIdx } +// skipTrailingInputBoxLine skips the next line if it contains any of the markers. +// In case of Gemini and Cursor, the user input is echoed back in a box +// This function searches for the markers passed by the caller and skips the next line if it contains all of them. +func skipTrailingInputBoxLine(lines []string, lastUserInputLineIdx *int, markers ...string) { + if *lastUserInputLineIdx+1 >= len(lines) { + return + } + line := lines[*lastUserInputLineIdx+1] + for _, m := range markers { + if !strings.Contains(line, m) { + return + } + } + *lastUserInputLineIdx++ +} + // RemoveUserInput removes the user input from the message. // Goose, Aider, and Claude Code echo back the user's input to // make it visible in the terminal. This function makes a best effort @@ -169,10 +185,8 @@ func RemoveUserInput(msgRaw string, userInputRaw string) string { // that doesn't contain the echoed user input. lastUserInputLineIdx := msgRuneLineLocations[userInputEndIdx] - // In case of Gemini, the user input echoed back is wrapped in a rounded box, so we remove it. - if lastUserInputLineIdx+1 < len(msgLines) && strings.Contains(msgLines[lastUserInputLineIdx+1], "╯") && strings.Contains(msgLines[lastUserInputLineIdx+1], "╰") { - lastUserInputLineIdx += 1 - } + skipTrailingInputBoxLine(msgLines, &lastUserInputLineIdx, "╯", "╰") // Gemini + skipTrailingInputBoxLine(msgLines, &lastUserInputLineIdx, "┘", "└") // Cursor return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n") } @@ -207,6 +221,7 @@ const ( AgentTypeCodex AgentType = "codex" AgentTypeGemini AgentType = "gemini" AgentTypeAmp AgentType = "amp" + AgentTypeCursor AgentType = "cursor" AgentTypeCustom AgentType = "custom" ) @@ -238,6 +253,8 @@ func FormatAgentMessage(agentType AgentType, message string, userInput string) s return formatGenericMessage(message, userInput) case AgentTypeAmp: return formatGenericMessage(message, userInput) + case AgentTypeCursor: + return formatGenericMessage(message, userInput) case AgentTypeCustom: return formatGenericMessage(message, userInput) default: diff --git a/lib/msgfmt/testdata/format/cursor/confirmation_box/expected.txt b/lib/msgfmt/testdata/format/cursor/confirmation_box/expected.txt new file mode 100644 index 0000000..2e4162f --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/confirmation_box/expected.txt @@ -0,0 +1,19 @@ + I'll check the repository's root, name, remotes, and current branch. + + + + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ $ git rev-parse --show-toplevel in . │ + │ $ basename "$(git rev-parse --show-toplevel)" in . │ + │ $ git remote -v in . │ + │ $ git rev-parse --abbrev-ref HEAD in . │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Run this command? │ + │ Not in allowlist: git │ + │ → Run (y) (enter) │ + │ Reject (esc or p) │ + │ Add Shell(git) to allowlist? (tab) │ + │ Auto-run all commands (shift+tab) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/confirmation_box/msg.txt b/lib/msgfmt/testdata/format/cursor/confirmation_box/msg.txt new file mode 100644 index 0000000..1df8d24 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/confirmation_box/msg.txt @@ -0,0 +1,26 @@ + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Which repo is this ? │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + I'll check the repository's root, name, remotes, and current branch. + + + + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ $ git rev-parse --show-toplevel in . │ + │ $ basename "$(git rev-parse --show-toplevel)" in . │ + │ $ git remote -v in . │ + │ $ git rev-parse --abbrev-ref HEAD in . │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Run this command? │ + │ Not in allowlist: git │ + │ → Run (y) (enter) │ + │ Reject (esc or p) │ + │ Add Shell(git) to allowlist? (tab) │ + │ Auto-run all commands (shift+tab) │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/confirmation_box/user.txt b/lib/msgfmt/testdata/format/cursor/confirmation_box/user.txt new file mode 100644 index 0000000..c7d3776 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/confirmation_box/user.txt @@ -0,0 +1 @@ +Which repo is this ? \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/first_message/expected.txt b/lib/msgfmt/testdata/format/cursor/first_message/expected.txt new file mode 100644 index 0000000..ed79526 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/first_message/expected.txt @@ -0,0 +1,11 @@ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ⬢ Welcome to Cursor Agent Beta │ + │ │ + │ Cursor Agent CLI is in beta. Security safeguards are still evolving. It can read, modify, and delete files, and execute shell commands you approve. Use at your own risk and only in trusted environments. │ + │ │ + │ Please read about our security at https://cursor.com/security. │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/first_message/msg.txt b/lib/msgfmt/testdata/format/cursor/first_message/msg.txt new file mode 100644 index 0000000..c560cda --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/first_message/msg.txt @@ -0,0 +1,19 @@ + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ⬢ Welcome to Cursor Agent Beta │ + │ │ + │ Cursor Agent CLI is in beta. Security safeguards are still evolving. It can read, modify, and delete files, and execute shell commands you approve. Use at your own risk and only in trusted environments. │ + │ │ + │ Please read about our security at https://cursor.com/security. │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ → Plan, search, build anything │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + OpenAI GPT-5 + / for commands · @ for files + diff --git a/lib/msgfmt/testdata/format/cursor/first_message/user.txt b/lib/msgfmt/testdata/format/cursor/first_message/user.txt new file mode 100644 index 0000000..e69de29 diff --git a/lib/msgfmt/testdata/format/cursor/multi-line-input/expected.txt b/lib/msgfmt/testdata/format/cursor/multi-line-input/expected.txt new file mode 100644 index 0000000..727c753 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/multi-line-input/expected.txt @@ -0,0 +1,24 @@ + I’m going to search the codebase for the function name and related identifiers to locate the file containing that snippet. + + ⬢ Searched 4 searches + … 1 earlier item hidden + Searched "removeCodexInputBox" in . + Searched "RemoveUserInput\(" in . + Searched "trimEmptyLines\(" in . + + Scanning relevant Go files to confirm the exact location and provide a precise citation. + + ⬢ Read lib/msgfmt/msgfmt.go + + It’s in lib/msgfmt/msgfmt.go. + + lib/msgfmt/msgfmt.go lines 235-240 + + 235 │ func formatCodexMessage(message string, userInput string) string { + 236 │ message = RemoveUserInput(message, userInput) + 237 │ message = removeCodexInputBox(message) + 238 │ message = trimEmptyLines(message) + 239 │ return message + 240 │ } + + • Found in lib/msgfmt/msgfmt.go (lines 235–240). \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/multi-line-input/msg.txt b/lib/msgfmt/testdata/format/cursor/multi-line-input/msg.txt new file mode 100644 index 0000000..5cc4417 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/multi-line-input/msg.txt @@ -0,0 +1,48 @@ + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Which file has this snippet │ + │ func formatCodexMessage(message string, userInput string) string { │ + │ message = RemoveUserInput(message, userInput) │ + │ message = removeCodexInputBox(message) │ + │ message = trimEmptyLines(message) │ + │ return message │ + │ } │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + I’m going to search the codebase for the function name and related identifiers to locate the file containing that snippet. + + ⬢ Searched 4 searches + … 1 earlier item hidden + Searched "removeCodexInputBox" in . + Searched "RemoveUserInput\(" in . + Searched "trimEmptyLines\(" in . + + Scanning relevant Go files to confirm the exact location and provide a precise citation. + + ⬢ Read lib/msgfmt/msgfmt.go + + It’s in lib/msgfmt/msgfmt.go. + + lib/msgfmt/msgfmt.go lines 235-240 + + 235 │ func formatCodexMessage(message string, userInput string) string { + 236 │ message = RemoveUserInput(message, userInput) + 237 │ message = removeCodexInputBox(message) + 238 │ message = trimEmptyLines(message) + 239 │ return message + 240 │ } + + • Found in lib/msgfmt/msgfmt.go (lines 235–240). + + + + + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ → Add a follow-up │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + OpenAI GPT-5 + / for commands · @ for files diff --git a/lib/msgfmt/testdata/format/cursor/multi-line-input/user.txt b/lib/msgfmt/testdata/format/cursor/multi-line-input/user.txt new file mode 100644 index 0000000..e85fa2a --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/multi-line-input/user.txt @@ -0,0 +1,7 @@ +Which file has this snippet +func formatCodexMessage(message string, userInput string) string { + message = RemoveUserInput(message, userInput) + message = removeCodexInputBox(message) + message = trimEmptyLines(message) + return message +} \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/second_message/expected.txt b/lib/msgfmt/testdata/format/cursor/second_message/expected.txt new file mode 100644 index 0000000..c166795 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/second_message/expected.txt @@ -0,0 +1,9 @@ + I’m going to check the current repository for untracked files and count them. + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ $ git ls-files --others --exclude-standard | wc -l 16ms in current dir │ + │ │ + │ 17 │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + 17 \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/second_message/msg.txt b/lib/msgfmt/testdata/format/cursor/second_message/msg.txt new file mode 100644 index 0000000..1e76f20 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/second_message/msg.txt @@ -0,0 +1,27 @@ + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ How many untracked files are there? │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + I’m going to check the current repository for untracked files and count them. + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ $ git ls-files --others --exclude-standard | wc -l 16ms in current dir │ + │ │ + │ 17 │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + 17 + + + + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ → Add a follow-up │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + ▶︎ Auto-run all commands (shift+tab to turn off) + + OpenAI GPT-5 + / for commands · @ for files diff --git a/lib/msgfmt/testdata/format/cursor/second_message/user.txt b/lib/msgfmt/testdata/format/cursor/second_message/user.txt new file mode 100644 index 0000000..f739c91 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/second_message/user.txt @@ -0,0 +1 @@ +How many untracked files are there? \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/thinking/expected.txt b/lib/msgfmt/testdata/format/cursor/thinking/expected.txt new file mode 100644 index 0000000..c0c75aa --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/thinking/expected.txt @@ -0,0 +1 @@ + ⬡ Thinking. 172 tokens \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/thinking/msg.txt b/lib/msgfmt/testdata/format/cursor/thinking/msg.txt new file mode 100644 index 0000000..0975dfb --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/thinking/msg.txt @@ -0,0 +1,17 @@ + Cursor Agent + ~/Documents/work/agentapi · feat-cursor-cli + + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Which repo is this ? │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + + + ⬡ Thinking. 172 tokens + ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ → Add a follow-up ctrl+c to stop │ + └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + + OpenAI GPT-5 + / for commands · @ for files \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/cursor/thinking/user.txt b/lib/msgfmt/testdata/format/cursor/thinking/user.txt new file mode 100644 index 0000000..c7d3776 --- /dev/null +++ b/lib/msgfmt/testdata/format/cursor/thinking/user.txt @@ -0,0 +1 @@ +Which repo is this ? \ No newline at end of file From 12fe8d10210350340e3da8f2327786d11961d83e Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:34:20 +0530 Subject: [PATCH 2/7] Update README.md Co-authored-by: Cian Johnston --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5502f5..2fafa62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AgentAPI -Control [Claude Code](https://github.com/anthropics/claude-code), [Goose](https://github.com/block/goose), [Aider](https://github.com/Aider-AI/aider), [Gemini](https://github.com/google-gemini/gemini-cli), [Sourcegraph Amp](https://github.com/sourcegraph/amp-cli), [Codex](https://github.com/openai/codex) and [Cursor CLI](https://cursor.com/en/cli) with an HTTP API. +Control [Claude Code](https://github.com/anthropics/claude-code), [Goose](https://github.com/block/goose), [Aider](https://github.com/Aider-AI/aider), [Gemini](https://github.com/google-gemini/gemini-cli), [Sourcegraph Amp](https://github.com/sourcegraph/amp-cli), [Codex](https://github.com/openai/codex), and [Cursor CLI](https://cursor.com/en/cli) with an HTTP API. ![agentapi-chat](https://github.com/user-attachments/assets/57032c9f-4146-4b66-b219-09e38ab7690d) From d49ea90363189edc1b4a321ee25e0cd88a247371 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:34:29 +0530 Subject: [PATCH 3/7] Update lib/msgfmt/msgfmt.go Co-authored-by: Cian Johnston --- lib/msgfmt/msgfmt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index d4baf50..00aa433 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -142,7 +142,8 @@ func findUserInputEndIdx(userInputStartIdx int, msg []rune, userInput []rune) in // skipTrailingInputBoxLine skips the next line if it contains any of the markers. // In case of Gemini and Cursor, the user input is echoed back in a box -// This function searches for the markers passed by the caller and skips the next line if it contains all of them. +// This function searches for the markers passed by the caller and skips the next line +// if it contains all of them in the same order. func skipTrailingInputBoxLine(lines []string, lastUserInputLineIdx *int, markers ...string) { if *lastUserInputLineIdx+1 >= len(lines) { return From b09f7ffe66d1bd922ab7816cd3b099a6fa3e69ea Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 15 Aug 2025 18:58:12 +0530 Subject: [PATCH 4/7] feat: improve code quality --- lib/msgfmt/msgfmt.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index 00aa433..bd1ca23 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -140,21 +140,22 @@ func findUserInputEndIdx(userInputStartIdx int, msg []rune, userInput []rune) in return msgIdx } -// skipTrailingInputBoxLine skips the next line if it contains any of the markers. -// In case of Gemini and Cursor, the user input is echoed back in a box -// This function searches for the markers passed by the caller and skips the next line -// if it contains all of them in the same order. -func skipTrailingInputBoxLine(lines []string, lastUserInputLineIdx *int, markers ...string) { - if *lastUserInputLineIdx+1 >= len(lines) { - return +// skipTrailingInputBoxLine checks if the next line contains all the given markers +// and returns the incremented index if found. In case of Gemini and Cursor, the user +// input is echoed back in a box. This function searches for the markers passed by the +// caller and returns (currentIdx+1, true) if the next line contains all of them in the same order, +// otherwise returns (currentIdx, false). +func skipTrailingInputBoxLine(lines []string, currentIdx int, markers ...string) (idx int, found bool) { + if currentIdx+1 >= len(lines) { + return currentIdx, false } - line := lines[*lastUserInputLineIdx+1] + line := lines[currentIdx+1] for _, m := range markers { if !strings.Contains(line, m) { - return + return currentIdx, false } } - *lastUserInputLineIdx++ + return currentIdx + 1, true } // RemoveUserInput removes the user input from the message. @@ -186,8 +187,15 @@ func RemoveUserInput(msgRaw string, userInputRaw string) string { // that doesn't contain the echoed user input. lastUserInputLineIdx := msgRuneLineLocations[userInputEndIdx] - skipTrailingInputBoxLine(msgLines, &lastUserInputLineIdx, "╯", "╰") // Gemini - skipTrailingInputBoxLine(msgLines, &lastUserInputLineIdx, "┘", "└") // Cursor + // Skip Gemini trailing input box line + if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "╯", "╰"); found { + lastUserInputLineIdx = idx + } + + // Skip Cursor trailing input box line + if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found { + lastUserInputLineIdx = idx + } return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n") } From 54a0b74566d2ae2787b64b95b148162e9d5b7603 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 15 Aug 2025 19:12:55 +0530 Subject: [PATCH 5/7] feat: passdown agent type for safer formatting --- README.md | 2 +- lib/msgfmt/msgfmt.go | 34 +++++++++++++++++++--------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 2fafa62..b64c92f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ agentapi server -- goose ``` > [!NOTE] -> When using Codex, always specify the agent type explicitly (`agentapi server --type=codex -- codex`), or message formatting may break. +> When using Codex, Gemini 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). diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index bd1ca23..907f7e8 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -167,7 +167,7 @@ func skipTrailingInputBoxLine(lines []string, currentIdx int, markers ...string) // For instance, if there are any leading or trailing lines with only whitespace, // and each line of the input in msgRaw is preceded by a character like `>`, // these lines will not be removed. -func RemoveUserInput(msgRaw string, userInputRaw string) string { +func RemoveUserInput(msgRaw string, userInputRaw string, agentType AgentType) string { if userInputRaw == "" { return msgRaw } @@ -188,13 +188,17 @@ func RemoveUserInput(msgRaw string, userInputRaw string) string { lastUserInputLineIdx := msgRuneLineLocations[userInputEndIdx] // Skip Gemini trailing input box line - if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "╯", "╰"); found { - lastUserInputLineIdx = idx + if agentType == AgentTypeGemini { + if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "╯", "╰"); found { + lastUserInputLineIdx = idx + } } // Skip Cursor trailing input box line - if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found { - lastUserInputLineIdx = idx + if agentType == AgentTypeCursor { + if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found { + lastUserInputLineIdx = idx + } } return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n") @@ -234,15 +238,15 @@ const ( AgentTypeCustom AgentType = "custom" ) -func formatGenericMessage(message string, userInput string) string { - message = RemoveUserInput(message, userInput) +func formatGenericMessage(message string, userInput string, agentType AgentType) string { + message = RemoveUserInput(message, userInput, agentType) message = removeMessageBox(message) message = trimEmptyLines(message) return message } func formatCodexMessage(message string, userInput string) string { - message = RemoveUserInput(message, userInput) + message = RemoveUserInput(message, userInput, AgentTypeCodex) message = removeCodexInputBox(message) message = trimEmptyLines(message) return message @@ -251,21 +255,21 @@ func formatCodexMessage(message string, userInput string) string { func FormatAgentMessage(agentType AgentType, message string, userInput string) string { switch agentType { case AgentTypeClaude: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeGoose: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeAider: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeCodex: return formatCodexMessage(message, userInput) case AgentTypeGemini: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeAmp: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeCursor: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) case AgentTypeCustom: - return formatGenericMessage(message, userInput) + return formatGenericMessage(message, userInput, agentType) default: return message } From e1f136d934b4c18761d4858fbb2a89cf18d079ed Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 15 Aug 2025 19:15:50 +0530 Subject: [PATCH 6/7] fix: tests --- lib/msgfmt/msgfmt_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msgfmt/msgfmt_test.go b/lib/msgfmt/msgfmt_test.go index 837ff52..404371e 100644 --- a/lib/msgfmt/msgfmt_test.go +++ b/lib/msgfmt/msgfmt_test.go @@ -188,7 +188,7 @@ func TestRemoveUserInput(t *testing.T) { assert.NoError(t, err) expected, err := testdataDir.ReadFile(path.Join(dir, c.Name(), "expected.txt")) assert.NoError(t, err) - assert.Equal(t, string(expected), RemoveUserInput(string(msg), string(userInput))) + assert.Equal(t, string(expected), RemoveUserInput(string(msg), string(userInput), AgentTypeCustom)) }) } } From 153b3e137d699fc4047a41b15ba27a755b6c95a7 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 15 Aug 2025 21:51:54 +0530 Subject: [PATCH 7/7] chore: code improvements --- lib/msgfmt/msgfmt.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index 907f7e8..f7478dd 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -187,15 +187,12 @@ func RemoveUserInput(msgRaw string, userInputRaw string, agentType AgentType) st // that doesn't contain the echoed user input. lastUserInputLineIdx := msgRuneLineLocations[userInputEndIdx] - // Skip Gemini trailing input box line + // Skip Gemini/Cursor trailing input box line if agentType == AgentTypeGemini { if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "╯", "╰"); found { lastUserInputLineIdx = idx } - } - - // Skip Cursor trailing input box line - if agentType == AgentTypeCursor { + } else if agentType == AgentTypeCursor { if idx, found := skipTrailingInputBoxLine(msgLines, lastUserInputLineIdx, "┘", "└"); found { lastUserInputLineIdx = idx }