08. Provider Dialect Boundary
This chapter explains why the agent runtime should not bind itself directly to OpenAI Chat or Responses wire formats. Provider dialects convert external protocols into stable internal model structures.
After reading this chapter, you should understand:
- what the Model IR is
- how the dialect contract isolates provider differences
- why tool schemas should compile from the agent-owned tool contract
- why Anthropic support can be deferred while the boundary exists now
Background
The runtime originally leaned toward OpenAI Chat Completions shapes. That would not survive multiple wire APIs.
OpenAI Chat Completions, OpenAI Responses, and future Anthropic APIs differ in:
- request body shape
- tool schema shape
- streaming event names
- tool-call delta format
- usage fields
- assistant message commit points
The agent loop should not know those differences.
Model IR
The project introduced provider-neutral model types:
AgentModelMessage
AgentModelToolDefinition
AgentModelToolCall
AgentModelRequest
AgentModelResponse
AgentModelStreamEvent
AgentModelUsageSnapshotThey live in:
lib/agent-model-types.tsDialect Contract
The dialect interface lives in:
lib/model-provider-dialect.tsImplemented dialects:
lib/openai-chat-completions-dialect.ts
lib/openai-responses-dialect.tsThe gateway chooses a dialect:
lib/model-gateway.tsDatabase Analogy
The design mirrors SQL dialects:
Runtime IR -> Dialect -> Provider wire formatThe agent loop is like a query planner. The dialect is like the SQL compiler. Provider quirks stay behind the compiler.
Tool Schema Boundary
Agent tools use provider-neutral inputSchema and schemaStrict.
OpenAI dialects compile that to their required wire schema. Later Anthropic dialects should do their own conversion.
Important Rule
lib/agent.ts should not import provider SDK wire types such as:
ChatCompletionMessage
ResponseStreamEventIf those types appear in the agent loop, the dialect boundary has leaked.
Git Evidence
Relevant commit:
837f89f Add provider dialect architectureWhy Anthropic Was Deferred
The architecture is prepared for Anthropic, but the runtime needed to become stronger first:
- model-visible history
- streaming commit semantics
- real tools
- debug visibility
- tests
Adding another provider before these were stable would multiply uncertainty.
Common Misunderstandings
Misunderstanding 1: A Dialect Is Unnecessary Until Non-OpenAI Providers
Even inside OpenAI, Chat Completions and Responses are different protocols. The dialect exists so the agent loop does not care about wire format.
Misunderstanding 2: OpenAI Schema Can Be The Internal Tool Contract
OpenAI strict schema has provider-specific constraints. The internal tool contract should express runtime semantics, then the OpenAI dialect compiles it for OpenAI.
Misunderstanding 3: Adding A Provider Is Just Changing The URL
Messages, tool calls, stream events, usage, and stop reasons can all differ. The extensibility point is the dialect boundary, not only baseURL.
Chapter Summary
This chapter isolates provider differences in dialects: the runtime uses a stable IR, while dialects compile requests, parse streams, convert tool schemas, and normalize usage.
Chapter Checkpoint
Verify the two hard rules of the dialect layer: tool schemas are compiled by the dialect, and the agent loop imports no provider wire types.
- OpenAI strict tool schema compilation tests (no key required):
npx tsx --test tests/openai-tool-schema.test.tsMeasured output:
✔ OpenAI strict tool schema marks every property as required
✔ OpenAI strict tool schema represents optional properties with null type
ℹ pass 2These two cases prove exactly this chapter's Tool Schema Boundary: the internal inputSchema stays provider-neutral, and strict-mode rules ("every property required, optional fields expressed with a null type") are compiled by the OpenAI dialect rather than written into the tool contract.
- Check that the boundary has not leaked (measured: no output; the command exits with code 1, which means pass):
grep -n "ChatCompletionMessage\|ResponseStreamEvent" lib/agent.tsIf this grep ever prints anything, the dialect boundary has leaked into the agent loop — the exact rule stated in this chapter.