diff --git a/.gitignore b/.gitignore index 5b47947..dee2827 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ snapshot2-*.log schema.yaml **/.claude/settings.local.json out + + +#IDEs +.idea \ No newline at end of file diff --git a/README.md b/README.md index 7150d0b..1956061 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), 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) and [Codex](https://github.com/openai/codex) with an HTTP API. ![agentapi-chat](https://github.com/user-attachments/assets/57032c9f-4146-4b66-b219-09e38ab7690d) diff --git a/cmd/root.go b/cmd/root.go index 49b99bb..3dabbc9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,7 +12,7 @@ import ( var rootCmd = &cobra.Command{ Use: "agentapi", Short: "AgentAPI CLI", - Long: `AgentAPI - HTTP API for Claude Code, Goose, Aider, and Codex`, + Long: `AgentAPI - HTTP API for Claude Code, Goose, Aider, Gemini and Codex`, Version: "0.2.3", } diff --git a/cmd/server/server.go b/cmd/server/server.go index 58d0964..5cb479a 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -34,6 +34,7 @@ const ( AgentTypeGoose AgentType = msgfmt.AgentTypeGoose AgentTypeAider AgentType = msgfmt.AgentTypeAider AgentTypeCodex AgentType = msgfmt.AgentTypeCodex + AgentTypeGemini AgentType = msgfmt.AgentTypeGemini AgentTypeCustom AgentType = msgfmt.AgentTypeCustom ) @@ -46,6 +47,8 @@ func parseAgentType(firstArg string, agentTypeVar string) (AgentType, error) { agentType = AgentTypeGoose case string(AgentTypeAider): agentType = AgentTypeAider + case string(AgentTypeGemini): + agentType = AgentTypeGemini case string(AgentTypeCustom): agentType = AgentTypeCustom case string(AgentTypeCodex): @@ -68,6 +71,8 @@ func parseAgentType(firstArg string, agentTypeVar string) (AgentType, error) { agentType = AgentTypeAider case string(AgentTypeCodex): agentType = AgentTypeCodex + case string(AgentTypeGemini): + agentType = AgentTypeGemini default: agentType = AgentTypeCustom } @@ -137,7 +142,7 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er var ServerCmd = &cobra.Command{ Use: "server [agent]", Short: "Run the server", - Long: `Run the server with the specified agent (claude, goose, aider, codex)`, + Long: `Run the server with the specified agent (claude, goose, aider, gemini, codex)`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) diff --git a/cmd/server/server_test.go b/cmd/server/server_test.go index fd4a032..ff0611e 100644 --- a/cmd/server/server_test.go +++ b/cmd/server/server_test.go @@ -23,6 +23,11 @@ func TestParseAgentType(t *testing.T) { agentTypeVar: "", want: AgentTypeClaude, }, + { + firstArg: "gemini", + agentTypeVar: "", + want: AgentTypeGemini, + }, { firstArg: "goose", agentTypeVar: "", @@ -48,6 +53,11 @@ func TestParseAgentType(t *testing.T) { agentTypeVar: "claude", want: AgentTypeClaude, }, + { + firstArg: "claude", + agentTypeVar: "gemini", + want: AgentTypeGemini, + }, { firstArg: "aider", agentTypeVar: "claude", diff --git a/lib/msgfmt/msgfmt.go b/lib/msgfmt/msgfmt.go index ca1e83b..cca75d1 100644 --- a/lib/msgfmt/msgfmt.go +++ b/lib/msgfmt/msgfmt.go @@ -1,6 +1,8 @@ package msgfmt -import "strings" +import ( + "strings" +) const WhiteSpaceChars = " \t\n\r\f\v" @@ -166,6 +168,12 @@ func RemoveUserInput(msgRaw string, userInputRaw string) string { // Return the original message starting with the first line // 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 + } + return strings.Join(msgLines[lastUserInputLineIdx+1:], "\n") } @@ -197,6 +205,7 @@ const ( AgentTypeGoose AgentType = "goose" AgentTypeAider AgentType = "aider" AgentTypeCodex AgentType = "codex" + AgentTypeGemini AgentType = "gemini" AgentTypeCustom AgentType = "custom" ) @@ -217,6 +226,8 @@ func FormatAgentMessage(agentType AgentType, message string, userInput string) s return formatGenericMessage(message, userInput) case AgentTypeCodex: return formatGenericMessage(message, userInput) + case AgentTypeGemini: + return formatGenericMessage(message, userInput) case AgentTypeCustom: return formatGenericMessage(message, userInput) default: diff --git a/lib/msgfmt/msgfmt_test.go b/lib/msgfmt/msgfmt_test.go index 9758741..b02cbbc 100644 --- a/lib/msgfmt/msgfmt_test.go +++ b/lib/msgfmt/msgfmt_test.go @@ -218,7 +218,7 @@ func TestTrimEmptyLines(t *testing.T) { func TestFormatAgentMessage(t *testing.T) { dir := "testdata/format" - agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeCodex, AgentTypeCustom} + agentTypes := []AgentType{AgentTypeClaude, AgentTypeGoose, AgentTypeAider, AgentTypeGemini, AgentTypeCodex, AgentTypeCustom} for _, agentType := range agentTypes { t.Run(string(agentType), func(t *testing.T) { cases, err := testdataDir.ReadDir(path.Join(dir, string(agentType))) diff --git a/lib/msgfmt/testdata/format/gemini/first_message/expected.txt b/lib/msgfmt/testdata/format/gemini/first_message/expected.txt new file mode 100644 index 0000000..baf10ec --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/first_message/expected.txt @@ -0,0 +1,5 @@ +Tips for getting started: +1. Ask questions, edit files, or run commands. +2. Be specific for the best results. +3. Create GEMINI.md files to customize your interactions with Gemini. +4. /help for more information. \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/gemini/first_message/msg.txt b/lib/msgfmt/testdata/format/gemini/first_message/msg.txt new file mode 100644 index 0000000..2cad627 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/first_message/msg.txt @@ -0,0 +1,13 @@ +Tips for getting started: +1. Ask questions, edit files, or run commands. +2. Be specific for the best results. +3. Create GEMINI.md files to customize your interactions with Gemini. +4. /help for more information. + + + +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ > Type your message or @path/to/file │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +~/Documents/work/agentapi (feat-claude-cli*) no sandbox (see /docs) gemini-2.5-pro (100% context left) diff --git a/lib/msgfmt/testdata/format/gemini/first_message/user.txt b/lib/msgfmt/testdata/format/gemini/first_message/user.txt new file mode 100644 index 0000000..e69de29 diff --git a/lib/msgfmt/testdata/format/gemini/multi-line-input/expected.txt b/lib/msgfmt/testdata/format/gemini/multi-line-input/expected.txt new file mode 100644 index 0000000..00bee4c --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/multi-line-input/expected.txt @@ -0,0 +1,14 @@ + ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ✔ SearchText 'setInterval\(fetchMessages, 1000\)' in **/*.tsx │ + │ │ + │ No matches found │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ✔ SearchText 'checkServerStatus, 250' in **/*.tsx │ + │ │ + │ No matches found │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +✦ I couldn't find that exact code snippet in the project. It's possible it has been modified or is from a file that wasn't included in the initial project listing. + + To help me locate it, could you tell me more about what the code does or what component it might be a part of? For example, is it part of the main chat window, a status indicator, + or something else? \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/gemini/multi-line-input/msg.txt b/lib/msgfmt/testdata/format/gemini/multi-line-input/msg.txt new file mode 100644 index 0000000..2618aa1 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/multi-line-input/msg.txt @@ -0,0 +1,43 @@ +╭───────────────────────────────────────────────────────────────────╮ +│ > Which file is this code from? │ +│ │ +│ ```ts │ +│ // Set up polling for messages and server status │ +│ useEffect(() => { │ +│ // Check server status initially │ +│ checkServerStatus(); │ +│ │ +│ // Set up polling intervals │ +│ const messageInterval = setInterval(fetchMessages, 1000); │ +│ const statusInterval = setInterval(checkServerStatus, 250); │ +│ │ +│ // Clean up intervals on component unmount │ +│ return () => { │ +│ clearInterval(messageInterval); │ +│ clearInterval(statusInterval); │ +│ }; │ +│ }, []); │ +│ ``` │ +╰───────────────────────────────────────────────────────────────────╯ + + ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ✔ SearchText 'setInterval\(fetchMessages, 1000\)' in **/*.tsx │ + │ │ + │ No matches found │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ✔ SearchText 'checkServerStatus, 250' in **/*.tsx │ + │ │ + │ No matches found │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +✦ I couldn't find that exact code snippet in the project. It's possible it has been modified or is from a file that wasn't included in the initial project listing. + + To help me locate it, could you tell me more about what the code does or what component it might be a part of? For example, is it part of the main chat window, a status indicator, + or something else? + + +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ > Type your message or @path/to/file │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +~/Documents/work/agentapi (feat-claude-cli*) no sandbox (see /docs) gemini-2.5-pro (99% context left) diff --git a/lib/msgfmt/testdata/format/gemini/multi-line-input/user.txt b/lib/msgfmt/testdata/format/gemini/multi-line-input/user.txt new file mode 100644 index 0000000..c3b14b0 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/multi-line-input/user.txt @@ -0,0 +1,19 @@ +Which file is this code from? + +```ts +// Set up polling for messages and server status +useEffect(() => { + // Check server status initially + checkServerStatus(); + + // Set up polling intervals + const messageInterval = setInterval(fetchMessages, 1000); + const statusInterval = setInterval(checkServerStatus, 250); + + // Clean up intervals on component unmount + return () => { + clearInterval(messageInterval); + clearInterval(statusInterval); + }; +}, []); +``` diff --git a/lib/msgfmt/testdata/format/gemini/second_message/expected.txt b/lib/msgfmt/testdata/format/gemini/second_message/expected.txt new file mode 100644 index 0000000..c407519 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/second_message/expected.txt @@ -0,0 +1 @@ +✦ I am ready to assist you. What can I help you with? \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/gemini/second_message/msg.txt b/lib/msgfmt/testdata/format/gemini/second_message/msg.txt new file mode 100644 index 0000000..21e86b3 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/second_message/msg.txt @@ -0,0 +1,12 @@ +╭──────────────────╮ +│ > How are you? │ +╰──────────────────╯ + +✦ I am ready to assist you. What can I help you with? + + +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ > Type your message or @path/to/file │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + +~/Documents/work/agentapi (feat-claude-cli*) no sandbox (see /docs) gemini-2.5-pro (99% context left) \ No newline at end of file diff --git a/lib/msgfmt/testdata/format/gemini/second_message/user.txt b/lib/msgfmt/testdata/format/gemini/second_message/user.txt new file mode 100644 index 0000000..3099b43 --- /dev/null +++ b/lib/msgfmt/testdata/format/gemini/second_message/user.txt @@ -0,0 +1 @@ +How are you? \ No newline at end of file