openai.internal.chat_lower_journal

`ai.Journal` -> chat-completions messages. Assistant turns carry their text in `content` and their calls in `tool_calls`; each result comes back as its own `role: "tool"` message keyed by `tool_call_id`. Journal-only events (`RunStarted`, `ToolRequested`, `Usage`, `FinalProduced`) and reasoning blocks lower to nothing.

Reference version

Signature

function openai.internal.chat_lower_journal(
j: ai.Journal,
preview: bool = …
) -> openai.internal.ChatMessage[] throws baml.errors.InvalidArgument | baml.errors.Io | baml.errors.Timeout | baml.json.SerializationError | ai.errors.InvalidRequest | ai.errors.NetworkFailure | ai.errors.PreviewUnsupported

ai.Journal -> chat-completions messages. Assistant turns carry their text in content and their calls in tool_calls; each result comes back as its own role: "tool" message keyed by tool_call_id. Journal-only events (RunStarted, ToolRequested, Usage, FinalProduced) and reasoning blocks lower to nothing.

Callers pass the journal through ai.wire.sanitize_for_client first, so foreign reasoning, empty turns and unanswered tool calls are already handled.

A journal turn's text always collapses to a bare string (_chat_content with collapse = true): a text-only turn was always sent that way, and a bare string is the one content shape every compatible server accepts. A turn carrying media, or a user turn carrying metadata, is a part list.

Source:<builtin>/openai/ns_internal/chat.bamlbytes 2898432897