Repository navigation
Conversation
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
rajpratham1
left a comment
There was a problem hiding this comment.
Looks good to me. The change correctly merges duplicate --disable-features / --enable-features switches instead of allowing later flags to override earlier ones. The added regression tests also cover the main cases, including default flags and extra_args. No issues found.
|
Thank you @mikeumus for contributing the Cloudflare Workers AI provider path and for documenting your live endpoint checks. We have preserved your commits and integrated them into the shared Our maintainer workflow is documented here: https://github.com/sapientinc/PRAXIST/blob/main/docs/guides/contributing.md#what-happens-after-submission. The validation branch is now ready for the separately scheduled real-task experiment stage. If that stage confirms there is no cross-task regression, we will promote the validated work to We are closing this source PR because its implementation has been transferred to the validation branch, not because the contribution was rejected. This discussion and attribution remain available. |
|
You're welcome @electrixoul and @rajpratham1 ❤️ |
|
You're welcome @Glen-SP 😀 Thank you for opening, sourcing and maintaining such a powerful tool. We use it now with a lot of the AI research that we do. 🙇🏻♂️💕 |
Summary
Adds a
cloudflaremodel provider so Praxist can run peers against CloudflareWorkers AI, plus the two runtime-side fixes that provider turned out to require.
Workers AI exposes one OpenAI-compatible surface at
https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1and publishes no Anthropic Messages route, so the plugin rides the existing
OpenAI-compatible client and only swaps the base URL and the bearer credential
(
CLOUDFLARE_API_KEY). The account id is part of the URL rather than thecredential, so it is interpolated once in
praxist/core/cloudflare.pyandshared by the CLI registry, the Codex relay, and the research-loop stage.
CLOUDFLARE_BASE_URLoverrides the whole URL when an operator needs it.Model ids are full
@cf/vendor/modelpaths (@cf/deepseek-ai/deepseek-v4-flash-0731,@cf/zai-org/glm-5.3-flash). That is why the plugin declares a distinctcloudflare_workers_aiapi_format instead of reusingopenai_compatible: thegeneric format strips everything before the first
/during model-namenormalization, which would rewrite
@cf/deepseek-ai/deepseek-v4-flash-0731todeepseek-ai/deepseek-v4-flash-0731— a name Workers AI rejects. The format ishandled exactly like
openrouterin_normalize_model_for_api_format; itchanges nothing else about the wire protocol.
The provider is selectable through the normal
configure-llmpath(
PROVIDER_KEY_MAP/PROVIDER_REF_FOR_SHORT_NAME/CredentialResolver), with@cf/deepseek-ai/deepseek-v4-flash-0731ascheap_peerand@cf/deepseek-ai/deepseek-v4-pro-0813asstrong_reasoner.The two runtime fixes
Both were required to make the provider usable, and both surfaced only at
resolve/starttime —praxist doctorpassed while neither was in place, sothey are included here rather than split out:
codex_sdkallowlist (plugins/agent_runtimes/codex_sdk/plugin.yaml) —compatible_model_providersdid not listmodel_provider:cloudflare, soruntime resolution rejected the pairing even though Workers AI reaches the
Codex runtime through the ordinary relay.
claude_sdkis deliberately notextended: Workers AI has no Anthropic route.
workflow_stages/research_loop/backend/agent.py,provider_env.py,stage.py) —_scoped_legacy_provider_envhad nocloudflarebranch, so the key and account id were scrubbed out of the peersubprocess environment and the relay started unauthenticated. The new branch
forwards
CLOUDFLARE_API_KEY(also mirrored toOPENAI_API_KEY, which iswhat the relay reads),
CLOUDFLARE_ACCOUNT_ID, andCLOUDFLARE_BASE_URL,and leaves the Anthropic surface unset.
freeze_provider_envand_provider_envgained matching entries so the variables are explicitlynulled for every other provider rather than leaking across a switch.
Scope
8dd890f(provider),b7dfc9e(codex_sdk allowlist),e904864(credential forwarding),e49c2a2(docs).praxist/core/cloudflare.py; newpraxist/plugins/model_providers/cloudflare/; touchedpraxist/cli/_env.py,praxist/cli/start.py,praxist/core/credentials.py,praxist/core/modeling.py,praxist/plugins/agent_runtimes/codex_sdk/(
_relay.py,adapter.py,plugin.yaml), andpraxist/plugins/workflow_stages/research_loop/(backend/agent.py,provider_env.py,stage.py).api_formatvalue(
cloudflare_workers_ai), one new provider ref (model_provider:cloudflare),three new environment variables.
_relay.pygains an exportedprovider_upstream()helper because Cloudflare's upstream cannot live in thestatic
_PROVIDER_UPSTREAMStable; existing table entries are unchanged.A missing
CLOUDFLARE_ACCOUNT_IDraises a namedValueErrorexplaining whichvariable to set, surfaced as a
RuntimeErrorfromstart_relay.gaps in shared provider plumbing that any OpenAI-compatible, account-scoped
provider would hit.
Verification
Run on macOS 27.0 (arm64), Praxist 0.5.0,
codex_sdkruntime via the relay:praxist doctor --target claude— clean.@cf/deepseek-ai/deepseek-v4-flash-0731and@cf/zai-org/glm-5.3-flash.mainbaseline(
aa198d0). Every remaining delta was reviewed; the one difference notexplained by the baseline is a timing flake, confirmed by re-running it.
ruffclean.Not verified, and deliberately called out:
this provider is unproven.
run continued, but the cause was not root-caused, so I would not claim the
streaming path is production-hardened on this route yet.
end-to-end against the live endpoint rather than in the test suite; happy to
add focused tests for
workers_ai_base_urlresolution order,_normalize_model_for_api_formaton@cf/...ids, and the_scoped_legacy_provider_envcloudflare branch if you want them in this PR.Checklist
end-to-end against the live endpoint, unit tests not yet written.
(
docs/guides/model-providers.md,docs/guides/agent-runtimes.md,docs/reference/plugins.md.)(No new dependencies; the provider reuses the existing OpenAI-compatible
client.)
.github/CONTRIBUTING.mdand the contribution terms inLICENSE.md.