Summary
Generalize the OpenAI adapter into a reusable OpenAI-compatible provider seam so that providers speaking the OpenAI Chat Completions wire format (Grok/xAI, Kimi, GLM, DeepSeek, and open-weight endpoints via aggregators) can be added as a data row + pricing rows, not a new adapter. Ship Grok (xAI) as the reference provider that proves the seam.
This is the framework ticket. The per-provider issues (Kimi, GLM, DeepSeek, generic compat) depend on it.
Why this is cheap
adapters/driven/providers/openai.py already translates the OpenAI Chat Completions shape in both directions (to_wire / from_wire), and these providers emit the same messages / tools payloads and the same finish_reason values (stop, tool_calls, length). The only thing hard-coded today is the client construction: AsyncOpenAI() with no base_url. Point that at a per-provider base URL + key and the existing translation works unchanged. application/loop.py does not move (load-bearing rule).
Scope
Seam checklist (the 5 touchpoints, for reference)
| # |
File |
Change |
| 1 |
adapters/driven/providers/* |
Reuse openai.py translation via the registry; no new adapter |
| 2 |
domain/model/stop_reason.py |
Map compat providers through the openai table |
| 3 |
composition.py::make_gateway |
Registry lookup + env-var check |
| 4 |
pyproject.toml |
Comment: openai extra covers compat providers |
| 5 |
dryfire/data/pricing.yaml |
xai:<model> rows |
Acceptance
make check passes (lint + typecheck + arch + test), all offline.
- A suite with
provider: xai runs against a recorded cassette with zero new adapter code beyond the seam.
- The Anthropic and OpenAI paths are unchanged (existing v0.1/v0.2 backward-compat tests green).
Out of scope
Native non-compat wire shapes (Gemini) — tracked separately.
Summary
Generalize the OpenAI adapter into a reusable OpenAI-compatible provider seam so that providers speaking the OpenAI Chat Completions wire format (Grok/xAI, Kimi, GLM, DeepSeek, and open-weight endpoints via aggregators) can be added as a data row + pricing rows, not a new adapter. Ship Grok (xAI) as the reference provider that proves the seam.
This is the framework ticket. The per-provider issues (Kimi, GLM, DeepSeek, generic compat) depend on it.
Why this is cheap
adapters/driven/providers/openai.pyalready translates the OpenAI Chat Completions shape in both directions (to_wire/from_wire), and these providers emit the samemessages/toolspayloads and the samefinish_reasonvalues (stop,tool_calls,length). The only thing hard-coded today is the client construction:AsyncOpenAI()with nobase_url. Point that at a per-provider base URL + key and the existing translation works unchanged.application/loop.pydoes not move (load-bearing rule).Scope
OpenAIGateway.__init__to acceptbase_urland anapi_keysourced from a configurable env var; keep the default (nobase_url) byte-for-byte identical to today.name(e.g.xai, forprovider:modelpricing keys) but maps finish reasons through the existing"openai"table indomain/model/stop_reason.py(makemap_stop_reason's provider key a parameter of the gateway, notself.name).{name: (base_url, env_var)}— consumed bycomposition.py::make_gateway, replacing per-providerifbranches for the compat family with one lookup.base_url=https://api.x.ai/v1, key fromXAI_API_KEY. Addxai:<model>pricing rows todryfire/data/pricing.yaml.pyproject.toml: no new SDK — theopenaiextra already covers compat providers. Document that in the extra's comment.from_wire/to_wireround-trips, including a tool-call turn and a malformed-arguments turn (defensive parse must hold).@pytest.mark.livesmoke test behindXAI_API_KEY.Seam checklist (the 5 touchpoints, for reference)
adapters/driven/providers/*openai.pytranslation via the registry; no new adapterdomain/model/stop_reason.pyopenaitablecomposition.py::make_gatewaypyproject.tomlopenaiextra covers compat providersdryfire/data/pricing.yamlxai:<model>rowsAcceptance
make checkpasses (lint + typecheck + arch + test), all offline.provider: xairuns against a recorded cassette with zero new adapter code beyond the seam.Out of scope
Native non-compat wire shapes (Gemini) — tracked separately.