Skip to content

Commit 58c02b9

Browse files
committed
Add body_key support to managed tool execution
When body_key is set on a tool execution entry, the proxy wraps the tool arguments as {body_key: args} instead of sending args flat.
1 parent f1379a5 commit 58c02b9

File tree

4 files changed

+76
-8
lines changed

4 files changed

+76
-8
lines changed

internal/agentctx/agentctx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type ToolExecution struct {
5353
BaseURL string `json:"base_url"`
5454
Method string `json:"method"`
5555
Path string `json:"path"`
56+
BodyKey string `json:"body_key,omitempty"`
5657
Auth *AuthEntry `json:"auth,omitempty"`
5758
}
5859

internal/agentctx/agentctx_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,17 @@ func TestLoadReadsToolsManifest(t *testing.T) {
220220
"version": 1,
221221
"tools": [
222222
{
223-
"name": "trading-api.get_market_context",
224-
"description": "Retrieve market context",
223+
"name": "trading-api.propose_trade",
224+
"description": "Submit trade proposal",
225225
"inputSchema": {"type": "object"},
226226
"annotations": {"readOnly": true},
227227
"execution": {
228228
"transport": "http",
229229
"service": "trading-api",
230230
"base_url": "http://trading-api:4000",
231-
"method": "GET",
232-
"path": "/api/v1/market_context/{claw_id}",
231+
"method": "POST",
232+
"path": "/api/v1/trades",
233+
"body_key": "trade",
233234
"auth": {"type": "bearer", "token": "tool-token"}
234235
}
235236
}
@@ -255,12 +256,15 @@ func TestLoadReadsToolsManifest(t *testing.T) {
255256
t.Fatalf("unexpected tools manifest header: %+v", ctx.Tools)
256257
}
257258
tool := ctx.Tools.Tools[0]
258-
if tool.Name != "trading-api.get_market_context" {
259+
if tool.Name != "trading-api.propose_trade" {
259260
t.Fatalf("unexpected tool name: %+v", tool)
260261
}
261262
if tool.Execution.Service != "trading-api" || tool.Execution.Auth == nil || tool.Execution.Auth.Token != "tool-token" {
262263
t.Fatalf("unexpected tool execution: %+v", tool.Execution)
263264
}
265+
if tool.Execution.BodyKey != "trade" {
266+
t.Fatalf("unexpected tool body key: %+v", tool.Execution)
267+
}
264268
if ctx.Tools.Policy.MaxRounds != 8 || ctx.Tools.Policy.TimeoutPerToolMS != 30000 || ctx.Tools.Policy.TotalTimeoutMS != 120000 {
265269
t.Fatalf("unexpected tool policy: %+v", ctx.Tools.Policy)
266270
}

internal/proxy/toolmediation.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ func (h *Handler) callManagedHTTPTool(ctx context.Context, agentID string, tool
693693
if err != nil {
694694
return toolErrorPayload("invalid_arguments", err.Error(), 0, nil), 0, nil
695695
}
696-
targetURL, body, err := buildManagedToolRequest(tool.Execution.BaseURL, strings.ToUpper(tool.Execution.Method), renderedPath, remaining)
696+
targetURL, body, err := buildManagedToolRequest(tool.Execution.BaseURL, strings.ToUpper(tool.Execution.Method), renderedPath, tool.Execution.BodyKey, remaining)
697697
if err != nil {
698698
return toolErrorPayload("request_build_failed", err.Error(), 0, nil), 0, nil
699699
}
@@ -765,7 +765,7 @@ func renderManagedToolPath(path string, agentID string, args map[string]any) (st
765765
return rendered, remaining, nil
766766
}
767767

768-
func buildManagedToolRequest(baseURL string, method string, path string, args map[string]any) (string, io.Reader, error) {
768+
func buildManagedToolRequest(baseURL string, method string, path string, bodyKey string, args map[string]any) (string, io.Reader, error) {
769769
u, err := url.Parse(strings.TrimSpace(baseURL))
770770
if err != nil {
771771
return "", nil, err
@@ -785,7 +785,11 @@ func buildManagedToolRequest(baseURL string, method string, path string, args ma
785785
if len(args) == 0 {
786786
args = map[string]any{}
787787
}
788-
body, err := json.Marshal(args)
788+
payload := any(args)
789+
if strings.TrimSpace(bodyKey) != "" {
790+
payload = map[string]any{strings.TrimSpace(bodyKey): args}
791+
}
792+
body, err := json.Marshal(payload)
789793
if err != nil {
790794
return "", nil, err
791795
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package proxy
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"testing"
8+
)
9+
10+
func TestBuildManagedToolRequestPreservesFlatJSONBodyByDefault(t *testing.T) {
11+
targetURL, body, err := buildManagedToolRequest("http://trading-api:4000", http.MethodPost, "/api/v1/trades", "", map[string]any{
12+
"ticker": "NVDA",
13+
"qty_requested": float64(50),
14+
})
15+
if err != nil {
16+
t.Fatalf("buildManagedToolRequest: %v", err)
17+
}
18+
if targetURL != "http://trading-api:4000/api/v1/trades" {
19+
t.Fatalf("unexpected target URL: %q", targetURL)
20+
}
21+
22+
raw, err := io.ReadAll(body)
23+
if err != nil {
24+
t.Fatalf("read body: %v", err)
25+
}
26+
var payload map[string]any
27+
if err := json.Unmarshal(raw, &payload); err != nil {
28+
t.Fatalf("unmarshal body: %v", err)
29+
}
30+
if payload["ticker"] != "NVDA" || payload["qty_requested"] != float64(50) {
31+
t.Fatalf("unexpected flat payload: %#v", payload)
32+
}
33+
}
34+
35+
func TestBuildManagedToolRequestWrapsJSONBodyWhenBodyKeyIsSet(t *testing.T) {
36+
targetURL, body, err := buildManagedToolRequest("http://trading-api:4000", http.MethodPost, "/api/v1/trades", "trade", map[string]any{
37+
"ticker": "NVDA",
38+
"qty_requested": float64(50),
39+
})
40+
if err != nil {
41+
t.Fatalf("buildManagedToolRequest: %v", err)
42+
}
43+
if targetURL != "http://trading-api:4000/api/v1/trades" {
44+
t.Fatalf("unexpected target URL: %q", targetURL)
45+
}
46+
47+
raw, err := io.ReadAll(body)
48+
if err != nil {
49+
t.Fatalf("read body: %v", err)
50+
}
51+
var payload map[string]map[string]any
52+
if err := json.Unmarshal(raw, &payload); err != nil {
53+
t.Fatalf("unmarshal body: %v", err)
54+
}
55+
trade := payload["trade"]
56+
if trade["ticker"] != "NVDA" || trade["qty_requested"] != float64(50) {
57+
t.Fatalf("unexpected wrapped payload: %#v", payload)
58+
}
59+
}

0 commit comments

Comments
 (0)