An AI SDK Is a Capability Contract

December 25, 2025

It is easy to describe an AI SDK as a convenient wrapper around model providers. Pass messages in, receive tokens out, and switch the provider name when necessary. That description hides the parts that become difficult in a real application: which model supports which input, how a tool call moves through the server, what happens when the stream drops, and who is allowed to perform the action the model requested.

TanStack AI makes one design tension especially clear. Developers want to use a newly released model immediately. A typed SDK wants to know what that model supports before promising autocomplete and compile-time checks. Those goals are related, but they do not always move at the same speed.

The model name carries an implicit promise

Suppose an application sends images, asks for a structured response, and gives the model a tool to look up inventory. A plain string model name tells TypeScript none of those capabilities. The API call may compile and fail only after a user request reaches the provider.

One approach is to maintain a model catalog that records supported modalities, options, and tool features. TanStack AI chose to add model metadata deliberately as providers released models. The payoff is that a developer selecting a supported model can receive more accurate types for its options and capabilities. The cost is a delay between a model launch and an SDK update.

That is a real product trade-off. An SDK that accepts any string maximizes immediate access but can only make broad promises. A curated catalog can narrow mistakes earlier, yet it must be maintained. A well-designed escape hatch lets an expert move ahead while making it clear that they have left the typed contract. Using an unlisted model can be possible, but a type cast gives up the safety of the catalog.

The TanStack AI documentation describes model-specific options and content modalities in its core package. Its model capabilities reference makes the capability fields explicit. Check the current documentation when implementing this pattern because SDK details can change.

Provider adapters solve one boundary

An adapter can make different provider APIs look consistent to the application. That is useful, but it is only one boundary. The app still needs to decide what messages it stores, how it streams partial output to the UI, how it retries or reports a failed request, and where a tool actually executes.

For example, a model may decide to call addToCart with a product ID. The SDK can handle the exchange of messages and tool results. It cannot decide whether the current user owns that cart or whether the product is still in stock. Those checks belong in application code. A tool schema is a useful contract, but a schema is not authorization.

A shopping flow shows why tool loops take work to build: the model requests an action, the application executes it, the result goes back to the model, and the application shows the outcome to the user. The plumbing may be boilerplate, but the effect is consequential. The app should validate the ID, check the user's permissions, make the mutation idempotent where needed, and show the result clearly. The TanStack AI tools documentation describes shared tool definitions with server and client implementations. The existence of both execution environments makes the placement decision visible rather than automatic.

Streaming is an application concern

“Streaming” is often presented as a visual effect: text appears a few words at a time. In an application, it is also a transport and state problem. The server emits chunks. The client reconstructs messages. A tool call may interrupt the text stream. A request can stop before it finishes. The user can cancel. The UI needs to distinguish a completed answer from a partial one.

That means an AI SDK should help with the protocol without making the application forget its own state machine. TanStack AI's connection adapter documentation separates the transport that carries chunks from the chat client that assembles messages and updates the UI. The separation is useful because HTTP streaming, server-sent events, and an in-process async iterable do not have identical failure behavior.

When I evaluate an SDK for a product, I would test the unhappy path before the happy demo: disconnect during a tool result, cancel while a response is streaming, repeat a request after a timeout, and switch a model that lacks a feature the UI assumed was available. Those cases expose the SDK's real contract more quickly than a basic chat screen does.

Type safety is a boundary, not a guarantee

Good types can prevent a developer from asking for a capability that the SDK knows a model lacks. They cannot guarantee that a provider's service is available, that the model follows instructions, or that a tool call is safe to run. Runtime validation remains necessary for input, output, and any side effect.

Types can also become stale. A provider changes a feature, an SDK catalog lags, or a model alias points to a different version. The safest design keeps capability metadata reviewable and makes the fallback path explicit. That is better than pretending that “provider agnostic” means every model behaves identically.

This is why I find the capability contract a more useful lens than the wrapper metaphor. A serious AI SDK tells developers which assumptions it can enforce, which differences it preserves, and which decisions the application must still own. The best abstraction removes repetitive plumbing while leaving the important boundaries visible.

For more on this topic, watch Talks with Ido Evergreen: TanStack AI with Alem Tuzlak.