Skip to content

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.

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.

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 separately
assert.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 object
assert.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 system
assert.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)
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.
  • translate — Uses this internally on the Anthropic path; most callers want that instead of calling this directly.