Repository navigation
feat(providers): add Cloudflare, Groq, Mistral, OrcaRouter and xAI providers - #200
Merged
Merged
Conversation
Mirror the OpenRouter integration so Praxist users can route through OrcaRouter's OpenAI-compatible gateway directly: - new model_provider:orcarouter plugin (api_format "orcarouter", default model "orcarouter/auto") - wire ORCAROUTER_API_KEY through credential discovery, CLI provider resolution, setup profiles, and the research-loop provider env - support OrcaRouter through the codex_sdk compatibility relay and Claude SDK base-url normalization - treat orcarouter model names as vendor-prefixed like openrouter - redact sk-orca- keys and document the provider in guides and skills Co-Authored-By: Claude <noreply@anthropic.com>
Adds a first-class `cloudflare` model provider backed by Cloudflare
Workers AI's OpenAI-compatible endpoint, so research runs can use the
DeepSeek models already served on our account without a new external
provider key.
Workers AI publishes only the OpenAI chat-completions surface at
/client/v4/accounts/{account_id}/ai/v1 (probing /v1/messages and
/anthropic/v1/messages returns "No route for that URI"), so the provider
rides the existing OpenAI-compatible path and the Codex relay rather
than the Anthropic transport used by deepseek_alias/openrouter.
Two details drove the shape of the change:
- The account id lives in the URL, not the credential, so the base URL
is interpolated at call time from CLOUDFLARE_ACCOUNT_ID (overridable
with an explicit account id or a whole-URL CLOUDFLARE_BASE_URL)
instead of being a static table entry.
- Workers AI model ids are full `@cf/vendor/model` paths. The generic
openai_compatible normalizer strips everything before the first "/",
which would corrupt them, so the provider declares its own
`cloudflare_workers_ai` api_format that preserves the whole name.
Auth is a bearer token from CLOUDFLARE_API_KEY. Verified end-to-end
through the relay against both @cf/deepseek-ai/deepseek-v4-flash-0731
and @cf/deepseek-ai/deepseek-v4-pro-0813.
`praxist resolve` refused the new provider with "agent_runtime:codex_sdk is not compatible with model_provider:cloudflare". The runtime plugin declares an explicit provider allowlist that the previous commit did not update, so the provider registered cleanly and passed `praxist doctor` but could not actually start a run. Adds cloudflare to codex_sdk's compatible_model_providers. It is left out of claude_sdk's allowlist on purpose: Workers AI publishes no Anthropic Messages route, so that pairing could never work. Note that `praxist doctor` does not exercise this conformance check; only `praxist resolve`/`start` do.
A launched run died immediately with "CLOUDFLARE_API_KEY is required for Codex SDK provider cloudflare", both peers red after 6 seconds. `_scoped_legacy_provider_env` builds the runtime credential environment from a per-provider allowlist. cloudflare had no branch, so it fell through to the legacy default that forwards only the Anthropic/OpenRouter variables, and the bearer token never reached the Codex client — even though the key was present in the launcher's own environment and in the persisted config. Adds the cloudflare branch (CLOUDFLARE_API_KEY, OPENAI_API_KEY for the relay, plus CLOUDFLARE_ACCOUNT_ID/CLOUDFLARE_BASE_URL so the account-scoped upstream can be interpolated in the run process), mirroring the deepseek_alias branch. Also adds CLOUDFLARE_API_KEY to the Codex adapter's _MODEL_CREDENTIAL_ENV_KEYS so it is stripped from task shell/MCP child environments unconditionally. The `_looks_secret` "KEY" heuristic already covered it, but that path has a task_keys escape hatch and every sibling provider key is listed explicitly. Neither gap is visible to `praxist doctor` or `praxist resolve`; only a real run reaches this code.
Adds `model_provider:cloudflare` to the built-in provider list, the codex_sdk relay routing table, and the plugin API reference. Records the two things that are not obvious from the manifest: the endpoint base is account-scoped (interpolated from CLOUDFLARE_ACCOUNT_ID, overridable via CLOUDFLARE_BASE_URL), and the distinct `cloudflare_workers_ai` API format exists only so `@cf/vendor/model` ids survive model-name normalization. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A5c7aqDR1adMAtrFtzbw7o
- Add model_provider:groq_alias, model_provider:mistral_alias, and model_provider:xai_alias plugins on the OpenAI-compatible path. - Register short name mapping in praxist.cli._env and alias normalization in praxist.cli._setup_common. - Add GROQ_API_KEY, MISTRAL_API_KEY, and XAI_API_KEY to CredentialResolver.ENV_SPECS. - Declare compatibility with agent_runtime:codex_sdk for Chat Completions relay execution. - Add provider environment handling in research_loop stage and provider_env. - Update plugin reference and provider guides in docs/. - Add unit, conformance, relay, setup, and hardening test coverage.
xai_alias declared grok-2-latest and grok-2-mini, which xAI no longer serves.
Every call returns {"code":"invalid-argument","error":"Model not found:
grok-2-latest"}, so the provider could not answer a single request with its
shipped defaults.
Verified live against the xAI catalog:
default_model grok-4.6
strong_reasoner grok-4.6
cheap_peer grok-4.20-0309-non-reasoning
grok-4.20-0309-non-reasoning is the cheapest text model on the account (186
total tokens for a two-character prompt, against 682 for grok-4.6). All three
match the existing "grok*" compatibility pattern.
test_modeling_normalize mirrors the yaml, so its expected value moves too.
groq_alias could not serve a single request. All three declared models were retired (llama-3.3-70b-versatile, llama-3.1-8b-instant -> HTTP 404), and no model in Groq's current catalogue could replace them, because renaming alone does not work here. Groq publishes ids such as openai/gpt-oss-20b, qwen/qwen3.8-27b and groq/compound. Under api_format: openai_compatible, _normalize_model_for_api_format strips the segment before the first "/", treating the vendor as a leftover aggregator prefix. startup.py then validates the *stripped* name against compatible_model_patterns and validate_model_for_provider raises. groq/compound also reached the API as "compound", which Groq does not serve. cloudflare_workers_ai already documents and solves this exact problem: its @cf/vendor/model ids are exempt from stripping. Groq is the same case, so it gets the same treatment via a dedicated api_format. api_format is the value threaded through every call site (build_call sees only profile.api_format), so a plugin-level flag would not reach it without new plumbing. api_format is compared in four places, all in modeling.py; three are capability tags, where "groq" correctly falls through to openai_compatible + tool_use. capabilities still declares provider.openai_compatible, which remains accurate: Groq is OpenAI-compatible on the wire, and api_format now encodes model-id shape only -- exactly as it does for Cloudflare. Verified live through praxist's own chain (normalize -> validate -> build_call -> Groq): cheap_peer openai/gpt-oss-20b and strong_reasoner openai/gpt-oss-120b both return HTTP 200. Cross-provider model names (anthropic/claude-opus-4.7, grok-4.6, mistral-large-latest) are still rejected, and all nine bundled providers still resolve their declared default through normalize and validate unchanged.
…ider-expansion Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # praxist/plugins/workflow_stages/research_loop/stage.py
This was referenced Sep 15, 2026
4 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds five model providers — Cloudflare Workers AI, Groq, Mistral, OrcaRouter and
xAI — as first-class plugins, with the runtime and credential wiring each needs to
work through
praxist configure-llm. Two defects found during validation arefixed here:
groq_aliaswas structurally unusable, andxai_aliasshippedretired models.
Scope
praxist/plugins/model_providers/, pluspraxist/core/{cloudflare,credentials, modeling,redaction}.py,praxist/cli/{_env,_setup_common,setup,start}.py,codex_sdk/{_relay,adapter}.py, andresearch_loop/{provider_env,startup, stage}.py.normalize_model_for_providergains one exemptapi_format.the
groqapi_formatis new, so no previously valid configuration changesmeaning. Docs and skills updated alongside.
or protocol; these are transport and credential concerns only.
Verification
Each plugin's declared
default_modelwas checked against the vendor's livecatalog (
GET /models), then with a 1-token chat call. This is the check the testsuite cannot perform — to
pytest, a model name is just a string.default_modelopenrouteranthropic/claude-opus-4.7mistral_aliasmistral-large-latestcloudflare@cf/deepseek-ai/deepseek-v4-flash-0731orcarouterorcarouter/autogroq_aliasllama-3.3-70b-versatilexai_aliasgrok-2-latestDefect 1 —
groq_aliascould not route. All three declared models returned404; Groq's catalog no longer carries a Llama chat model. A rename was not enough:
under
api_format: openai_compatiblethevendor/prefix was stripped beforestartup.pyvalidated the name againstcompatible_model_patterns, so no modelin the catalog both survived normalization and passed validation. Fixed with a
dedicated
groqapi_formatexempt from stripping, mirroring howcloudflare_workers_aialready handles its@cf/vendor/modelIDs.Defect 2 —
xai_aliasdeclared retired models.grok-2-latestandgrok-2-minino longer exist; the live catalog is grok-4.x. Config-only fix:grok-4.6fordefault_modelandstrong_reasoner,grok-4.20-0309-non-reasoningforcheap_peer.Integration test — Praxist's own resolution chain. Raw HTTP proves the vendor
works, not that the plugin wiring does. This drove
plugin.yaml→provider_upstream()→provider_key_var()→ live call, the path a realpraxist configure-llm --provider Xtakes. All six providers resolved andreturned 200.
Checklist
.github/CONTRIBUTING.mdand the contribution terms inLICENSE.md.