mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-21 05:40:51 +08:00
The proxy_ prefix logic correctly skips built-in tools (those with a non-empty "type" field) in tools[] definitions but does not skip them in messages[].content[] tool_use blocks or tool_choice. This causes web_search in conversation history to become proxy_web_search, which Anthropic does not recognize. Fix: collect built-in tool names from tools[] into a set and also maintain a hardcoded fallback set (web_search, code_execution, text_editor, computer) for cases where the built-in tool appears in history but not in the current request's tools[] array. Skip prefixing in messages and tool_choice when name matches a built-in.
143 lines
5.1 KiB
Go
143 lines
5.1 KiB
Go
package executor
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestApplyClaudeToolPrefix(t *testing.T) {
|
|
input := []byte(`{"tools":[{"name":"alpha"},{"name":"proxy_bravo"}],"tool_choice":{"type":"tool","name":"charlie"},"messages":[{"role":"assistant","content":[{"type":"tool_use","name":"delta","id":"t1","input":{}}]}]}`)
|
|
out := applyClaudeToolPrefix(input, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "tools.0.name").String(); got != "proxy_alpha" {
|
|
t.Fatalf("tools.0.name = %q, want %q", got, "proxy_alpha")
|
|
}
|
|
if got := gjson.GetBytes(out, "tools.1.name").String(); got != "proxy_bravo" {
|
|
t.Fatalf("tools.1.name = %q, want %q", got, "proxy_bravo")
|
|
}
|
|
if got := gjson.GetBytes(out, "tool_choice.name").String(); got != "proxy_charlie" {
|
|
t.Fatalf("tool_choice.name = %q, want %q", got, "proxy_charlie")
|
|
}
|
|
if got := gjson.GetBytes(out, "messages.0.content.0.name").String(); got != "proxy_delta" {
|
|
t.Fatalf("messages.0.content.0.name = %q, want %q", got, "proxy_delta")
|
|
}
|
|
}
|
|
|
|
func TestApplyClaudeToolPrefix_BuiltinToolSkipped(t *testing.T) {
|
|
body := []byte(`{
|
|
"tools": [
|
|
{"type": "web_search_20250305", "name": "web_search", "max_uses": 5},
|
|
{"name": "Read"}
|
|
],
|
|
"messages": [
|
|
{"role": "user", "content": [
|
|
{"type": "tool_use", "name": "web_search", "id": "ws1", "input": {}},
|
|
{"type": "tool_use", "name": "Read", "id": "r1", "input": {}}
|
|
]}
|
|
]
|
|
}`)
|
|
out := applyClaudeToolPrefix(body, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "tools.0.name").String(); got != "web_search" {
|
|
t.Fatalf("tools.0.name = %q, want %q", got, "web_search")
|
|
}
|
|
if got := gjson.GetBytes(out, "messages.0.content.0.name").String(); got != "web_search" {
|
|
t.Fatalf("messages.0.content.0.name = %q, want %q", got, "web_search")
|
|
}
|
|
if got := gjson.GetBytes(out, "tools.1.name").String(); got != "proxy_Read" {
|
|
t.Fatalf("tools.1.name = %q, want %q", got, "proxy_Read")
|
|
}
|
|
if got := gjson.GetBytes(out, "messages.0.content.1.name").String(); got != "proxy_Read" {
|
|
t.Fatalf("messages.0.content.1.name = %q, want %q", got, "proxy_Read")
|
|
}
|
|
}
|
|
|
|
func TestApplyClaudeToolPrefix_KnownBuiltinInHistoryOnly(t *testing.T) {
|
|
body := []byte(`{
|
|
"tools": [
|
|
{"name": "Read"}
|
|
],
|
|
"messages": [
|
|
{"role": "user", "content": [
|
|
{"type": "tool_use", "name": "web_search", "id": "ws1", "input": {}}
|
|
]}
|
|
]
|
|
}`)
|
|
out := applyClaudeToolPrefix(body, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "messages.0.content.0.name").String(); got != "web_search" {
|
|
t.Fatalf("messages.0.content.0.name = %q, want %q", got, "web_search")
|
|
}
|
|
if got := gjson.GetBytes(out, "tools.0.name").String(); got != "proxy_Read" {
|
|
t.Fatalf("tools.0.name = %q, want %q", got, "proxy_Read")
|
|
}
|
|
}
|
|
|
|
func TestApplyClaudeToolPrefix_CustomToolsPrefixed(t *testing.T) {
|
|
body := []byte(`{
|
|
"tools": [{"name": "Read"}, {"name": "Write"}],
|
|
"messages": [
|
|
{"role": "user", "content": [
|
|
{"type": "tool_use", "name": "Read", "id": "r1", "input": {}},
|
|
{"type": "tool_use", "name": "Write", "id": "w1", "input": {}}
|
|
]}
|
|
]
|
|
}`)
|
|
out := applyClaudeToolPrefix(body, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "tools.0.name").String(); got != "proxy_Read" {
|
|
t.Fatalf("tools.0.name = %q, want %q", got, "proxy_Read")
|
|
}
|
|
if got := gjson.GetBytes(out, "tools.1.name").String(); got != "proxy_Write" {
|
|
t.Fatalf("tools.1.name = %q, want %q", got, "proxy_Write")
|
|
}
|
|
if got := gjson.GetBytes(out, "messages.0.content.0.name").String(); got != "proxy_Read" {
|
|
t.Fatalf("messages.0.content.0.name = %q, want %q", got, "proxy_Read")
|
|
}
|
|
if got := gjson.GetBytes(out, "messages.0.content.1.name").String(); got != "proxy_Write" {
|
|
t.Fatalf("messages.0.content.1.name = %q, want %q", got, "proxy_Write")
|
|
}
|
|
}
|
|
|
|
func TestApplyClaudeToolPrefix_ToolChoiceBuiltin(t *testing.T) {
|
|
body := []byte(`{
|
|
"tools": [
|
|
{"type": "web_search_20250305", "name": "web_search"},
|
|
{"name": "Read"}
|
|
],
|
|
"tool_choice": {"type": "tool", "name": "web_search"}
|
|
}`)
|
|
out := applyClaudeToolPrefix(body, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "tool_choice.name").String(); got != "web_search" {
|
|
t.Fatalf("tool_choice.name = %q, want %q", got, "web_search")
|
|
}
|
|
}
|
|
|
|
func TestStripClaudeToolPrefixFromResponse(t *testing.T) {
|
|
input := []byte(`{"content":[{"type":"tool_use","name":"proxy_alpha","id":"t1","input":{}},{"type":"tool_use","name":"bravo","id":"t2","input":{}}]}`)
|
|
out := stripClaudeToolPrefixFromResponse(input, "proxy_")
|
|
|
|
if got := gjson.GetBytes(out, "content.0.name").String(); got != "alpha" {
|
|
t.Fatalf("content.0.name = %q, want %q", got, "alpha")
|
|
}
|
|
if got := gjson.GetBytes(out, "content.1.name").String(); got != "bravo" {
|
|
t.Fatalf("content.1.name = %q, want %q", got, "bravo")
|
|
}
|
|
}
|
|
|
|
func TestStripClaudeToolPrefixFromStreamLine(t *testing.T) {
|
|
line := []byte(`data: {"type":"content_block_start","content_block":{"type":"tool_use","name":"proxy_alpha","id":"t1"},"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.name").String(); got != "alpha" {
|
|
t.Fatalf("content_block.name = %q, want %q", got, "alpha")
|
|
}
|
|
}
|