diff --git a/plugins/wasm-go/extensions/ai-proxy/README.md b/plugins/wasm-go/extensions/ai-proxy/README.md index 8cda5d67d..4a82dd824 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README.md +++ b/plugins/wasm-go/extensions/ai-proxy/README.md @@ -233,7 +233,6 @@ Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如 - 设置 Claude Code 特定的请求头(user-agent、x-app、anthropic-beta) - 为请求 URL 添加 `?beta=true` 查询参数 - 自动注入 Claude Code 的系统提示词(如未提供) -- 自动注入 Bash 工具定义(如未提供) 这允许在 Higress 中直接使用 Claude Code 的 OAuth Token 进行身份验证。 @@ -1240,7 +1239,7 @@ provider: 启用此模式后,插件将自动: - 使用 Bearer Token 认证(而非 x-api-key) - 设置 Claude Code 特定的请求头和查询参数 -- 注入 Claude Code 的系统提示词和 Bash 工具(如未提供) +- 注入 Claude Code 的系统提示词(如未提供) **请求示例** @@ -1259,7 +1258,6 @@ provider: 插件将自动转换为适合 Claude Code 的请求格式,包括: - 添加系统提示词:`"You are Claude Code, Anthropic's official CLI for Claude."` -- 添加 Bash 工具定义(用于执行命令) - 设置适当的认证和请求头 ### 使用智能协议转换 diff --git a/plugins/wasm-go/extensions/ai-proxy/README_EN.md b/plugins/wasm-go/extensions/ai-proxy/README_EN.md index 6c505390f..293800254 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README_EN.md +++ b/plugins/wasm-go/extensions/ai-proxy/README_EN.md @@ -199,7 +199,6 @@ When `claudeCodeMode: true` is enabled, the plugin will: - Set Claude Code-specific request headers (user-agent, x-app, anthropic-beta) - Add `?beta=true` query parameter to request URLs - Automatically inject Claude Code system prompt if not provided -- Automatically inject Bash tool definition if not provided This enables direct use of Claude Code OAuth tokens for authentication in Higress. @@ -1177,7 +1176,7 @@ provider: Once this mode is enabled, the plugin will automatically: - Use Bearer Token authentication (instead of x-api-key) - Set Claude Code-specific request headers and query parameters -- Inject Claude Code system prompt and Bash tool definitions if not provided +- Inject Claude Code system prompt if not provided **Request Example** @@ -1196,7 +1195,6 @@ Once this mode is enabled, the plugin will automatically: The plugin will automatically transform the request into Claude Code format, including: - Adding system prompt: `"You are Claude Code, Anthropic's official CLI for Claude."` -- Adding Bash tool definition (for command execution) - Setting appropriate authentication and request headers ### Using Intelligent Protocol Conversion diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/claude.go b/plugins/wasm-go/extensions/ai-proxy/provider/claude.go index 5c9e40fdc..4a372bb67 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/claude.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/claude.go @@ -21,11 +21,9 @@ const ( claudeDefaultMaxTokens = 4096 // Claude Code mode constants - claudeCodeUserAgent = "claude-cli/2.1.2 (external, cli)" - claudeCodeBetaFeatures = "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219" - claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude." - claudeCodeBashToolName = "Bash" - claudeCodeBashToolDesc = "Run bash commands" + claudeCodeUserAgent = "claude-cli/2.1.2 (external, cli)" + claudeCodeBetaFeatures = "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219" + claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude." ) type claudeProviderInitializer struct{} @@ -552,32 +550,6 @@ func (c *claudeProvider) buildClaudeTextGenRequest(origRequest *chatCompletionRe claudeRequest.Tools = append(claudeRequest.Tools, claudeTool) } - // In Claude Code mode, add Bash tool if not present - if c.config.claudeCodeMode { - hasBashTool := false - for _, tool := range claudeRequest.Tools { - if tool.Name == claudeCodeBashToolName { - hasBashTool = true - break - } - } - if !hasBashTool { - claudeRequest.Tools = append(claudeRequest.Tools, claudeTool{ - Name: claudeCodeBashToolName, - Description: claudeCodeBashToolDesc, - InputSchema: map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "command": map[string]interface{}{ - "type": "string", - }, - }, - "required": []string{"command"}, - }, - }) - } - } - if tc := origRequest.getToolChoiceObject(); tc != nil { claudeRequest.ToolChoice = &claudeToolChoice{ Name: tc.Function.Name, diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/claude_test.go b/plugins/wasm-go/extensions/ai-proxy/provider/claude_test.go index fdc02f297..fb93ed20b 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/claude_test.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/claude_test.go @@ -237,104 +237,6 @@ func TestClaudeProvider_BuildClaudeTextGenRequest_ClaudeCodeMode(t *testing.T) { assert.Equal(t, "ephemeral", claudeReq.System.ArrayValue[0].CacheControl["type"]) }) - t.Run("injects_bash_tool_when_missing", func(t *testing.T) { - request := &chatCompletionRequest{ - Model: "claude-sonnet-4-5-20250929", - MaxTokens: 8192, - Messages: []chatMessage{ - {Role: roleUser, Content: "List files"}, - }, - } - - claudeReq := provider.buildClaudeTextGenRequest(request) - - // Should have Bash tool injected - require.Len(t, claudeReq.Tools, 1) - assert.Equal(t, claudeCodeBashToolName, claudeReq.Tools[0].Name) - assert.Equal(t, claudeCodeBashToolDesc, claudeReq.Tools[0].Description) - // Verify input schema - assert.NotNil(t, claudeReq.Tools[0].InputSchema) - assert.Equal(t, "object", claudeReq.Tools[0].InputSchema["type"]) - }) - - t.Run("does_not_duplicate_bash_tool", func(t *testing.T) { - request := &chatCompletionRequest{ - Model: "claude-sonnet-4-5-20250929", - MaxTokens: 8192, - Messages: []chatMessage{ - {Role: roleUser, Content: "List files"}, - }, - Tools: []tool{ - { - Type: "function", - Function: function{ - Name: "Bash", - Description: "Custom bash tool", - Parameters: map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "command": map[string]interface{}{"type": "string"}, - }, - }, - }, - }, - }, - } - - claudeReq := provider.buildClaudeTextGenRequest(request) - - // Should not duplicate Bash tool - assert.Len(t, claudeReq.Tools, 1) - assert.Equal(t, "Bash", claudeReq.Tools[0].Name) - // Should preserve the original description - assert.Equal(t, "Custom bash tool", claudeReq.Tools[0].Description) - }) - - t.Run("adds_bash_tool_alongside_existing_tools", func(t *testing.T) { - request := &chatCompletionRequest{ - Model: "claude-sonnet-4-5-20250929", - MaxTokens: 8192, - Messages: []chatMessage{ - {Role: roleUser, Content: "Hello"}, - }, - Tools: []tool{ - { - Type: "function", - Function: function{ - Name: "Read", - Description: "Read files", - Parameters: map[string]interface{}{ - "type": "object", - }, - }, - }, - { - Type: "function", - Function: function{ - Name: "Write", - Description: "Write files", - Parameters: map[string]interface{}{ - "type": "object", - }, - }, - }, - }, - } - - claudeReq := provider.buildClaudeTextGenRequest(request) - - // Should have original tools plus Bash tool - assert.Len(t, claudeReq.Tools, 3) - - toolNames := make([]string, len(claudeReq.Tools)) - for i, tool := range claudeReq.Tools { - toolNames[i] = tool.Name - } - assert.Contains(t, toolNames, "Read") - assert.Contains(t, toolNames, "Write") - assert.Contains(t, toolNames, "Bash") - }) - t.Run("full_request_transformation", func(t *testing.T) { request := &chatCompletionRequest{ Model: "claude-sonnet-4-5-20250929", @@ -363,9 +265,8 @@ func TestClaudeProvider_BuildClaudeTextGenRequest_ClaudeCodeMode(t *testing.T) { require.Len(t, claudeReq.Messages, 1) assert.Equal(t, roleUser, claudeReq.Messages[0].Role) - // Verify Bash tool - require.Len(t, claudeReq.Tools, 1) - assert.Equal(t, "Bash", claudeReq.Tools[0].Name) + // Verify no tools are injected by default + assert.Empty(t, claudeReq.Tools) // Verify the request can be serialized to JSON jsonBytes, err := json.Marshal(claudeReq) @@ -388,8 +289,6 @@ func TestClaudeConstants(t *testing.T) { assert.Equal(t, "claude-cli/2.1.2 (external, cli)", claudeCodeUserAgent) assert.Equal(t, "oauth-2025-04-20,interleaved-thinking-2025-05-14,claude-code-20250219", claudeCodeBetaFeatures) assert.Equal(t, "You are Claude Code, Anthropic's official CLI for Claude.", claudeCodeSystemPrompt) - assert.Equal(t, "Bash", claudeCodeBashToolName) - assert.Equal(t, "Run bash commands", claudeCodeBashToolDesc) } func TestClaudeProvider_GetApiName(t *testing.T) { diff --git a/plugins/wasm-go/extensions/ai-proxy/test/claude.go b/plugins/wasm-go/extensions/ai-proxy/test/claude.go index 582cbf3fc..3c4347726 100644 --- a/plugins/wasm-go/extensions/ai-proxy/test/claude.go +++ b/plugins/wasm-go/extensions/ai-proxy/test/claude.go @@ -270,47 +270,6 @@ func RunClaudeOnHttpRequestBodyTests(t *testing.T) { require.Equal(t, "ephemeral", cacheControlMap["type"]) }) - t.Run("claude code mode injects bash tool", func(t *testing.T) { - host, status := test.NewTestHost(claudeCodeModeConfig) - defer host.Reset() - require.Equal(t, types.OnPluginStartStatusOK, status) - - host.CallOnHttpRequestHeaders([][2]string{ - {":authority", "api.anthropic.com"}, - {":path", "/v1/chat/completions"}, - {":method", "POST"}, - {"Content-Type", "application/json"}, - }) - - body := `{ - "model": "claude-sonnet-4-5-20250929", - "max_tokens": 8192, - "messages": [ - {"role": "user", "content": "List files"} - ] - }` - action := host.CallOnHttpRequestBody([]byte(body)) - require.Equal(t, types.ActionContinue, action) - - processedBody := host.GetRequestBody() - var request map[string]interface{} - err := json.Unmarshal(processedBody, &request) - require.NoError(t, err) - - // Claude Code mode should inject Bash tool - tools, hasTools := request["tools"] - require.True(t, hasTools, "claude code mode should inject tools") - - toolsArr, ok := tools.([]interface{}) - require.True(t, ok) - require.Len(t, toolsArr, 1) - - bashTool, ok := toolsArr[0].(map[string]interface{}) - require.True(t, ok) - require.Equal(t, "Bash", bashTool["name"]) - require.Equal(t, "Run bash commands", bashTool["description"]) - }) - t.Run("claude code mode preserves existing system prompt", func(t *testing.T) { host, status := test.NewTestHost(claudeCodeModeConfig) defer host.Reset() @@ -351,111 +310,6 @@ func RunClaudeOnHttpRequestBodyTests(t *testing.T) { require.True(t, ok) require.Equal(t, "You are a custom assistant.", systemBlock["text"]) }) - - t.Run("claude code mode does not duplicate bash tool", func(t *testing.T) { - host, status := test.NewTestHost(claudeCodeModeConfig) - defer host.Reset() - require.Equal(t, types.OnPluginStartStatusOK, status) - - host.CallOnHttpRequestHeaders([][2]string{ - {":authority", "api.anthropic.com"}, - {":path", "/v1/chat/completions"}, - {":method", "POST"}, - {"Content-Type", "application/json"}, - }) - - body := `{ - "model": "claude-sonnet-4-5-20250929", - "max_tokens": 8192, - "messages": [ - {"role": "user", "content": "Hello"} - ], - "tools": [ - { - "type": "function", - "function": { - "name": "Bash", - "description": "Custom bash tool", - "parameters": {"type": "object"} - } - } - ] - }` - action := host.CallOnHttpRequestBody([]byte(body)) - require.Equal(t, types.ActionContinue, action) - - processedBody := host.GetRequestBody() - var request map[string]interface{} - err := json.Unmarshal(processedBody, &request) - require.NoError(t, err) - - // Should not duplicate Bash tool - tools, hasTools := request["tools"] - require.True(t, hasTools) - - toolsArr, ok := tools.([]interface{}) - require.True(t, ok) - require.Len(t, toolsArr, 1, "should not duplicate Bash tool") - }) - - t.Run("claude code mode adds bash tool alongside existing tools", func(t *testing.T) { - host, status := test.NewTestHost(claudeCodeModeConfig) - defer host.Reset() - require.Equal(t, types.OnPluginStartStatusOK, status) - - host.CallOnHttpRequestHeaders([][2]string{ - {":authority", "api.anthropic.com"}, - {":path", "/v1/chat/completions"}, - {":method", "POST"}, - {"Content-Type", "application/json"}, - }) - - body := `{ - "model": "claude-sonnet-4-5-20250929", - "max_tokens": 8192, - "messages": [ - {"role": "user", "content": "Hello"} - ], - "tools": [ - { - "type": "function", - "function": { - "name": "Read", - "description": "Read files", - "parameters": {"type": "object"} - } - } - ] - }` - action := host.CallOnHttpRequestBody([]byte(body)) - require.Equal(t, types.ActionContinue, action) - - processedBody := host.GetRequestBody() - var request map[string]interface{} - err := json.Unmarshal(processedBody, &request) - require.NoError(t, err) - - // Should have both Read and Bash tools - tools, hasTools := request["tools"] - require.True(t, hasTools) - - toolsArr, ok := tools.([]interface{}) - require.True(t, ok) - require.Len(t, toolsArr, 2, "should have Read tool plus injected Bash tool") - - // Verify both tools exist - toolNames := make([]string, 0) - for _, tool := range toolsArr { - toolMap, ok := tool.(map[string]interface{}) - if ok { - if name, hasName := toolMap["name"]; hasName { - toolNames = append(toolNames, name.(string)) - } - } - } - require.Contains(t, toolNames, "Read") - require.Contains(t, toolNames, "Bash") - }) }) }