package convert import ( "encoding/json" "openteam/server/internal/proxy/openai" ) // claudeMessagesToChat converts Claude messages JSON into OpenAI chat messages. func claudeMessagesToChat(raw json.RawMessage, out *[]openai.ChatMessage) error { var msgs []struct { Role string `json:"role"` Content json.RawMessage `json:"content"` } if err := json.Unmarshal(raw, &msgs); err != nil { return err } for _, m := range msgs { var text string if err := json.Unmarshal(m.Content, &text); err == nil { content, _ := json.Marshal(text) *out = append(*out, openai.ChatMessage{Role: m.Role, Content: content}) continue } var blocks []map[string]json.RawMessage if err := json.Unmarshal(m.Content, &blocks); err != nil { return err } // Split blocks into: plain content parts, tool_use (→ tool_calls), // and tool_result (→ separate role=tool messages). var parts []json.RawMessage var toolCalls []json.RawMessage for _, b := range blocks { var typ string _ = json.Unmarshal(b["type"], &typ) switch typ { case "tool_use": tc := map[string]any{ "id": rawString(b["id"]), "type": "function", "function": map[string]any{ "name": rawString(b["name"]), "arguments": string(b["input"]), }, } encoded, _ := json.Marshal(tc) toolCalls = append(toolCalls, encoded) case "tool_result": msg := openai.ChatMessage{ Role: "tool", ToolCallID: rawString(b["tool_use_id"]), } // content may be a string or array of text blocks. if b["content"] != nil { var s string if json.Unmarshal(b["content"], &s) == nil { content, _ := json.Marshal(s) msg.Content = content } else { var texts []struct { Type string `json:"type"` Text string `json:"text"` } if json.Unmarshal(b["content"], &texts) == nil { var buf string for _, t := range texts { buf += t.Text } content, _ := json.Marshal(buf) msg.Content = content } } } encoded, _ := json.Marshal(msg) *out = append(*out, msg) _ = encoded default: // text / image blocks → OpenAI content part. part, err := claudeBlockToOpenAIContent(b) if err != nil { return err } if part != nil { parts = append(parts, part) } } } if len(toolCalls) > 0 { msg := openai.ChatMessage{Role: "assistant"} if len(parts) > 0 { content, _ := json.Marshal(partsToText(parts)) msg.Content = content } tcArr, _ := json.Marshal(toolCalls) msg.ToolCalls = tcArr *out = append(*out, msg) } else if len(parts) > 0 { if len(parts) == 1 { // Collapse a single text part back to a plain string. var s string if json.Unmarshal(parts[0], &s) == nil { *out = append(*out, openai.ChatMessage{Role: m.Role, Content: parts[0]}) } else { arr, _ := json.Marshal(parts) *out = append(*out, openai.ChatMessage{Role: m.Role, Content: arr}) } } else { arr, _ := json.Marshal(parts) *out = append(*out, openai.ChatMessage{Role: m.Role, Content: arr}) } } } return nil } func claudeBlockToOpenAIContent(b map[string]json.RawMessage) (json.RawMessage, error) { var typ string _ = json.Unmarshal(b["type"], &typ) switch typ { case "text": part := map[string]any{"type": "text", "text": rawString(b["text"])} return json.Marshal(part) case "image": // b["source"] may not be present; be defensive. if b["source"] != nil { var src struct { Type string `json:"type"` URL string `json:"url"` Data string `json:"data"` MediaType string `json:"media_type"` } _ = json.Unmarshal(b["source"], &src) if src.URL != "" { return json.Marshal(map[string]any{"type": "image_url", "image_url": map[string]any{"url": src.URL}}) } if src.Data != "" { return json.Marshal(map[string]any{"type": "image_url", "image_url": map[string]any{"url": "data:" + src.MediaType + ";base64," + src.Data}}) } } return nil, nil default: return nil, nil } } func partsToText(parts []json.RawMessage) string { var out string for _, p := range parts { var t struct { Type string `json:"type"` Text string `json:"text"` } if json.Unmarshal(p, &t) == nil { out += t.Text } } return out } func rawString(raw json.RawMessage) string { if len(raw) == 0 { return "" } var s string if err := json.Unmarshal(raw, &s); err != nil { return string(raw) } return s } // chatToClaudeMessages converts chat messages (with system already removed) // into Claude Messages body. func chatToClaudeMessages(msgs []openai.ChatMessage, system string) (map[string]any, error) { claudeMsgs := []map[string]any{} for _, m := range msgs { if m.Role == "system" { continue } // Tool calls on assistant messages → tool_use blocks. if m.Role == "assistant" && len(m.ToolCalls) > 0 { content := []map[string]any{} // Preserve any text content. if text := contentString(m.Content); text != "" { content = append(content, map[string]any{"type": "text", "text": text}) } var calls []struct { ID string `json:"id"` Function struct { Name string `json:"name"` Arguments json.RawMessage `json:"arguments"` } `json:"function"` } _ = json.Unmarshal(m.ToolCalls, &calls) for _, c := range calls { var input map[string]any if err := json.Unmarshal(c.Function.Arguments, &input); err != nil { input = map[string]any{"raw": string(c.Function.Arguments)} } content = append(content, map[string]any{ "type": "tool_use", "id": c.ID, "name": c.Function.Name, "input": input, }) } claudeMsgs = append(claudeMsgs, map[string]any{"role": "assistant", "content": content}) continue } // Tool role messages → tool_result blocks. if m.Role == "tool" { content := []map[string]any{{ "type": "tool_result", "tool_use_id": m.ToolCallID, "content": contentString(m.Content), }} claudeMsgs = append(claudeMsgs, map[string]any{"role": "user", "content": content}) continue } claudeMsgs = append(claudeMsgs, map[string]any{"role": m.Role, "content": contentString(m.Content)}) } if len(claudeMsgs) == 0 { claudeMsgs = append(claudeMsgs, map[string]any{"role": "user", "content": "Hi"}) } body := map[string]any{"messages": claudeMsgs} if system != "" { body["system"] = system } return body, nil } // toolsToClaude converts OpenAI function tools to Claude tools. func toolsToClaude(tools []openai.Tool) []map[string]any { out := []map[string]any{} for _, t := range tools { if t.Type != "" && t.Type != "function" { continue } out = append(out, map[string]any{ "name": t.Function.Name, "description": t.Function.Description, "input_schema": json.RawMessage(t.Function.Parameters), }) } return out } // claudeToolsToOpenAI converts Claude tools to OpenAI function tools. func claudeToolsToOpenAI(tools json.RawMessage) []openai.Tool { var list []struct { Name string `json:"name"` Description string `json:"description"` InputSchema json.RawMessage `json:"input_schema"` } if err := json.Unmarshal(tools, &list); err != nil { return nil } out := []openai.Tool{} for _, t := range list { out = append(out, openai.Tool{ Type: "function", Function: openai.FunctionTool{ Name: t.Name, Description: t.Description, Parameters: t.InputSchema, }, }) } return out }