mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-14 07:57:26 +02:00
Add files via upload
This commit is contained in:
@@ -35,11 +35,11 @@ import (
|
||||
|
||||
// claudeRequest 表示 Anthropic Messages API 的请求体。
|
||||
type claudeRequest struct {
|
||||
Model string `json:"model"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
System string `json:"system,omitempty"`
|
||||
Messages []claudeMessage `json:"messages"`
|
||||
Tools []claudeTool `json:"tools,omitempty"`
|
||||
Model string `json:"model"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
System string `json:"system,omitempty"`
|
||||
Messages []claudeMessage `json:"messages"`
|
||||
Tools []claudeTool `json:"tools,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
Thinking json.RawMessage `json:"thinking,omitempty"`
|
||||
OutputConfig json.RawMessage `json:"output_config,omitempty"`
|
||||
@@ -316,10 +316,73 @@ func convertOpenAIToClaude(payload interface{}) (*claudeRequest, error) {
|
||||
req.OutputConfig = json.RawMessage(raw)
|
||||
}
|
||||
}
|
||||
if err := validateClaudeToolPairs(req.Messages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// validateClaudeToolPairs prevents malformed OpenAI history from reaching
|
||||
// Anthropic/Bedrock. Every tool_use batch must be answered by the immediately
|
||||
// following user message, with exactly one tool_result for every ID.
|
||||
func validateClaudeToolPairs(messages []claudeMessage) error {
|
||||
validatedResultMessages := make(map[int]struct{})
|
||||
for i, msg := range messages {
|
||||
expected := make(map[string]struct{})
|
||||
for _, block := range msg.Content.Blocks {
|
||||
if block.Type != "tool_use" {
|
||||
continue
|
||||
}
|
||||
id := strings.TrimSpace(block.ID)
|
||||
if id == "" {
|
||||
return fmt.Errorf("claude bridge: assistant message %d has tool_use without id", i)
|
||||
}
|
||||
if _, duplicate := expected[id]; duplicate {
|
||||
return fmt.Errorf("claude bridge: assistant message %d has duplicate tool_use id %q", i, id)
|
||||
}
|
||||
expected[id] = struct{}{}
|
||||
}
|
||||
if len(expected) == 0 {
|
||||
continue
|
||||
}
|
||||
if i+1 >= len(messages) || messages[i+1].Role != "user" {
|
||||
return fmt.Errorf("claude bridge: assistant message %d tool_use is not immediately followed by user tool_result", i)
|
||||
}
|
||||
seen := make(map[string]struct{}, len(expected))
|
||||
for _, block := range messages[i+1].Content.Blocks {
|
||||
if block.Type != "tool_result" {
|
||||
continue
|
||||
}
|
||||
id := strings.TrimSpace(block.ToolUseID)
|
||||
if _, ok := expected[id]; !ok {
|
||||
return fmt.Errorf("claude bridge: user message %d has unexpected tool_result id %q", i+1, id)
|
||||
}
|
||||
if _, duplicate := seen[id]; duplicate {
|
||||
return fmt.Errorf("claude bridge: user message %d has duplicate tool_result id %q", i+1, id)
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
}
|
||||
for id := range expected {
|
||||
if _, ok := seen[id]; !ok {
|
||||
return fmt.Errorf("claude bridge: user message %d is missing tool_result for id %q", i+1, id)
|
||||
}
|
||||
}
|
||||
validatedResultMessages[i+1] = struct{}{}
|
||||
}
|
||||
for i, msg := range messages {
|
||||
if _, ok := validatedResultMessages[i]; ok {
|
||||
continue
|
||||
}
|
||||
for _, block := range msg.Content.Blocks {
|
||||
if block.Type == "tool_result" {
|
||||
return fmt.Errorf("claude bridge: user message %d has orphan tool_result id %q", i, block.ToolUseID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Conversion: Claude Response → OpenAI Response (non-streaming)
|
||||
// ============================================================
|
||||
|
||||
@@ -106,6 +106,65 @@ func TestConvertOpenAIToClaude_OutputConfigEffort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIToClaudeAcceptsCompleteMultiToolBatch(t *testing.T) {
|
||||
payload := map[string]interface{}{
|
||||
"model": "claude-test",
|
||||
"messages": []interface{}{
|
||||
map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"tool_calls": []interface{}{
|
||||
map[string]interface{}{"id": "c1", "function": map[string]interface{}{"name": "one", "arguments": "{}"}},
|
||||
map[string]interface{}{"id": "c2", "function": map[string]interface{}{"name": "two", "arguments": "{}"}},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{"role": "tool", "tool_call_id": "c1", "content": "r1"},
|
||||
map[string]interface{}{"role": "tool", "tool_call_id": "c2", "content": "r2"},
|
||||
map[string]interface{}{"role": "assistant", "content": "done"},
|
||||
},
|
||||
}
|
||||
req, err := convertOpenAIToClaude(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(req.Messages) != 3 || len(req.Messages[1].Content.Blocks) != 2 {
|
||||
t.Fatalf("unexpected converted messages: %+v", req.Messages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIToClaudeRejectsMissingToolResult(t *testing.T) {
|
||||
payload := map[string]interface{}{
|
||||
"model": "claude-test",
|
||||
"messages": []interface{}{
|
||||
map[string]interface{}{
|
||||
"role": "assistant",
|
||||
"tool_calls": []interface{}{
|
||||
map[string]interface{}{"id": "c1", "function": map[string]interface{}{"name": "one", "arguments": "{}"}},
|
||||
map[string]interface{}{"id": "c2", "function": map[string]interface{}{"name": "two", "arguments": "{}"}},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{"role": "tool", "tool_call_id": "c1", "content": "r1"},
|
||||
},
|
||||
}
|
||||
_, err := convertOpenAIToClaude(payload)
|
||||
if err == nil || !strings.Contains(err.Error(), "missing tool_result") {
|
||||
t.Fatalf("expected missing tool_result error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIToClaudeRejectsOrphanToolResult(t *testing.T) {
|
||||
payload := map[string]interface{}{
|
||||
"model": "claude-test",
|
||||
"messages": []interface{}{
|
||||
map[string]interface{}{"role": "user", "content": "start"},
|
||||
map[string]interface{}{"role": "tool", "tool_call_id": "orphan", "content": "result"},
|
||||
},
|
||||
}
|
||||
_, err := convertOpenAIToClaude(payload)
|
||||
if err == nil || !strings.Contains(err.Error(), "orphan tool_result") {
|
||||
t.Fatalf("expected orphan tool_result error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeToOpenAIResponseJSON_Thinking(t *testing.T) {
|
||||
claudeBody := []byte(`{
|
||||
"id":"msg_1","type":"message","role":"assistant","model":"x","stop_reason":"end_turn",
|
||||
|
||||
Reference in New Issue
Block a user