Skip to content

feat(provider): OpenAI-compatible provider - #553

Open
espetro wants to merge 11 commits into
vercel-labs:mainfrom
espetro:openai-compatible-provider
Open

feat(provider): OpenAI-compatible provider#553
espetro wants to merge 11 commits into
vercel-labs:mainfrom
espetro:openai-compatible-provider

Conversation

@espetro

@espetro espetro commented Aug 31, 2026

Copy link
Copy Markdown

Adds OpenAI-compatible (Chat Completions) as a first-class inference provider on top of the current provider_set architecture (stacked on #536).

What

  • Config/auth: ProviderId.openai_compatible enum case + parse aliases, provider catalog entry, openai_model settings field, stored-key credential resolution from OPENAI_API_KEY / LITELLM_API_KEY env
  • Gateway: src/gateway/openai_compatible.zig Chat Completions transport (POST /chat/completions, SSE streaming, Bearer auth, admission + delivery signaling, tool_use/tool_calls fragments reducer) and openai_compatible_models.zig model catalog (GET /v1/models)
  • Routing: openai-compatible wired through provider_set.Set Bundle, cli surfaces, ACP, subagent, doctor, auth runtime, footer model menu
  • Lifecycle: FX_MODEL env fallback and OpenAiModelNotSelected error

Verification

  • zig build test: only the 6 pre-existing main failures (tool_runtime, command_runner, workspace_files) — verified in pristine worktree
  • Live smoke tested via Bifrost openai-compatible wire using OPENAI_API_KEY and FX_OPENAI_BASE_URL=http://localhost:8317/openai: single-turn text and a tool-calling round trip (shell exec) both work

Notes for reviewers

  • Cuts Responses API style, permission reviewer, settings-based base_url/api-key persistence, and pr-168's broad test churn (cf. Add OpenAI-compatible client transport #168 for the full version)
  • Use FX_MODEL env for the model id when switching providers via settings, or openai_model key
  • Provider selection alias: openai or openai-compatible (both parsed via model_provider.parse)

espetro added 11 commits August 31, 2026 12:00
…ial source

Extend ProviderId with .anthropic and CredentialSource with
.anthropic_api_key, mirroring the openai/openai_api_key pair from
the OpenAI-compatible transport.

Credentials resolve from the ANTHROPIC_API_KEY environment variable
or a profile-owned anthropic_api_key setting, never from repository
config. Settings store gains the anthropic_model preference and
config_runtime validates it alongside the existing per-provider
model fields.
Implement the Anthropic Messages wire as a stream_provider.Provider:
POST {base}/v1/messages with x-api-key and anthropic-version headers,
top-level system hoisting, tool_use/tool_result blocks, and SSE
parsing for message_start, content_block_start/delta/stop (text,
thinking, and input_json deltas), message_delta, message_stop, and
error events. Stop reasons map to the shared failure taxonomy
(end_turn to stop, max_tokens to length, refusal to content_filter).

Assistant messages carrying both text and tool calls serialize the
tool_use blocks inside the still-open content array; closing it
before the blocks produced invalid JSON on every agentic replay.

Base URL defaults to https://api.anthropic.com and is overridable
via FX_ANTHROPIC_BASE_URL, with FX_E2E_ANTHROPIC_URL reserved for
loopback e2e fixtures. Boundaries match the existing transports
(1MB SSE line, 64MB aggregate, 128 tool calls, 4MB tool arguments).

anthropic_models.zig fetches /v1/models with the same caps as the
grok catalog and exports the standard model_catalog_provider pair.

Both files keep credential acceptance keyed by name so they compile
independently of the enum-case commit.
…faces

Register the anthropic bundle in builtins/providers.zig and thread it
through every surface the openai bundle reaches: the auth provider
picker (choiceAt now yields the fifth provider entry), acp server and
subagent prompt routing, cli_ask and cli_surface config structs,
doctor diagnostics, output contracts, credential resolution call
sites (profile anthropic key propagated alongside the openai key),
and main.zig dependency injection for both the entry config and the
ask/acp runner paths.

Anthropic carries no permission reviewer yet, matching the optional
reviewer slots; openai_compatible.zig rejects the anthropic_api_key
source symmetrically. The model menu lists the anthropic credential
label. Agent streams flow through the same ProviderRoutes dispatch
as the other wire protocols.
… unset

configuredProviderSelection returned ModelNotSelected whenever the
saved settings lacked a model for the active provider, even when
FX_MODEL carried a process override, making 'FX_PROVIDER=openai
FX_MODEL=...' fail before the stream provider was ever consulted.

Fall back to the trimmed FX_MODEL value before erroring for the
non-gateway providers (anthropic included) and scope each
TestEnv install in the override test to its own block so the
FX_PROVIDER assertions no longer inherit a stale environment.
Verified live against an OpenAI-compatible gateway on both the
openai and anthropic wires, including a tool-calling round trip.
Gateway already resolves a compiled default model, so consulting
FX_MODEL before the default changed the reported configured_model
and broke the startup env-override test.
Requests on the anthropic wire returned ProviderAdmissionMissing at
the gateway_step boundary because the codec never called
admission.admit() after serialization, unlike the other transports.
Registers the openai-compatible provider id (aliases: openai,
openai-compatible), its auth catalog entry, and the openai_model
settings field mirroring the other provider-scoped model settings.
Exhaustive switches across auth, runtime, and cli surfaces gain
minimal openai_compatible arms so the provider id compiles everywhere;
transport wiring lands in the following commits.
…ider

Resolves the openai-compatible provider from OPENAI_API_KEY or
LITELLM_API_KEY in the environment first, then falls back to the
stored API key, tagging resolved credentials as .stored_key so no new
CredentialSource variant is needed. Adds provider-specific missing
credential guidance mirroring the anthropic arm.
…t and model catalog

Ports pr-168's chat-completions codec to the current provider ABI:
request bodies build via openai_json/openai_tools, the SSE stream
reduces through an EventSink in openai_client, and openai_compatible
implements stream_provider with admission.admit() before HTTP open and
delivery.markPossiblySent() before send. Base URL resolves from
FX_OPENAI_BASE_URL (default https://api.openai.com/v1) with a loopback-
restricted e2e override. The /models catalog provider reuses the same
bounded HTTP helpers. Responses-API remnants from pr-168 are cut.
… cli surfaces

Adds the .openai_compatible arm to the provider_set native Set with
agent_stream and model catalog providers wired from the ported codec.
Live smoke tested via Bifrost openai wire (text + tool loop) using
OPENAI_API_KEY and FX_OPENAI_BASE_URL env vars.
@vercel-security-reviewer

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant