mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-20 21:30:50 +08:00
fix: add proxy_ prefix handling for tool_reference content blocks (#1)
applyClaudeToolPrefix, stripClaudeToolPrefixFromResponse, and stripClaudeToolPrefixFromStreamLine now handle "tool_reference" blocks (field "tool_name") in addition to "tool_use" blocks (field "name"). Without this fix, tool_reference blocks in conversation history retain their original unprefixed names while tool definitions carry the proxy_ prefix, causing Anthropic API 400 errors: "Tool reference 'X' not found in available tools." Co-authored-by: Kirill Turanskiy <kt@novamedia.ru>
This commit is contained in:
committed by
Kirill Turanskiy
parent
453aaf8774
commit
98f0a3e3bd
@@ -784,15 +784,22 @@ func applyClaudeToolPrefix(body []byte, prefix string) []byte {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
content.ForEach(func(contentIndex, part gjson.Result) bool {
|
content.ForEach(func(contentIndex, part gjson.Result) bool {
|
||||||
if part.Get("type").String() != "tool_use" {
|
partType := part.Get("type").String()
|
||||||
return true
|
if partType == "tool_use" {
|
||||||
}
|
|
||||||
name := part.Get("name").String()
|
name := part.Get("name").String()
|
||||||
if name == "" || strings.HasPrefix(name, prefix) {
|
if name == "" || strings.HasPrefix(name, prefix) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
path := fmt.Sprintf("messages.%d.content.%d.name", msgIndex.Int(), contentIndex.Int())
|
path := fmt.Sprintf("messages.%d.content.%d.name", msgIndex.Int(), contentIndex.Int())
|
||||||
body, _ = sjson.SetBytes(body, path, prefix+name)
|
body, _ = sjson.SetBytes(body, path, prefix+name)
|
||||||
|
} else if partType == "tool_reference" {
|
||||||
|
toolName := part.Get("tool_name").String()
|
||||||
|
if toolName == "" || strings.HasPrefix(toolName, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("messages.%d.content.%d.tool_name", msgIndex.Int(), contentIndex.Int())
|
||||||
|
body, _ = sjson.SetBytes(body, path, prefix+toolName)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
@@ -811,15 +818,22 @@ func stripClaudeToolPrefixFromResponse(body []byte, prefix string) []byte {
|
|||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
content.ForEach(func(index, part gjson.Result) bool {
|
content.ForEach(func(index, part gjson.Result) bool {
|
||||||
if part.Get("type").String() != "tool_use" {
|
partType := part.Get("type").String()
|
||||||
return true
|
if partType == "tool_use" {
|
||||||
}
|
|
||||||
name := part.Get("name").String()
|
name := part.Get("name").String()
|
||||||
if !strings.HasPrefix(name, prefix) {
|
if !strings.HasPrefix(name, prefix) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
path := fmt.Sprintf("content.%d.name", index.Int())
|
path := fmt.Sprintf("content.%d.name", index.Int())
|
||||||
body, _ = sjson.SetBytes(body, path, strings.TrimPrefix(name, prefix))
|
body, _ = sjson.SetBytes(body, path, strings.TrimPrefix(name, prefix))
|
||||||
|
} else if partType == "tool_reference" {
|
||||||
|
toolName := part.Get("tool_name").String()
|
||||||
|
if !strings.HasPrefix(toolName, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("content.%d.tool_name", index.Int())
|
||||||
|
body, _ = sjson.SetBytes(body, path, strings.TrimPrefix(toolName, prefix))
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return body
|
return body
|
||||||
@@ -834,17 +848,35 @@ func stripClaudeToolPrefixFromStreamLine(line []byte, prefix string) []byte {
|
|||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
contentBlock := gjson.GetBytes(payload, "content_block")
|
contentBlock := gjson.GetBytes(payload, "content_block")
|
||||||
if !contentBlock.Exists() || contentBlock.Get("type").String() != "tool_use" {
|
if !contentBlock.Exists() {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockType := contentBlock.Get("type").String()
|
||||||
|
var updated []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if blockType == "tool_use" {
|
||||||
name := contentBlock.Get("name").String()
|
name := contentBlock.Get("name").String()
|
||||||
if !strings.HasPrefix(name, prefix) {
|
if !strings.HasPrefix(name, prefix) {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
updated, err := sjson.SetBytes(payload, "content_block.name", strings.TrimPrefix(name, prefix))
|
updated, err = sjson.SetBytes(payload, "content_block.name", strings.TrimPrefix(name, prefix))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
} else if blockType == "tool_reference" {
|
||||||
|
toolName := contentBlock.Get("tool_name").String()
|
||||||
|
if !strings.HasPrefix(toolName, prefix) {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
updated, err = sjson.SetBytes(payload, "content_block.tool_name", strings.TrimPrefix(toolName, prefix))
|
||||||
|
if err != nil {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
trimmed := bytes.TrimSpace(line)
|
trimmed := bytes.TrimSpace(line)
|
||||||
if bytes.HasPrefix(trimmed, []byte("data:")) {
|
if bytes.HasPrefix(trimmed, []byte("data:")) {
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ func TestApplyClaudeToolPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyClaudeToolPrefix_WithToolReference(t *testing.T) {
|
||||||
|
input := []byte(`{"tools":[{"name":"alpha"}],"messages":[{"role":"user","content":[{"type":"tool_reference","tool_name":"beta"},{"type":"tool_reference","tool_name":"proxy_gamma"}]}]}`)
|
||||||
|
out := applyClaudeToolPrefix(input, "proxy_")
|
||||||
|
|
||||||
|
if got := gjson.GetBytes(out, "messages.0.content.0.tool_name").String(); got != "proxy_beta" {
|
||||||
|
t.Fatalf("messages.0.content.0.tool_name = %q, want %q", got, "proxy_beta")
|
||||||
|
}
|
||||||
|
if got := gjson.GetBytes(out, "messages.0.content.1.tool_name").String(); got != "proxy_gamma" {
|
||||||
|
t.Fatalf("messages.0.content.1.tool_name = %q, want %q", got, "proxy_gamma")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyClaudeToolPrefix_SkipsBuiltinTools(t *testing.T) {
|
func TestApplyClaudeToolPrefix_SkipsBuiltinTools(t *testing.T) {
|
||||||
input := []byte(`{"tools":[{"type":"web_search_20250305","name":"web_search"},{"name":"my_custom_tool","input_schema":{"type":"object"}}]}`)
|
input := []byte(`{"tools":[{"type":"web_search_20250305","name":"web_search"},{"name":"my_custom_tool","input_schema":{"type":"object"}}]}`)
|
||||||
out := applyClaudeToolPrefix(input, "proxy_")
|
out := applyClaudeToolPrefix(input, "proxy_")
|
||||||
@@ -49,6 +61,18 @@ func TestStripClaudeToolPrefixFromResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStripClaudeToolPrefixFromResponse_WithToolReference(t *testing.T) {
|
||||||
|
input := []byte(`{"content":[{"type":"tool_reference","tool_name":"proxy_alpha"},{"type":"tool_reference","tool_name":"bravo"}]}`)
|
||||||
|
out := stripClaudeToolPrefixFromResponse(input, "proxy_")
|
||||||
|
|
||||||
|
if got := gjson.GetBytes(out, "content.0.tool_name").String(); got != "alpha" {
|
||||||
|
t.Fatalf("content.0.tool_name = %q, want %q", got, "alpha")
|
||||||
|
}
|
||||||
|
if got := gjson.GetBytes(out, "content.1.tool_name").String(); got != "bravo" {
|
||||||
|
t.Fatalf("content.1.tool_name = %q, want %q", got, "bravo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStripClaudeToolPrefixFromStreamLine(t *testing.T) {
|
func TestStripClaudeToolPrefixFromStreamLine(t *testing.T) {
|
||||||
line := []byte(`data: {"type":"content_block_start","content_block":{"type":"tool_use","name":"proxy_alpha","id":"t1"},"index":0}`)
|
line := []byte(`data: {"type":"content_block_start","content_block":{"type":"tool_use","name":"proxy_alpha","id":"t1"},"index":0}`)
|
||||||
out := stripClaudeToolPrefixFromStreamLine(line, "proxy_")
|
out := stripClaudeToolPrefixFromStreamLine(line, "proxy_")
|
||||||
@@ -61,3 +85,16 @@ func TestStripClaudeToolPrefixFromStreamLine(t *testing.T) {
|
|||||||
t.Fatalf("content_block.name = %q, want %q", got, "alpha")
|
t.Fatalf("content_block.name = %q, want %q", got, "alpha")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStripClaudeToolPrefixFromStreamLine_WithToolReference(t *testing.T) {
|
||||||
|
line := []byte(`data: {"type":"content_block_start","content_block":{"type":"tool_reference","tool_name":"proxy_beta"},"index":0}`)
|
||||||
|
out := stripClaudeToolPrefixFromStreamLine(line, "proxy_")
|
||||||
|
|
||||||
|
payload := bytes.TrimSpace(out)
|
||||||
|
if bytes.HasPrefix(payload, []byte("data:")) {
|
||||||
|
payload = bytes.TrimSpace(payload[len("data:"):])
|
||||||
|
}
|
||||||
|
if got := gjson.GetBytes(payload, "content_block.tool_name").String(); got != "beta" {
|
||||||
|
t.Fatalf("content_block.tool_name = %q, want %q", got, "beta")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user