How the Free-Claude-Code Message Converter Translates Anthropic to OpenAI Formats
The message converter in free-claude-code translates Anthropic's native message format into OpenAI-compatible chat completions by normalizing block structures, splitting tool results into separate messages, and wrapping thinking blocks in XML tags while preserving conversation order.
The free-claude-code repository provides a bridge between Anthropic's Claude API and OpenAI's Chat Completion interface. At the heart of this interoperability lies a single utility in providers/common/message_converter.py that handles the bidirectional translation of message payloads, tool definitions, and system prompts.
Core Architecture and Entry Points
The conversion logic is centralized in the AnthropicToOpenAIConverter class. Its public interface consists of static methods that transform Anthropic-style message lists into OpenAI-compliant dictionaries.
The primary entry point is convert_messages(messages, ...), which iterates over each message and dispatches to role-specific handlers. Helper functions get_block_attr and get_block_type (lines 7-13) normalize access to message content, allowing the converter to handle both plain dictionaries and objects with attributes uniformly.
Converting User Messages
User messages in the Anthropic format contain either a plain string or a list of content blocks (text, image, tool_result). The _convert_user_message method (lines 27-61) handles this conversion through a specific sequencing strategy:
- Plain text blocks are accumulated into a single user message.
- Tool result blocks trigger a flush of any pending text, then emit a separate
role: "tool"message containing thetool_use_idand result content.
This split preserves the exact ordering of the conversation while satisfying OpenAI's requirement that tool results exist as distinct messages with a tool_call_id reference.
from providers.common.message_converter import AnthropicToOpenAIConverter
# Example: User message with text and tool result
msg = type(
"Msg",
(),
{
"role": "user",
"content": [
{"type": "text", "text": "Here is the result you asked for:"},
{"type": "tool_result", "tool_use_id": "tc2", "content": "Sunny, 21°C"},
],
},
)
converted = AnthropicToOpenAIConverter.convert_messages([msg])
print(converted)
Output:
[
{"role": "user", "content": "Here is the result you asked for:"},
{"role": "tool", "tool_call_id": "tc2", "content": "Sunny, 21°C"}
]
Converting Assistant Messages
Assistant messages require the most complex transformation. The _convert_assistant_message method (lines 65-122) processes blocks of type text, thinking, and tool_use according to specific rules:
- Text blocks are concatenated to form the message content.
- Thinking blocks are wrapped in
<thinking>XML tags (unlessinclude_thinking=False) and appended to content. - Tool use blocks are extracted into the OpenAI
tool_callsarray format withid,type, andfunctionfields.
If the assistant message contains only tool calls with no textual content, the converter forces the content to a single space to satisfy NIM provider requirements that reject empty content strings.
# Example: Assistant with thinking and tool use
assistant_msg = type(
"Msg",
(),
{
"role": "assistant",
"content": [
{"type": "thinking", "thinking": "Analyzing weather request..."},
{"type": "text", "text": "I'll check the weather for you."},
{"type": "tool_use", "id": "tc1", "name": "get_weather", "input": {"city": "Paris"}}
],
},
)
print(AnthropicToOpenAIConverter.convert_messages([assistant_msg]))
Output:
[
{
"role": "assistant",
"content": "<thinking>Analyzing weather request...</thinking>\nI'll check the weather for you.",
"tool_calls": [
{
"id": "tc1",
"type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\": \"Paris\"}"}
}
]
}
]
Handling System Prompts and Tools
The converter handles auxiliary request components through dedicated static methods.
System prompt conversion via convert_system_prompt (lines 194-211) accepts either a raw string or a list of "text" blocks, emitting a single OpenAI system message.
Tool schema translation maps Anthropic's name, description, and input_schema fields to OpenAI's function object format. The convert_tools method (lines 166-174) restructures each tool definition, while convert_tool_choice (lines 176-194) translates Anthropic's auto/any/tool enums into OpenAI's auto/required/function representations.
Building the Complete Request Body
The build_base_request_body function (lines 214-263) assembles the final OpenAI-compatible payload. It orchestrates the conversion of messages, system prompts, and tools, then injects optional parameters like max_tokens, temperature, and top_p using the helper set_if_not_none from providers/common/utils.py.
from providers.common.message_converter import build_base_request_body
class FakeRequest:
model = "gpt-4o"
max_tokens = 512
temperature = 0.7
messages = [] # Anthropic format messages
tools = [
type("Tool", (), {
"name": "get_weather",
"description": "Fetch weather",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}}
})
]
tool_choice = {"type": "any"}
body = build_base_request_body(FakeRequest())
This produces a complete request body with the model identifier, converted message history, and properly formatted tool definitions ready for the OpenAI Chat Completions endpoint.
Summary
- The free-claude-code message converter lives in
providers/common/message_converter.pyand provides a centralized translation layer between Anthropic and OpenAI APIs. convert_messagesdelegates to role-specific handlers:_convert_user_messagefor user content (splitting tool results into separate messages) and_convert_assistant_messagefor assistant content (handling thinking blocks and tool calls).- Thinking content is wrapped in XML tags, while tool definitions are remapped from Anthropic's
input_schemato OpenAI'sparametersformat. - The
build_base_request_bodyfunction assembles the complete payload, ensuring compatibility with strict NIM providers by avoiding empty content strings.
Frequently Asked Questions
How does the converter handle Anthropic's thinking blocks?
The converter wraps thinking content in <thinking> XML tags and appends it to the message's text content. This behavior can be disabled by setting include_thinking=False in the conversion call, which strips thinking blocks entirely from the output.
Why are tool results split into separate messages?
OpenAI's Chat Completion API requires tool results to exist as distinct messages with role: "tool" and a tool_call_id field referencing the original tool call. The converter automatically extracts tool_result blocks from Anthropic's user messages and emits them as separate entries while preserving the conversational order established in the source list.
What happens if an assistant message contains only tool calls?
When an assistant message has no text content but contains tool calls, the converter forces the content field to a single space character. This prevents API errors with certain NIM (NVIDIA Inference Microservices) providers that reject empty content strings, ensuring compatibility across different OpenAI-compatible endpoints.
Where does the tool schema translation occur?
Tool schema conversion happens in the convert_tools method (lines 166-174), which maps Anthropic's input_schema field to OpenAI's parameters field within a function object. The convert_tool_choice method (lines 176-194) handles the mapping of Anthropic's tool_choice enums to OpenAI's equivalent values, translating any to required and auto to auto.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →