I'm curious how other people are handling a problem that only shows up once an app talks to more than one model provider.
We run coding agents against Claude, OpenAI-compatible models, and a few others. At first, we put provider-specific logic inside each app. That worked until streaming and tool calls started diverging:
We recently moved that logic behind a small gateway. It gives us one internal event model and keeps the applications talking to one contract.
The tradeoff is obvious: the gateway becomes part of the critical path, and debugging needs to preserve the raw provider events. But it has been much easier to add or replace a model without touching every agent.
I'm not trying to pitch a finished product here. I'm more interested in the architecture choices people have made.
If you run agents across multiple providers, where does this compatibility layer live for you: inside each client, in a shared SDK, or behind a gateway? And what would make you avoid a gateway?
I wrote down a few of the edge cases we ran into here: https://api.luoluocoder.com
I'd use a gateway once more than one app depends on the model layer, but I would keep raw provider events queryable next to the normalized stream. The painful bugs are usually partial ones: tool-call chunks, retry state, finish reasons, and usage numbers that only make sense if you can inspect the original request without replaying it. If there is only one client, a shared SDK is probably simpler until the second real consumer appears.