mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-18 12:23:44 +00:00
Keep Claude Code compatibility work low-risk and reviewable
This change stops short of broader Claude Code runtime alignment and instead hardens two safe edges: builtin tool prefix handling and source-informed sentinel coverage for future drift checks. Constraint: Must preserve existing default behavior for current users Rejected: Implement control-plane/session alignment now | too much runtime risk for a first slice Confidence: high Scope-risk: narrow Reversibility: clean Directive: Treat the new fixtures as compatibility sentinels, not a full Claude Code schema contract Tested: go test ./test/...; go test ./sdk/translator/...; go test ./internal/runtime/executor -run 'Claude|Builtin|Tool'; go test ./... Not-tested: End-to-end Claude Code direct-connect/session runtime behavior
This commit is contained in:
38
internal/runtime/executor/claude_builtin_tools.go
Normal file
38
internal/runtime/executor/claude_builtin_tools.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package executor
|
||||
|
||||
import "github.com/tidwall/gjson"
|
||||
|
||||
var defaultClaudeBuiltinToolNames = []string{
|
||||
"web_search",
|
||||
"code_execution",
|
||||
"text_editor",
|
||||
"computer",
|
||||
}
|
||||
|
||||
func newClaudeBuiltinToolRegistry() map[string]bool {
|
||||
registry := make(map[string]bool, len(defaultClaudeBuiltinToolNames))
|
||||
for _, name := range defaultClaudeBuiltinToolNames {
|
||||
registry[name] = true
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
||||
func augmentClaudeBuiltinToolRegistry(body []byte, registry map[string]bool) map[string]bool {
|
||||
if registry == nil {
|
||||
registry = newClaudeBuiltinToolRegistry()
|
||||
}
|
||||
tools := gjson.GetBytes(body, "tools")
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return registry
|
||||
}
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if tool.Get("type").String() == "" {
|
||||
return true
|
||||
}
|
||||
if name := tool.Get("name").String(); name != "" {
|
||||
registry[name] = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
return registry
|
||||
}
|
||||
46
internal/runtime/executor/claude_builtin_tools_test.go
Normal file
46
internal/runtime/executor/claude_builtin_tools_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestClaudeBuiltinToolRegistry_DefaultSeedFallback(t *testing.T) {
|
||||
registry := augmentClaudeBuiltinToolRegistry(nil, nil)
|
||||
for _, name := range defaultClaudeBuiltinToolNames {
|
||||
if !registry[name] {
|
||||
t.Fatalf("default builtin %q missing from fallback registry", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyClaudeToolPrefix_KnownFallbackBuiltinsRemainUnprefixed(t *testing.T) {
|
||||
for _, builtin := range defaultClaudeBuiltinToolNames {
|
||||
t.Run(builtin, func(t *testing.T) {
|
||||
input := []byte(fmt.Sprintf(`{
|
||||
"tools":[{"name":"Read"}],
|
||||
"tool_choice":{"type":"tool","name":%q},
|
||||
"messages":[{"role":"assistant","content":[{"type":"tool_use","name":%q,"id":"toolu_1","input":{}},{"type":"tool_reference","tool_name":%q},{"type":"tool_result","tool_use_id":"toolu_1","content":[{"type":"tool_reference","tool_name":%q}]}]}]
|
||||
}`, builtin, builtin, builtin, builtin))
|
||||
out := applyClaudeToolPrefix(input, "proxy_")
|
||||
|
||||
if got := gjson.GetBytes(out, "tool_choice.name").String(); got != builtin {
|
||||
t.Fatalf("tool_choice.name = %q, want %q", got, builtin)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "messages.0.content.0.name").String(); got != builtin {
|
||||
t.Fatalf("messages.0.content.0.name = %q, want %q", got, builtin)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "messages.0.content.1.tool_name").String(); got != builtin {
|
||||
t.Fatalf("messages.0.content.1.tool_name = %q, want %q", got, builtin)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "messages.0.content.2.content.0.tool_name").String(); got != builtin {
|
||||
t.Fatalf("messages.0.content.2.content.0.tool_name = %q, want %q", got, builtin)
|
||||
}
|
||||
if got := gjson.GetBytes(out, "tools.0.name").String(); got != "proxy_Read" {
|
||||
t.Fatalf("tools.0.name = %q, want %q", got, "proxy_Read")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -919,12 +919,9 @@ func applyClaudeToolPrefix(body []byte, prefix string) []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
// Collect built-in tool names (those with a non-empty "type" field) so we can
|
||||
// skip them consistently in both tools and message history.
|
||||
builtinTools := map[string]bool{}
|
||||
for _, name := range []string{"web_search", "code_execution", "text_editor", "computer"} {
|
||||
builtinTools[name] = true
|
||||
}
|
||||
// Collect built-in tool names from the authoritative fallback seed list and
|
||||
// augment it with any typed built-ins present in the current request body.
|
||||
builtinTools := augmentClaudeBuiltinToolRegistry(body, nil)
|
||||
|
||||
if tools := gjson.GetBytes(body, "tools"); tools.Exists() && tools.IsArray() {
|
||||
tools.ForEach(func(index, tool gjson.Result) bool {
|
||||
|
||||
Reference in New Issue
Block a user