Skip to content

feat: implement initial AxeClient for remote agent execution - #51

Open
jhawpetoss6-collab wants to merge 1 commit into
jrswab:masterfrom
jhawpetoss6-collab:strike/api-client-implementation
Open

feat: implement initial AxeClient for remote agent execution#51
jhawpetoss6-collab wants to merge 1 commit into
jrswab:masterfrom
jhawpetoss6-collab:strike/api-client-implementation

Conversation

@jhawpetoss6-collab

@jhawpetoss6-collab jhawpetoss6-collab commented Mar 24, 2026

Copy link
Copy Markdown

Summary

Introduces initial AxeClient implementation in pkg/client for programmatic remote agent execution. The client supports API-key authentication and uses only the Go standard library, providing a lightweight, dependency-free solution for triggering and managing Axe agents.

Changelog

Added

  • AxeClient type with BaseURL and APIKey fields for remote agent communication
  • ExecuteAgent method to trigger remote agent execution with input parameters via POST requests to the /v1/agents/{agentID}/execute endpoint
  • Bearer token authentication using API key in request headers

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A new client module (pkg/client/api.go) introduces the AxeClient type with BaseURL and APIKey fields, along with an ExecuteAgent method that constructs and executes HTTP POST requests to an agent execution endpoint with JSON encoding and Bearer token authentication.

Changes

Cohort / File(s) Summary
New API Client
pkg/client/api.go
Added AxeClient struct with BaseURL and APIKey fields. Implemented ExecuteAgent method to execute agents via POST requests to versioned API endpoints, handling JSON serialization, Bearer token auth, and response decoding. Note: JSON marshaling and decoding errors are not handled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

✨ A fresh client emerges from the code,
With BaseURL and keys to lighten the load,
Agents execute with HTTP's grace,
JSON dancing through cyberspace! 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: introducing an initial AxeClient for executing remote agents, which is exactly what the changeset implements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/client/api.go`:
- Around line 17-19: The code currently discards errors from json.Marshal and
http.NewRequest and also ignores Decode errors and HTTP status codes; update the
function in pkg/client/api.go to check and return wrapped errors from
json.Marshal(input) and http.NewRequest("POST", url, ...) (include context like
"failed to marshal request body" and "failed to construct HTTP request"), ensure
client.Do(req) is only called if req != nil, validate resp.StatusCode and return
a clear error if it's not 2xx before attempting to decode, and handle/return
errors from json.NewDecoder(resp.Body).Decode(...) with contextual messages
(e.g., "failed to decode response body"); reference the symbols json.Marshal,
http.NewRequest, client.Do(req), resp.StatusCode, and
json.NewDecoder(...).Decode when making these changes.
- Around line 24-32: The HTTP call handling in the function that calls
client.Do(req) must be hardened: wrap the error returned from client.Do with
contextual hints (network, API key, BaseURL) before returning, verify
resp.StatusCode is 2xx (use resp.StatusCode) and return an explicit error
including the status and response body for non-2xx responses, and handle JSON
decoding errors from json.NewDecoder(resp.Body).Decode(&result) by returning a
wrapped decode error instead of ignoring it; keep the existing defer
resp.Body.Close() and ensure you import any error-wrapping or ioutil/io packages
needed to read the body for error messages.
- Around line 23-24: AxeClient currently constructs a new http.Client per call
(client := &http.Client{}) causing no timeouts and poor connection reuse; add an
HTTPClient field (e.g., HTTPClient http.Client or HTTPClient *http.Client) to
the AxeClient struct, use that field instead of creating a local client in the
method that calls client.Do(req), and ensure the constructor/newter for
AxeClient sets a sensible default timeout (e.g., 10s) when HTTPClient is nil so
callers can inject a custom client while preserving a safe fallback.
- Line 16: The code concatenates raw agentID into the path and also creates a
local variable named url which shadows the net/url package; change to normalize
the base with strings.TrimRight(c.BaseURL, "/") (or ensure trailing slash
handling), escape the agent ID with url.PathEscape(agentID), and assign the
result to a non-conflicting name such as endpoint (e.g., endpoint := base +
"/v1/agents/" + url.PathEscape(agentID) + "/execute") so you don't shadow the
net/url import and the path is safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a83f607-ec3b-487c-a892-440c13eca214

📥 Commits

Reviewing files that changed from the base of the PR and between f908407 and 499d3e0.

📒 Files selected for processing (1)
  • pkg/client/api.go

Comment thread pkg/client/api.go
}

func (c *AxeClient) ExecuteAgent(agentID string, input map[string]interface{}) (map[string]interface{}, error) {
url := c.BaseURL + "/v1/agents/" + agentID + "/execute"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

cat -n pkg/client/api.go

Repository: jrswab/axe


Repository: jrswab/axe
Exit code: 0

stdout:

     1	package client
     2	
     3	import (
     4		"bytes"
     5		"encoding/json"
     6		"net/http"
     7	)
     8	
     9	// AxeClient handles communication with the Axe agent service.
    10	type AxeClient struct {
    11		BaseURL string
    12		APIKey  string
    13	}
    14	
    15	func (c *AxeClient) ExecuteAgent(agentID string, input map[string]interface{}) (map[string]interface{}, error) {
    16		url := c.BaseURL + "/v1/agents/" + agentID + "/execute"
    17		data, _ := json.Marshal(input)
    18		
    19		req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
    20		req.Header.Set("Authorization", "Bearer " + c.APIKey)
    21		req.Header.Set("Content-Type", "application/json")
    22	
    23		client := &http.Client{}
    24		resp, err := client.Do(req)
    25		if err != nil {
    26			return nil, err
    27		}
    28		defer resp.Body.Close()
    29	
    30		var result map[string]interface{}
    31		json.NewDecoder(resp.Body).Decode(&result)
    32		return result, nil
    33	}

Script executed:

rg "BaseURL.*\+" pkg/

Repository: jrswab/axe


Repository: jrswab/axe
Exit code: 0

stdout:

pkg/client/api.go:	url := c.BaseURL + "/v1/agents/" + agentID + "/execute"

Escape agentID before building the endpoint path — but fix the variable shadowing in the suggested solution.

Line 16 concatenates raw agentID into the path. Special characters like /, ?, or spaces will corrupt the URL structure. Use url.PathEscape(agentID) to safely escape the ID, and normalize the BaseURL trailing slash.

However, the suggested fix has a critical bug: assigning to a variable named url shadows the net/url package import, causing url.PathEscape() to fail. Use a different variable name like endpoint instead:

Corrected fix
 import (
 	"bytes"
 	"encoding/json"
 	"net/http"
+	"net/url"
+	"strings"
 )
@@
-	url := c.BaseURL + "/v1/agents/" + agentID + "/execute"
+	base := strings.TrimRight(c.BaseURL, "/")
+	endpoint := base + "/v1/agents/" + url.PathEscape(agentID) + "/execute"
-	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
+	req, _ := http.NewRequest("POST", endpoint, bytes.NewBuffer(data))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
url := c.BaseURL + "/v1/agents/" + agentID + "/execute"
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"strings"
)
// ... in the relevant function:
base := strings.TrimRight(c.BaseURL, "/")
endpoint := base + "/v1/agents/" + url.PathEscape(agentID) + "/execute"
req, _ := http.NewRequest("POST", endpoint, bytes.NewBuffer(data))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/client/api.go` at line 16, The code concatenates raw agentID into the
path and also creates a local variable named url which shadows the net/url
package; change to normalize the base with strings.TrimRight(c.BaseURL, "/") (or
ensure trailing slash handling), escape the agent ID with
url.PathEscape(agentID), and assign the result to a non-conflicting name such as
endpoint (e.g., endpoint := base + "/v1/agents/" + url.PathEscape(agentID) +
"/execute") so you don't shadow the net/url import and the path is safe.

Comment thread pkg/client/api.go
Comment on lines +17 to +19
data, _ := json.Marshal(input)

req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the api.go file
find . -name "api.go" -path "*/client/*" | head -5

Repository: jrswab/axe

Length of output: 73


🏁 Script executed:

cat -n ./pkg/client/api.go

Repository: jrswab/axe

Length of output: 1067


Handle request-construction errors; right now they're silently dropped.

Lines 17 and 19 discard json.Marshal and http.NewRequest errors respectively. If request creation fails, req could be nil, making line 24 (client.Do(req)) unsafe. Additionally, line 31 silently ignores Decode errors, and there's no HTTP status code validation before attempting to decode the response. Bubble these up with actionable error messages that tell users how to fix the issue.

Suggested fix
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"net/http"
 )
@@
-	data, _ := json.Marshal(input)
+	data, err := json.Marshal(input)
+	if err != nil {
+		return nil, fmt.Errorf("encode execute payload as JSON: ensure input contains JSON-serializable values: %w", err)
+	}
 	
-	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
+	if err != nil {
+		return nil, fmt.Errorf("build execute request: check BaseURL %q and agentID %q: %w", c.BaseURL, agentID, err)
+	}

As per coding guidelines, "Errors should help the user fix the problem, not just describe it."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data, _ := json.Marshal(input)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
data, err := json.Marshal(input)
if err != nil {
return nil, fmt.Errorf("encode execute payload as JSON: ensure input contains JSON-serializable values: %w", err)
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("build execute request: check BaseURL %q and agentID %q: %w", c.BaseURL, agentID, err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/client/api.go` around lines 17 - 19, The code currently discards errors
from json.Marshal and http.NewRequest and also ignores Decode errors and HTTP
status codes; update the function in pkg/client/api.go to check and return
wrapped errors from json.Marshal(input) and http.NewRequest("POST", url, ...)
(include context like "failed to marshal request body" and "failed to construct
HTTP request"), ensure client.Do(req) is only called if req != nil, validate
resp.StatusCode and return a clear error if it's not 2xx before attempting to
decode, and handle/return errors from json.NewDecoder(resp.Body).Decode(...)
with contextual messages (e.g., "failed to decode response body"); reference the
symbols json.Marshal, http.NewRequest, client.Do(req), resp.StatusCode, and
json.NewDecoder(...).Decode when making these changes.

Comment thread pkg/client/api.go
Comment on lines +23 to +24
client := &http.Client{}
resp, err := client.Do(req)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file structure
find . -name "api.go" -type f | head -20

Repository: jrswab/axe

Length of output: 73


🏁 Script executed:

# Check the pkg/client/api.go file around lines 23-24
if [ -f "pkg/client/api.go" ]; then
  wc -l pkg/client/api.go
  echo "---"
  head -40 pkg/client/api.go
fi

Repository: jrswab/axe

Length of output: 861


🏁 Script executed:

# Let's see the broader context and method signatures
if [ -f "pkg/client/api.go" ]; then
  cat -n pkg/client/api.go | head -60
fi

Repository: jrswab/axe

Length of output: 1067


🏁 Script executed:

# Search for AxeClient struct definition and usage
rg "type AxeClient" -A 5

Repository: jrswab/axe

Length of output: 333


🏁 Script executed:

# Look for http.Client creation patterns in the codebase
rg "http\.Client\{\}" -B 2 -A 2 --type go

Repository: jrswab/axe

Length of output: 268


🏁 Script executed:

# Check for any existing HTTPClient field or similar patterns
rg "HTTPClient|httpClient" --type go -B 2 -A 2

Repository: jrswab/axe

Length of output: 891


🏁 Script executed:

# Look for tests related to this API client
fd "test.go|_test.go" | xargs rg -l "AxeClient" 2>/dev/null || echo "No tests found with AxeClient"

Repository: jrswab/axe

Length of output: 83


Inject the HTTP client and set a timeout (no per-call client creation).

Line 23 creates a fresh &http.Client{} on every call—no timeout, no connection reuse. That's a reliability and performance footgun. Inject an HTTPClient field on AxeClient with a sensible default timeout fallback (the codebase already uses this pattern elsewhere for MCP transports).

This also aligns with the guideline: "No global state — pass dependencies explicitly."

Suggested refactor
 import (
 	"bytes"
 	"encoding/json"
 	"net/http"
+	"time"
 )
@@
 type AxeClient struct {
 	BaseURL string
 	APIKey  string
+	HTTPClient *http.Client
 }
@@
-	client := &http.Client{}
-	resp, err := client.Do(req)
+	httpClient := c.HTTPClient
+	if httpClient == nil {
+		httpClient = &http.Client{Timeout: 30 * time.Second}
+	}
+	resp, err := httpClient.Do(req)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
client := &http.Client{}
resp, err := client.Do(req)
httpClient := c.HTTPClient
if httpClient == nil {
httpClient = &http.Client{Timeout: 30 * time.Second}
}
resp, err := httpClient.Do(req)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/client/api.go` around lines 23 - 24, AxeClient currently constructs a new
http.Client per call (client := &http.Client{}) causing no timeouts and poor
connection reuse; add an HTTPClient field (e.g., HTTPClient http.Client or
HTTPClient *http.Client) to the AxeClient struct, use that field instead of
creating a local client in the method that calls client.Do(req), and ensure the
constructor/newter for AxeClient sets a sensible default timeout (e.g., 10s)
when HTTPClient is nil so callers can inject a custom client while preserving a
safe fallback.

Comment thread pkg/client/api.go
Comment on lines +24 to +32
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "api.go" -path "*/client/*" | head -20

Repository: jrswab/axe

Length of output: 73


🏁 Script executed:

wc -l pkg/client/api.go 2>/dev/null || echo "File not found, searching..."

Repository: jrswab/axe

Length of output: 74


🏁 Script executed:

if [ -f pkg/client/api.go ]; then
  sed -n '20,40p' pkg/client/api.go
else
  find . -type f -name "*.go" | grep -i api | head -10
fi

Repository: jrswab/axe

Length of output: 381


🏁 Script executed:

cat -n pkg/client/api.go

Repository: jrswab/axe

Length of output: 1067


Add HTTP status checking and JSON decode error handling to prevent silent failures.

Lines 24-32 silently return success for HTTP 4xx/5xx responses and ignore JSON decode errors—resulting in failed executions reported as successful. Additionally, the error at line 24 lacks context to help operators debug (network issue? Invalid API key? Wrong BaseURL?).

Import the necessary packages and:

  1. Wrap HTTP errors with diagnostic hints
  2. Check resp.StatusCode is in the 2xx range before reading the body
  3. Handle JSON decode errors explicitly
Suggested fix
 import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"io"
 	"net/http"
+	"strings"
 )
@@
 	client := &http.Client{}
 	resp, err := client.Do(req)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("execute agent %q request failed: verify BaseURL/network/API key: %w", agentID, err)
 	}
 	defer resp.Body.Close()
+
+	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
+		return nil, fmt.Errorf("execute agent %q failed with status %d: %s", agentID, resp.StatusCode, strings.TrimSpace(string(body)))
+	}
 
 	var result map[string]interface{}
-	json.NewDecoder(resp.Body).Decode(&result)
+	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+		return nil, fmt.Errorf("decode execute response for agent %q as JSON: %w", agentID, err)
+	}
 	return result, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/client/api.go` around lines 24 - 32, The HTTP call handling in the
function that calls client.Do(req) must be hardened: wrap the error returned
from client.Do with contextual hints (network, API key, BaseURL) before
returning, verify resp.StatusCode is 2xx (use resp.StatusCode) and return an
explicit error including the status and response body for non-2xx responses, and
handle JSON decoding errors from json.NewDecoder(resp.Body).Decode(&result) by
returning a wrapped decode error instead of ignoring it; keep the existing defer
resp.Body.Close() and ensure you import any error-wrapping or ioutil/io packages
needed to read the body for error messages.

@jrswab

jrswab commented Mar 25, 2026

Copy link
Copy Markdown
Owner

I like this idea! When you have time check out what CodeRabbit has to say and run make lint locally to fix the linter failures. Thanks!

@jrswab

jrswab commented Mar 27, 2026

Copy link
Copy Markdown
Owner

@jhawpetoss6-collab are you able to continue working on this effort or shall I pick it up?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants