One reasoning_effort, Translated for Three Backends
An LLM-Bridge postmortem: OpenAI gives one unified reasoning_effort field, but the three backends accept depth in completely different ways — an SDK option, a config override, encoded in the model name.
OpenAI’s request format has a standard field, reasoning_effort, taking values like low / medium / high / xhigh to control how deep the model thinks. LLM-Bridge accepts this field outward — but inward, not one of the three backends accepts depth the same way. This post is about translating one field into three completely different landings.
claude: first, press the SDK’s default down
claude goes through the Agent SDK, where depth is the SDK’s effort option, taking low / medium / high / xhigh / max. The translation itself is easy; the hard part is the default.
The SDK’s own default is high. That default is set for “coding agents” — they want to think deeply, reason across turns. But LLM-Bridge is a chat gateway; most requests are a casual one-liner, and high is both slow and credit-hungry (headless claude burns the monthly Agent SDK allotment). So the gateway deliberately presses the default down to medium:
DEFAULT_EFFORT = "medium" # SDK default is high — too deep, too costly for chat
EFFORT_MAP = {
"minimal": "low", "low": "low", "medium": "medium",
"high": "high", "xhigh": "xhigh", "max": "max",
}
effort = EFFORT_MAP.get(request.reasoning_effort or "", DEFAULT_EFFORT)
If the request explicitly sets reasoning_effort, follow it; if not, use medium rather than the SDK’s high. When you wrap a third-party SDK, its default is tuned for its scenario, not necessarily yours — override it when you should, don’t silently inherit.
codex: from request field to a -c config override
codex is a CLI subprocess, where depth isn’t a command-line flag — you pass a config override through -c:
mapped_effort = EFFORT_MAP.get(effort or "")
if mapped_effort:
args += ["-c", f'model_reasoning_effort="{mapped_effort}"']
-c model_reasoning_effort="high" amounts to temporarily rewriting one of codex’s config entries. Request field → intermediate vocabulary → CLI config override, two hops in between. Without an effort, the parameter isn’t added, letting the CLI use its own default.
agy: depth isn’t in a field, it’s in the model name
At agy (the Antigravity CLI), the reasoning_effort field is ignored outright — not unimplemented, but because its models encode depth into their names:
Gemini 3.5 Flash (Low)
Gemini 3.5 Flash (Medium)
Gemini 3.5 Flash (High)
(Low) / (Medium) / (High) are three different model slugs, not one parameter of one model. Want shallower, pick gemini-3.5-flash-low; deeper, pick -high. The depth knob grows out of model selection, not a separate field. So this channel honestly doesn’t handle reasoning_effort, and the docs say so: control agy’s depth by choosing the model.
The same concept sits in a different place in each vendor’s abstraction: an SDK option for claude, a config entry for codex, part of the model identifier for agy. The adapter layer’s job is recognizing where it lands in each, then wiring the request field over.
Two mapping tables, because the vocabularies genuinely differ
You may have noticed claude and codex each have an EFFORT_MAP, and they don’t match. That’s not a copy-paste someone forgot to merge — the two vendors’ vocabularies are simply different:
- OpenAI’s
minimal: neither claude nor codex has a matching tier, so both map it tolow; - The top tier: claude has
max, somaxmaps tomax; codex only goes up toxhigh, so codex’s table hasmax → xhigh, capped.
# claude: "max": "max" -- SDK supports max
# codex: "max": "xhigh" -- codex has no max, cap at xhigh
Landing one unified field on N backends means maintaining, per backend, a “my vocabulary → its vocabulary” map, and honestly handling the tiers that don’t line up. This dull vocabulary alignment is the real workload hiding under the phrase “unified API.”
Aside: don’t let the user’s environment silently rewrite your request
In the same family as effort, there’s another hidden variable: codex reads the user’s ~/.codex/config.toml and drags its skills, plugins, and even reasoning settings into every request. For a chat gateway, that’s two problems — those skills add tens of thousands of input tokens for nothing, and if the user has xhigh reasoning set globally, every request of yours slows down, and the effort you set in code gets silently rewritten by the user’s environment.
So codex adds --ignore-user-config by default:
if self.ignore_user_config:
args.append("--ignore-user-config")
It only isolates the “user’s personalization,” and auth is unaffected (login is still read from ~/.codex). Now the gateway request is clean: depth is entirely determined by the request’s reasoning_effort, not swayed in secret by a global config the user tweaked some day. It’s configurable — turn it off if you want the user config carried.
Takeaways
One reasoning_effort field, translated for three backends:
- claude: the SDK’s
effortoption, the key being to press the SDK’shighdefault down tomedium— a third-party default is tuned for its scenario, not yours; - codex: turned into a
-c model_reasoning_effort="…"config override, the request field taking two hops to the CLI; - agy: depth encoded in the model name (
(Low/Medium/High)), the field ignored, controlled by picking the model; - Two mapping tables: neither has
minimal, both map it tolow;maxismaxfor claude, capped toxhighfor codex — vocabulary alignment is the real work of a unified API; --ignore-user-config: isolate the user’s global config, so it can’t sneak 20k tokens onto a request or rewrite the effort you set.
In one line: “unified API” sounds like smoothing away differences; doing it means writing, for every single difference, one honest mapping.
Comments