Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions dto/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ type ClaudeRequest struct {
ServiceTier string `json:"service_tier,omitempty"`
}

// OutputConfigForEffort just for extract effort
type OutputConfigForEffort struct {
Effort string `json:"effort,omitempty"`
}

// createClaudeFileSource 根据数据内容创建正确类型的 FileSource
func createClaudeFileSource(data string) *types.FileSource {
if strings.HasPrefix(data, "http://") || strings.HasPrefix(data, "https://") {
Expand Down Expand Up @@ -409,6 +414,15 @@ func (c *ClaudeRequest) GetTools() []any {
}
}

func (c *ClaudeRequest) GetEfforts() string {
var OutputConfig OutputConfigForEffort
if err := json.Unmarshal(c.OutputConfig, &OutputConfig); err == nil {
effort := OutputConfig.Effort
return effort
}
return ""
Comment on lines +417 to +423

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the target file to see the actual code at lines 417-423
cd /dev/shm && find . -type f -name "claude.go" -path "*/dto/*" 2>/dev/null | head -5

Repository: QuantumNous/new-api

Length of output: 121


🏁 Script executed:

# List files in dto directory
ls -la dto/ 2>/dev/null | head -20

Repository: QuantumNous/new-api

Length of output: 1351


🏁 Script executed:

# Check what files exist in dto
git ls-files dto/ | head -20

Repository: QuantumNous/new-api

Length of output: 484


🏁 Script executed:

# Search for GetEfforts in the codebase
rg -n "GetEfforts" --type go

Repository: QuantumNous/new-api

Length of output: 207


🏁 Script executed:

# Read the GetEfforts method from dto/claude.go with context
sed -n '410,430p' dto/claude.go

Repository: QuantumNous/new-api

Length of output: 507


🏁 Script executed:

# Check imports at the top of dto/claude.go
head -50 dto/claude.go

Repository: QuantumNous/new-api

Length of output: 1534


🏁 Script executed:

# Check what JSON wrapper functions are available in common/json.go
cat common/json.go

Repository: QuantumNous/new-api

Length of output: 817


Use common.Unmarshal in GetEfforts instead of json.Unmarshal.

Line 419 directly calls json.Unmarshal in business code. This violates the project guideline requiring all JSON marshal/unmarshal operations to use wrapper functions from common/json.go.

Proposed fix
 func (c *ClaudeRequest) GetEfforts() string {
-	var OutputConfig OutputConfigForEffort
-	if err := json.Unmarshal(c.OutputConfig, &OutputConfig); err == nil {
-		effort := OutputConfig.Effort
-		return effort
-	}
-	return ""
+	if len(c.OutputConfig) == 0 {
+		return ""
+	}
+	var outputConfig OutputConfigForEffort
+	if err := common.Unmarshal(c.OutputConfig, &outputConfig); err != nil {
+		return ""
+	}
+	return outputConfig.Effort
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *ClaudeRequest) GetEfforts() string {
var OutputConfig OutputConfigForEffort
if err := json.Unmarshal(c.OutputConfig, &OutputConfig); err == nil {
effort := OutputConfig.Effort
return effort
}
return ""
func (c *ClaudeRequest) GetEfforts() string {
if len(c.OutputConfig) == 0 {
return ""
}
var outputConfig OutputConfigForEffort
if err := common.Unmarshal(c.OutputConfig, &outputConfig); err != nil {
return ""
}
return outputConfig.Effort
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dto/claude.go` around lines 417 - 423, Replace the direct call to
json.Unmarshal in GetEfforts with the project's wrapper common.Unmarshal: call
common.Unmarshal(c.OutputConfig, &OutputConfig) inside ClaudeRequest.GetEfforts
(which uses the OutputConfigForEffort struct) and preserve the current behavior
of returning OutputConfig.Effort on success and "" on error; ensure the function
handles the returned error from common.Unmarshal the same way the current code
did (i.e., only return effort when err == nil).

}

// ProcessTools 处理工具列表,支持类型断言
func ProcessTools(tools []any) ([]*Tool, []*ClaudeWebSearchTool) {
var normalTools []*Tool
Expand Down
1 change: 1 addition & 0 deletions relay/channel/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
}

reasoning := openrouter.RequestReasoning{
Enabled: true,
MaxTokens: *thinking.BudgetTokens,
}

Expand Down
1 change: 1 addition & 0 deletions relay/channel/openrouter/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package openrouter
import "encoding/json"

type RequestReasoning struct {
Enabled bool `json:"enabled"`
// One of the following (not both):
Effort string `json:"effort,omitempty"` // Can be "high", "medium", or "low" (OpenAI-style)
MaxTokens int `json:"max_tokens,omitempty"` // Specific token limit (Anthropic-style)
Expand Down
28 changes: 21 additions & 7 deletions service/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,30 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re

isOpenRouter := info.ChannelType == constant.ChannelTypeOpenRouter

if claudeRequest.Thinking != nil && claudeRequest.Thinking.Type == "enabled" {
if claudeRequest.Thinking != nil && (claudeRequest.Thinking.Type == "enabled" || claudeRequest.Thinking.Type == "adaptive") {
if isOpenRouter {
reasoning := openrouter.RequestReasoning{
MaxTokens: claudeRequest.Thinking.GetBudgetTokens(),
if effort := claudeRequest.GetEfforts(); effort != "" {
effortBytes, _ := json.Marshal(effort)
openAIRequest.Verbosity = effortBytes
}
reasoningJSON, err := json.Marshal(reasoning)
if err != nil {
return nil, fmt.Errorf("failed to marshal reasoning: %w", err)
if claudeRequest.Thinking != nil {
var reasoning openrouter.RequestReasoning
if claudeRequest.Thinking.Type == "enabled" {
reasoning = openrouter.RequestReasoning{
Enabled: true,
MaxTokens: claudeRequest.Thinking.GetBudgetTokens(),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the service/convert.go file around lines 45-49
fd convert.go | grep -E "service|convert"

Repository: QuantumNous/new-api

Length of output: 81


🏁 Script executed:

# Get the actual code at lines 45-49 in service/convert.go
find . -path "*service/convert.go" -type f | head -1 | xargs head -60 | tail -20

Repository: QuantumNous/new-api

Length of output: 709


🏁 Script executed:

# Search for GetBudgetTokens method implementation
rg "GetBudgetTokens" -A 5 -B 2

Repository: QuantumNous/new-api

Length of output: 710


🏁 Script executed:

# Look at relay/channel/openai/adaptor.go around lines 296-298
find . -path "*relay/channel/openai/adaptor.go" -type f | xargs sed -n '290,305p'

Repository: QuantumNous/new-api

Length of output: 521


🏁 Script executed:

# Search for the Thinking struct definition to understand BudgetTokens type
rg "type.*Thinking\s*struct" -A 20 | head -50

Repository: QuantumNous/new-api

Length of output: 2180


Add nil validation for BudgetTokens when thinking.type == "enabled".

At Lines [45-49], GetBudgetTokens() silently returns 0 when BudgetTokens is nil, allowing enabled reasoning without an explicit token budget. This diverges from the stricter validation in relay/channel/openai/adaptor.go (Lines [296-298]), which explicitly rejects nil BudgetTokens when thinking is enabled. For consistency and correctness, validate BudgetTokens != nil before building reasoning.

Proposed fix
 				if claudeRequest.Thinking.Type == "enabled" {
+					if claudeRequest.Thinking.BudgetTokens == nil {
+						return nil, fmt.Errorf("BudgetTokens is nil when thinking is enabled")
+					}
 					reasoning = openrouter.RequestReasoning{
 						Enabled:   true,
 						MaxTokens: claudeRequest.Thinking.GetBudgetTokens(),
 					}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if claudeRequest.Thinking.Type == "enabled" {
reasoning = openrouter.RequestReasoning{
Enabled: true,
MaxTokens: claudeRequest.Thinking.GetBudgetTokens(),
}
if claudeRequest.Thinking.Type == "enabled" {
if claudeRequest.Thinking.BudgetTokens == nil {
return nil, fmt.Errorf("BudgetTokens is nil when thinking is enabled")
}
reasoning = openrouter.RequestReasoning{
Enabled: true,
MaxTokens: claudeRequest.Thinking.GetBudgetTokens(),
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/convert.go` around lines 45 - 49, When building
openrouter.RequestReasoning in the block where claudeRequest.Thinking.Type ==
"enabled", add an explicit nil-check for claudeRequest.Thinking.BudgetTokens
(don’t rely on GetBudgetTokens() returning 0); if BudgetTokens is nil return an
error or reject the request consistent with relay/channel/openai/adaptor.go
behavior, otherwise use the dereferenced value for MaxTokens and set
Enabled=true on openrouter.RequestReasoning; update the clause that currently
uses claudeRequest.Thinking.GetBudgetTokens() to first validate BudgetTokens !=
nil and only then construct the reasoning object.

} else if claudeRequest.Thinking.Type == "adaptive" {
reasoning = openrouter.RequestReasoning{
Enabled: true,
}
}
reasoningJSON, err := json.Marshal(reasoning)
if err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant section
if [ -f "service/convert.go" ]; then
  echo "=== Lines 35-60 of service/convert.go ==="
  sed -n '35,60p' service/convert.go
else
  echo "File not found: service/convert.go"
fi

Repository: QuantumNous/new-api

Length of output: 1069


🏁 Script executed:

# Verify common.Marshal wrapper function exists
echo "=== Checking for common.Marshal in common/json.go ==="
if [ -f "common/json.go" ]; then
  rg -A 5 "func (.*)?Marshal\(" common/json.go | head -40
else
  echo "File common/json.go not found"
fi

Repository: QuantumNous/new-api

Length of output: 263


🏁 Script executed:

# Check all json.Marshal and json.Unmarshal usage in service/convert.go
echo "=== All json.Marshal and json.Unmarshal calls in service/convert.go ==="
rg "(json\.Marshal|json\.Unmarshal)" service/convert.go -n

Repository: QuantumNous/new-api

Length of output: 448


🏁 Script executed:

# Check for json imports in service/convert.go
echo "=== Imports in service/convert.go ==="
head -30 service/convert.go | grep -E "^import|^\\s+\".*\""

Repository: QuantumNous/new-api

Length of output: 399


Replace direct json.Marshal calls with common.Marshal wrapper function.

Lines 40 and 55 use json.Marshal directly in business code instead of the common.Marshal wrapper function from common/json.go. Additionally, line 40 ignores the error return value with _, which should be properly handled.

Proposed fix
 			if effort := claudeRequest.GetEfforts(); effort != "" {
-				effortBytes, _ := json.Marshal(effort)
+				effortBytes, err := common.Marshal(effort)
+				if err != nil {
+					return nil, fmt.Errorf("failed to marshal effort: %w", err)
+				}
 				openAIRequest.Verbosity = effortBytes
 			}
-				reasoningJSON, err := json.Marshal(reasoning)
+				reasoningJSON, err := common.Marshal(reasoning)
 				if err != nil {
 					return nil, fmt.Errorf("failed to marshal reasoning: %w", err)
 				}

Per the coding guideline for **/*.go: "All JSON marshal/unmarshal operations MUST use wrapper functions in common/json.go (common.Marshal, common.Unmarshal, common.UnmarshalJsonStr, common.DecodeJson, common.GetJsonType). Do NOT directly call encoding/json in business code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/convert.go` around lines 39 - 56, Replace direct calls to
json.Marshal in the convert logic with the common.Marshal wrapper and handle
errors instead of ignoring them: when setting openAIRequest.Verbosity from
claudeRequest.GetEfforts() use common.Marshal(effort) and check/return/log the
error rather than using "_" to discard it; likewise replace
json.Marshal(reasoning) (the reasoningJSON and err usage in the Thinking
handling block) with common.Marshal(reasoning) and handle the error branch
consistently (propagate or log and return). Update references around
GetEfforts(), openAIRequest.Verbosity assignment, and the reasoningJSON variable
in the Thinking handling code to use common.Marshal and proper error handling.

return nil, fmt.Errorf("failed to marshal reasoning: %w", err)
}
openAIRequest.Reasoning = reasoningJSON
}
openAIRequest.Reasoning = reasoningJSON
} else {
thinkingSuffix := "-thinking"
if strings.HasSuffix(info.OriginModelName, thinkingSuffix) &&
Expand Down