Skip to content

Provider: OpenAI-compatible provider seam (+ Grok/xAI reference) #71

Description

@csmatar

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

  • Extend OpenAIGateway.__init__ to accept base_url and an api_key sourced from a configurable env var; keep the default (no base_url) byte-for-byte identical to today.
  • Decouple the pricing/name identity from the stop-reason mapping key: a compat gateway keeps its own name (e.g. xai, for provider:model pricing keys) but maps finish reasons through the existing "openai" table in domain/model/stop_reason.py (make map_stop_reason's provider key a parameter of the gateway, not self.name).
  • Introduce a small provider registry — a data table {name: (base_url, env_var)} — consumed by composition.py::make_gateway, replacing per-provider if branches for the compat family with one lookup.
  • Wire Grok (xAI) end-to-end through the registry: base_url=https://api.x.ai/v1, key from XAI_API_KEY. Add xai:<model> pricing rows to dryfire/data/pricing.yaml.
  • pyproject.toml: no new SDK — the openai extra already covers compat providers. Document that in the extra's comment.
  • Offline contract tests: recorded xAI payloads → from_wire / to_wire round-trips, including a tool-call turn and a malformed-arguments turn (defensive parse must hold).
  • One @pytest.mark.live smoke test behind XAI_API_KEY.

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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestproviderModel provider / adapter support

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions