openAIMessagesToAnthropic
JavaScript · package toolnexus · SPEC §11 · js/src/translate.ts
function openAIMessagesToAnthropic(messages: any[]): { messages: any[]; system: string }Converts an OpenAI messages array into Anthropic-native content blocks — the inbound conversion
client.translate uses internally on the Anthropic path,
exported standalone because it is useful on its own whenever you hold an OpenAI-shaped transcript
and need to hand it to an Anthropic-shaped call by some other route.
When to use it
Section titled “When to use it”Reach for this the moment you’re converting a transcript by hand instead of going through
translate — feeding an OpenAI-format history into a raw
Anthropic Messages API call, building a transcript viewer that needs to render both shapes, or
testing that your own tool-result bookkeeping survives the round trip. translate calls this for
you on style: "anthropic"; you only need it directly when translate itself doesn’t fit.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. A basic round trip — system hoisted out, tool call and result paired up
Section titled “1. A basic round trip — system hoisted out, tool call and result paired up”import assert from "node:assert"import { openAIMessagesToAnthropic } from "toolnexus"
const { messages, system } = openAIMessagesToAnthropic([ { role: "system", content: "Be terse." }, { role: "user", content: "weather in paris?" }, { role: "assistant", content: null, tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: '{"city":"Paris"}' } }] }, { role: "tool", tool_call_id: "c1", content: "sunny" },])
assert.equal(system, "Be terse.") // system HOISTED out — Anthropic takes it separatelyassert.equal(messages[0].content, "weather in paris?")assert.deepEqual(messages[1].content[0], { type: "tool_use", id: "c1", name: "get_weather", input: { city: "Paris" } }) // arguments re-PARSED to an objectassert.deepEqual(messages[2].content[0], { type: "tool_result", content: "sunny", tool_use_id: "c1" })
console.log("ok:", system, "|", messages.length, "messages")2. The part a flattening translator gets wrong — consecutive tool results MERGE into one turn
Section titled “2. The part a flattening translator gets wrong — consecutive tool results MERGE into one turn”Two tool calls in the same assistant turn produce two separate tool-role messages on the OpenAI
wire. Anthropic expects ONE result-bearing user turn answering the preceding assistant turn — not
two — so consecutive tool results merge.
import assert from "node:assert"import { openAIMessagesToAnthropic } from "toolnexus"
const { messages } = openAIMessagesToAnthropic([ { role: "user", content: "weather and time in tokyo?" }, { role: "assistant", content: null, tool_calls: [ { id: "c1", type: "function", function: { name: "get_weather", arguments: '{"city":"Tokyo"}' } }, { id: "c2", type: "function", function: { name: "get_time", arguments: '{"city":"Tokyo"}' } }, ] }, { role: "tool", tool_call_id: "c1", content: "sunny" }, { role: "tool", tool_call_id: "c2", content: "09:00" },])
// user, assistant(2 tool_use blocks), ONE merged user turn — NOT two separate user turns.assert.equal(messages.length, 3)assert.equal(messages[1].content.length, 2)assert.equal(messages[2].role, "user")assert.equal(messages[2].content.length, 2)assert.deepEqual(messages[2].content.map((b: any) => b.tool_use_id), ["c1", "c2"])
console.log("ok: 2 tool_calls -> 1 merged result turn, not 2")3. content array flattening, developer hoisting, and the companion converters
Section titled “3. content array flattening, developer hoisting, and the companion converters”A content given as an array of parts is flattened to text; developer messages hoist alongside
system. openAIToolsToAnthropic/openAIToolChoiceToAnthropic live in the same file and cover
the declaration side of the same OpenAI→Anthropic conversion.
import assert from "node:assert"import { openAIMessagesToAnthropic, openAIToolsToAnthropic, openAIToolChoiceToAnthropic } from "toolnexus"
const { messages, system } = openAIMessagesToAnthropic([ { role: "developer", content: "Follow house style." }, { role: "user", content: [{ type: "text", text: "hello " }, { type: "text", text: "there" }] },])assert.equal(system, "Follow house style.") // developer hoists exactly like systemassert.equal(messages[0].content, "hello there") // parts array flattened to plain text
const tools = openAIToolsToAnthropic([ { type: "function", function: { name: "get_weather", description: "d", parameters: { type: "object", properties: {} } } },])assert.equal(tools[0].name, "get_weather")assert.deepEqual(tools[0].input_schema, { type: "object", properties: {} })
const choice = openAIToolChoiceToAnthropic("required")assert.deepEqual(choice, { type: "any" }) // OpenAI "required"/"any" -> Anthropic {type:"any"}
console.log("ok:", system, "|", tools[0].name, "|", choice)Signature
Section titled “Signature”| Parameter | Type | What it is |
|---|---|---|
messages |
any[] |
An OpenAI messages array — plain user/assistant/system/developer/tool entries. |
returns .messages |
any[] |
Anthropic-native messages: tool_use/tool_result content blocks, tool results merged. |
returns .system |
string |
system/developer messages hoisted out and joined. |
See also
Section titled “See also”translate— Uses this internally on the Anthropic path; most callers want that instead of calling this directly.