Part 1
One interface over two SDKs
The SDKs sit behind one type from the first call.
Study first
- Anthropic's Messages API:
system,messages,max_tokens, streaming withclient.messages.streamand the final message'susage. - OpenAI's Responses API:
instructions,input,max_output_tokens, streaming events, andusageon the completed response. - The sampling controls: reasoning effort on models that reason (
output_config.effortfor Claude,reasoning.effortfor OpenAI) andtemperature, 0 to 1, on models that still accept it. The current Claude models rejecttemperature.
Build
ANTHROPIC_API_KEYin.env.local, from the Anthropic console, andOPENAI_API_KEYif you have one.src/lib/providers.ts: aProviderwith aname, a list ofModels (id and published price per million tokens, first one the default) andstream(completion, onText), which resolves with the tokens used. One factory per SDK, withmaxRetries: 3on the client.- Tests that feed each SDK an event stream written from the SDK's event types, through a fake
fetch: text deltas arrive in order, usage comes from the final event, and the request body carries the right fields.
Done when
pnpm testpasses for both providers, and a Haiku call throughanthropic().streamprints a reply.
Technical notes
- Prices live on the model list and nowhere else; a model without a published price has none, and the cost report names it instead of guessing.
- Both SDKs retry 429 and 5xx answers with backoff, Anthropic's 529 overloaded among them; after the third retry the error surfaces.
- The OpenAI stream has 3 terminal events:
response.completed,response.incomplete(the reply was cut; usage is still reported) andresponse.failed(thrown as an error with the API's message). - Clients are built on first use, so switching to a provider without a key fails at the request, where the error can be shown, not at the switch.

