Skip to content

Commit 9eabdd0

Browse files
authored
Merge pull request router-for-me#2522 from aikins01/fix/strip-tool-use-signature
fix(amp): strip signature from tool_use blocks before forwarding to Claude
2 parents c3f8dc3 + b0653ce commit 9eabdd0

2 files changed

Lines changed: 49 additions & 8 deletions

File tree

internal/api/modules/amp/response_rewriter.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package amp
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"fmt"
67
"net/http"
78
"strings"
@@ -290,8 +291,10 @@ func (rw *ResponseRewriter) rewriteStreamEvent(data []byte) []byte {
290291
}
291292

292293
// SanitizeAmpRequestBody removes thinking blocks with empty/missing/invalid signatures
293-
// from the messages array in a request body before forwarding to the upstream API.
294-
// This prevents 400 errors from the API which requires valid signatures on thinking blocks.
294+
// and strips the proxy-injected "signature" field from tool_use blocks in the messages
295+
// array before forwarding to the upstream API.
296+
// This prevents 400 errors from the API which requires valid signatures on thinking
297+
// blocks and does not accept a signature field on tool_use blocks.
295298
func SanitizeAmpRequestBody(body []byte) []byte {
296299
messages := gjson.GetBytes(body, "messages")
297300
if !messages.Exists() || !messages.IsArray() {
@@ -309,21 +312,30 @@ func SanitizeAmpRequestBody(body []byte) []byte {
309312
}
310313

311314
var keepBlocks []interface{}
312-
removedCount := 0
315+
contentModified := false
313316

314317
for _, block := range content.Array() {
315318
blockType := block.Get("type").String()
316319
if blockType == "thinking" {
317320
sig := block.Get("signature")
318321
if !sig.Exists() || sig.Type != gjson.String || strings.TrimSpace(sig.String()) == "" {
319-
removedCount++
322+
contentModified = true
320323
continue
321324
}
322325
}
323-
keepBlocks = append(keepBlocks, block.Value())
326+
327+
// Use raw JSON to prevent float64 rounding of large integers in tool_use inputs
328+
blockRaw := []byte(block.Raw)
329+
if blockType == "tool_use" && block.Get("signature").Exists() {
330+
blockRaw, _ = sjson.DeleteBytes(blockRaw, "signature")
331+
contentModified = true
332+
}
333+
334+
// sjson.SetBytes supports raw JSON strings if wrapped in gjson.Raw
335+
keepBlocks = append(keepBlocks, json.RawMessage(blockRaw))
324336
}
325337

326-
if removedCount > 0 {
338+
if contentModified {
327339
contentPath := fmt.Sprintf("messages.%d.content", msgIdx)
328340
var err error
329341
if len(keepBlocks) == 0 {
@@ -332,11 +344,10 @@ func SanitizeAmpRequestBody(body []byte) []byte {
332344
body, err = sjson.SetBytes(body, contentPath, keepBlocks)
333345
}
334346
if err != nil {
335-
log.Warnf("Amp RequestSanitizer: failed to remove thinking blocks from message %d: %v", msgIdx, err)
347+
log.Warnf("Amp RequestSanitizer: failed to sanitize message %d: %v", msgIdx, err)
336348
continue
337349
}
338350
modified = true
339-
log.Debugf("Amp RequestSanitizer: removed %d thinking blocks with invalid signatures from message %d", removedCount, msgIdx)
340351
}
341352
}
342353

internal/api/modules/amp/response_rewriter_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,36 @@ func TestSanitizeAmpRequestBody_RemovesWhitespaceAndNonStringSignatures(t *testi
145145
}
146146
}
147147

148+
func TestSanitizeAmpRequestBody_StripsSignatureFromToolUseBlocks(t *testing.T) {
149+
input := []byte(`{"messages":[{"role":"assistant","content":[{"type":"thinking","thinking":"thought","signature":"valid-sig"},{"type":"tool_use","id":"toolu_01","name":"Bash","input":{"cmd":"ls"},"signature":""}]}]}`)
150+
result := SanitizeAmpRequestBody(input)
151+
152+
if contains(result, []byte(`"signature":""`)) {
153+
t.Fatalf("expected signature to be stripped from tool_use block, got %s", string(result))
154+
}
155+
if !contains(result, []byte(`"valid-sig"`)) {
156+
t.Fatalf("expected thinking signature to remain, got %s", string(result))
157+
}
158+
if !contains(result, []byte(`"tool_use"`)) {
159+
t.Fatalf("expected tool_use block to remain, got %s", string(result))
160+
}
161+
}
162+
163+
func TestSanitizeAmpRequestBody_MixedInvalidThinkingAndToolUseSignature(t *testing.T) {
164+
input := []byte(`{"messages":[{"role":"assistant","content":[{"type":"thinking","thinking":"drop-me","signature":""},{"type":"tool_use","id":"toolu_01","name":"Bash","input":{"cmd":"ls"},"signature":""}]}]}`)
165+
result := SanitizeAmpRequestBody(input)
166+
167+
if contains(result, []byte("drop-me")) {
168+
t.Fatalf("expected invalid thinking block to be removed, got %s", string(result))
169+
}
170+
if contains(result, []byte(`"signature"`)) {
171+
t.Fatalf("expected signature to be stripped from tool_use block, got %s", string(result))
172+
}
173+
if !contains(result, []byte(`"tool_use"`)) {
174+
t.Fatalf("expected tool_use block to remain, got %s", string(result))
175+
}
176+
}
177+
148178
func contains(data, substr []byte) bool {
149179
for i := 0; i <= len(data)-len(substr); i++ {
150180
if string(data[i:i+len(substr)]) == string(substr) {

0 commit comments

Comments
 (0)