June 7, 2026

One send chokepoint for every model provider

A single request/response contract now sits in front of every LLM provider Chalie talks to

Yesterday’s path-only upload work closed one structural hole — bytes no longer leak into the conversation. Today I went after a second one: the provider integrations (Anthropic, OpenAI, Gemini, Ollama) had each grown their own service class, their own over-limit handling, and their own idea of what a “response” looked like. That divergence is exactly the kind of thing that turns into a silent bug the day one provider’s edge case doesn’t match the others’ assumptions. The fix was to stop treating “call the model” as several different problems and make it one.

The refactor introduces a request/response contract — a strict pair of data objects every provider call goes through — and a thin client interface that each platform implements against. The orchestrator that sits in front of them owns the pre-flight capacity check, the logging chokepoint every call passes through, and the token measurement the context compactor relies on. Because every provider now speaks the same contract, there’s a single place a request gets built, and a request that’s too large for a provider raises the same typed error no matter which model was going to answer it. That consistency is what lets Chalie treat providers interchangeably instead of special-casing each one’s failure mode.

The payoff of unifying the interface is what it let me delete: several separate service classes, a fallback wrapper, a logging wrapper, and just over 1,200 lines out of the file that used to hold all of this ad hoc. Deleting code you didn’t have to write carefully four times over is the actual win here — every one of those lines was a place a future change could silently diverge between providers. Fewer moving parts means fewer ways for “it works with one model but not another” bugs to sneak in.

Two smaller fixes rode along. The contributor docs got corrected where they’d drifted from what the code actually does — the policy table is 234 rows, not the number previously written down, and tool results are recorded through a single trail service rather than the older setup the docs still described. And on mobile, tapping the attach button in chat now opens the real OS photo picker instead of jumping straight to the camera — a one-line fix, but the kind of thing that makes an app feel native instead of assembled.

None of this changes what Chalie can do today — it changes how much I can trust what it does tomorrow. A single, typed contract in front of every provider means adding a new provider, hardening retry behavior, or improving how the compactor budgets tokens all become changes in one place instead of several. That’s the kind of foundation that pays for itself the next time a provider ships a breaking API change and only one file has to know about it.

  • Unified request/response contract plus a thin client interface shared by every model provider Chalie supports

  • The provider layer now runs as a standalone orchestrator owning capacity checks, call logging, and token measurement for the compactor

  • A single typed over-capacity error replaces provider-specific limit handling, so every provider fails the same way

  • Deleted the separate per-provider service classes and consolidated well over a thousand lines of duplicated logic

  • Fixed the mobile chat attach button to open the photo library, not just the camera

  • Corrected two places where contributor documentation had drifted from the shipped policy and tool-recording behavior