Skip to content

feat(providers): add Cloudflare, Groq, Mistral, OrcaRouter and xAI providers - #200

Merged
Glen-SP merged 13 commits into
mainfrom
validation/model-provider-expansion
Sep 15, 2026
Merged

Glen-SP merged 13 commits into
mainfrom
validation/model-provider-expansion

Conversation

@Glen-SP

@Glen-SP Glen-SP commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

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 are
fixed here: groq_alias was structurally unusable, and xai_alias shipped
retired models.

Scope

  • Affected modules or public contracts: five new plugins under
    praxist/plugins/model_providers/, plus praxist/core/{cloudflare,credentials, modeling,redaction}.py, praxist/cli/{_env,_setup_common,setup,start}.py,
    codex_sdk/{_relay,adapter}.py, and research_loop/{provider_env,startup, stage}.py. normalize_model_for_provider gains one exempt api_format.
  • Compatibility considerations: existing providers are untouched in behavior;
    the groq api_format is new, so no previously valid configuration changes
    meaning. Docs and skills updated alongside.
  • Task-agnostic rationale: provider selection is orthogonal to any task's metric
    or protocol; these are transport and credential concerns only.

Verification

Each plugin's declared default_model was checked against the vendor's live
catalog (GET /models), then with a 1-token chat call. This is the check the test
suite cannot perform — to pytest, a model name is just a string.

Provider Declared default_model Reachable
openrouter anthropic/claude-opus-4.7 yes
mistral_alias mistral-large-latest yes
cloudflare @cf/deepseek-ai/deepseek-v4-flash-0731 yes
orcarouter orcarouter/auto yes
groq_alias llama-3.3-70b-versatile no — defect 1, fixed
xai_alias grok-2-latest no — defect 2, fixed

Defect 1 — groq_alias could not route. All three declared models returned
404; Groq's catalog no longer carries a Llama chat model. A rename was not enough:
under api_format: openai_compatible the vendor/ prefix was stripped before
startup.py validated the name against compatible_model_patterns, so no model
in the catalog both survived normalization and passed validation. Fixed with a
dedicated groq api_format exempt from stripping, mirroring how
cloudflare_workers_ai already handles its @cf/vendor/model IDs.

Defect 2 — xai_alias declared retired models. grok-2-latest and
grok-2-mini no longer exist; the live catalog is grok-4.x. Config-only fix:
grok-4.6 for default_model and strong_reasoner,
grok-4.20-0309-non-reasoning for cheap_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 real
praxist configure-llm --provider X takes. All six providers resolved and
returned 200.

Checklist

  • The change is focused and does not include unrelated generated files.
  • Tests cover the affected behavior.
  • Documentation, templates, examples, and skills were updated where needed.
  • No credentials, private task data, or research-run artifacts are included.
  • New dependencies and copied assets include their source and license terms.
  • I have read .github/CONTRIBUTING.md and the contribution terms in LICENSE.md.

lovejones2914-spec and others added 13 commits September 3, 2026 19:22
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.
Brings the branch current with main so it satisfies strict required status
checks (branch protection: required_status_checks.strict, enforce_admins).
The two incoming commits are documentation only (#193, #194) and touch no
code, so the branch's Stage 2 result is unaffected.
…ider-expansion

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	praxist/plugins/workflow_stages/research_loop/stage.py
@Glen-SP
Glen-SP merged commit 34e6de7 into main Sep 15, 2026
7 checks passed
@Glen-SP
Glen-SP deleted the validation/model-provider-expansion branch September 15, 2026 20:09
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.

5 participants