Wire requests

One normalized request, rendered into both OpenAI dialects. They are implemented as separate builders sharing no object literals, because conflating them is the single largest source of 400s — and because a framework that claims to “craft requests correctly” should let you check.

The baseline. Note the tool shape: Responses puts name/parameters at the top level, Chat Completions nests them under `function`. Sending one to the other is a guaranteed 400.

Input — NormalizedRequest

{
  "model": "gpt-5.2",
  "system": "You are a research assistant.",
  "messages": [
    {
      "id": "user_1",
      "role": "user",
      "parts": [
        {
          "type": "text",
          "text": "Which vendor should we pick?"
        }
      ]
    }
  ],
  "tools": [
    {
      "name": "compareCompetitors",
      "description": "Positioning matrix.",
      "parameters": {
        "type": "object",
        "properties": {
          "against": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  ],
  "maxOutputTokens": 2000
}
POST /v1/responses
{
  "model": "gpt-5.2",
  "input": [
    {
      "type": "message",
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Which vendor should we pick?"
        }
      ]
    }
  ],
  "instructions": "You are a research assistant.",
  "tools": [
    {
      "type": "function",
      "name": "compareCompetitors",
      "description": "Positioning matrix.",
      "parameters": {
        "type": "object",
        "properties": {
          "against": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    }
  ],
  "tool_choice": "auto",
  "parallel_tool_calls": true,
  "max_output_tokens": 2000
}
POST /v1/chat/completions
{
  "model": "gpt-5.2",
  "messages": [
    {
      "role": "system",
      "content": "You are a research assistant."
    },
    {
      "role": "user",
      "content": "Which vendor should we pick?"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "compareCompetitors",
        "description": "Positioning matrix.",
        "parameters": {
          "type": "object",
          "properties": {
            "against": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  ],
  "parallel_tool_calls": true,
  "tool_choice": "auto",
  "max_completion_tokens": 2000
}

Where they disagree

ResponsesChat Completions
history fieldinput[]messages[]
systeminstructions (top level)a message with role system
user text partinput_texttext
image partinput_imageimage_url
file partinput_filefile
assistant textoutput_textcontent: string
tool declarationflat { type, name, parameters }nested { type, function: {…} }
model tool callinput item function_callassistant.tool_calls[]
tool outputinput item function_call_outputmessage role:"tool" + tool_call_id
output capmax_output_tokensmax_completion_tokens
JSON schematext.formatresponse_format.json_schema
reasoningreasoning.effortreasoning_effort
streamed usagealways includedneeds stream_options.include_usage
asyncbackground: true + polling— none
server historyprevious_response_id— none