Provider adapters
crates/providers implements the outbound LLM adapters behind engine::AiPort, with transport through Rig 0.39 (rig-core) in rig_adapter/. Orchestration resolves settings into provider configuration and calls create_provider(); it never depends on concrete provider clients.
For keys, OAuth, and readiness controls, follow Provider setup.
Request routing
Workflow run (engine)
→ AgentRequest (prompt, tools, provider id, model id)
→ AiPort (engine port)
→ orchestration routes to the effective provider
→ providers::create_provider(...) for each referenced provider
→ mapping/ (transcript + tool wire shape)
→ rig_adapter/ (Rig HTTP + streaming to the vendor API)- The engine owns run logic and sees only
AiPort, one interface for model turns, streaming, and tool-call results. - Orchestration loads
settings.json, merges secrets, resolves every provider the workflow references, validates all their credentials before execution, and builds a run-scoped router over those clients. It imports only factory and config types fromproviders, never Rig or vendor SDKs. crates/providersowns transport. Rig does most outbound HTTP and SSE streaming, and OpenFlow code around it handles:AgentRequest↔ payload conversion (mapping/,rig_adapter/convert.rs).- API keys, Anthropic headers, OpenAI-compatible base URLs (
auth.rs, profile fields). - ChatGPT Codex OAuth, refresh, and endpoints (
codex_oauth/,codex.rs). - Bedrock credentials and Converse calls when the desktop build enables the
bedrockfeature (aws_runtime.rs).
A provider failure stays attached to its node. OpenFlow never retries through a different provider.
Wire protocols
mapping/ builds Rig CompletionRequest values from AgentRequest; workflow code never calls OpenAI or Anthropic SDKs. create_provider() picks an adapter family from the active builtin profile (built-in profiles):
| Profile family | ProviderAdapterConfig | Rig model (simplified) | HTTP shape |
|---|---|---|---|
| Anthropic | Anthropic | rig_core::providers::anthropic completion model | Anthropic Messages API (/v1/messages), x-api-key + Anthropic version headers |
| OpenAI and most hosted vendors | OpenAiCompatible | rig_core::providers::openai | Responses API by default for OpenAI; Chat Completions for OpenRouter, Groq, Gemini compat, locals, etc. |
| Per-model override on a compat profile | OpenAiCompatible + model_transports | Same Rig Anthropic client against the profile base URL | Anthropic Messages on a non-Anthropic host (gateway or proxy) |
| ChatGPT (Codex) | OpenAiCodex (separate CodexClient) | Rig ChatGPT responses + OpenFlow OAuth | Private Codex backend (not api.openai.com API-key traffic) |
| Amazon Bedrock | Bedrock | rig-bedrock completion model | AWS Bedrock Converse (not Anthropic Messages on api.anthropic.com) |
At invoke time, rig_adapter builds one RigModel enum arm (Anthropic, OpenAiChat, OpenAiResponses, ChatGPT, or Bedrock), runs the turn, and maps the result to AgentTurnOutcome. The engine sees the same AiPort whichever arm ran.
Switching Anthropic and OpenAI in Settings changes wire protocol and auth, not workflow JSON. OpenAI and Groq share the OpenAI-compatible adapter type; spec.rs gives each its base URL, key environment variable, and default transport. Managed OpenAI defaults to Responses; most other compat profiles default to Chat Completions.
Supported adapter families
| Adapter family | Implementation | Role |
|---|---|---|
| OpenAI-compatible (via Rig) | rig_adapter/model.rs + convert.rs | Chat completions / Responses wire shape, tools, streaming |
| ChatGPT (Codex) OAuth + Rig | codex_oauth/, codex.rs, rig_adapter/model.rs, and rig_adapter/codex_http.rs | Browser/device login, refreshable credentials, ChatGPT Codex Responses streaming |
| Anthropic Messages (via Rig) | rig_adapter/ (+ anthropic_http.rs, claude_thinking.rs) | Anthropic-native mapping, thinking blocks, prompt cache |
| Amazon Bedrock Converse (via Rig) | rig_adapter/model.rs + aws_runtime.rs | AWS Bedrock transport and credential resolution (bedrock Cargo feature; off by default on providers/orchestration, on via desktop) |
| Shared mapping | crates/providers/src/mapping/ | Transcript conversion, tool argument parsing, jsonrepair-rs recovery |
| Factory | crates/providers/src/lib.rs | create_provider() returns Box<dyn AiPort> |
| Client entry | crates/providers/src/client.rs | AiClient: AiPort, config enums, model cache |
- Buffered and streaming
AiClientcalls share dispatch inrig_adapter/mod.rs; model construction selects the transport. - The per-client cache reuses models and rebuilds Bedrock models before credential expiry.
- Each invocation picks the model and builds its payload from the current
AgentRequest, including transcript and reasoning/fast-mode settings. - Codex keeps its own refreshable client but shares the underlying Rig model invocation methods.
- Inference and HTTP model discovery share key/header validation in
auth.rs. Discovery keeps its shorter total timeout. - Tool schemas use Rig's tool definition directly inside providers. Engine contracts stay independent of Rig.
Provider prompt caching
Prompt caching reuses exact prompt prefixes. It is separate from OpenFlow's validated tool-result cache.
| Provider path | OpenFlow behavior |
|---|---|
| OpenAI API, Responses | Stable prompt_cache_key per workflow node. GPT-5.6 models add prompt_cache_options.mode: "explicit" and a breakpoint on the final stable system block, so changing user/tool history triggers no billable cache write. |
| OpenAI API, Chat Completions | Same per-node prompt_cache_key; the provider picks the automatic breakpoint. |
| Anthropic Messages | Automatic moving-history caching plus explicit stable system and tool-definition markers, with Anthropic's default five-minute TTL. |
| Anthropic-compatible model override | Same Messages policy. Backend support is part of that endpoint's compatibility contract. |
| Amazon Bedrock Converse | Rig prompt caching only for the Claude and Nova model-ID allowlist in rig_adapter/model.rs; other models get no cache points. |
| Other OpenAI-compatible endpoints | prompt_cache_key, except Ollama and LM Studio. Acceptance, discounts, and lifetime are provider-defined. |
| ChatGPT (Codex) OAuth | No OpenFlow-managed cache controls; the private backend contract does not document them. |
- Cache reads map to
UsageReport.cached_input_tokens, writes toUsageReport.cache_creation_input_tokens. Orchestration projects the latest values into each node'sContextWindowSnapshotascachedInputTokensandcacheCreationInputTokens. - OpenAI Responses needs a final-wire shim: Rig 0.39 does not yet model
prompt_cache_key,prompt_cache_options, orcache_write_tokens. - Caching starts only above the provider's minimum prompt size. Cache keys improve routing; they do not memoize responses or change output semantics.
- Put stable system context and tool definitions first, run-specific user input and tool results later.
Per-model transport routing
Custom OpenAI-compatible profiles have a default Responses or Chat Completions transport. A model overrides it through ProviderProfile.model_transports (responses, chat_completions, or anthropic_messages), set per model in Settings → Providers as Responses API, Chat Completions API, or Anthropic Messages API on editable or custom profiles. Settings stores model IDs as data; provider code never infers transport from vendor names or model allowlists.
An anthropic_messages override uses the Rig Anthropic client against the profile base URL: standard /v1/messages request, Anthropic auth/version headers, tool schema, and response mapping. Removing the override restores the profile default.
OpenAI Responses and ChatGPT (Codex) preserve MCP schemas with map-like object parameters. Rig 0.39 marks every Responses function tool strict, but OpenAI strict mode rejects objects that allow additional properties. The final-wire adapters therefore set strict: false on affected tools only, without closing or rewriting the MCP server's schema. Closed schemas stay strict.
An adapter that must invoke without streaming keeps typed reasoning on the turn outcome. Its fallback stream emits displayable text and summary blocks before assistant text, never encrypted or redacted reasoning payloads.
Fast mode
Fast mode is an independent request setting; it does not select or rewrite reasoning effort.
- OpenAI API and ChatGPT (Codex) map the saved
fastModeboolean toservice_tier: "priority". Other providers lack this control. - The ChatGPT UI name Fast and Codex config value
fastare not wire-level service tiers. - Rig's ChatGPT adapter clears
service_tierwhile building the request.CodexHttpClientrestores the selected priority tier at the final HTTP boundary, keeping Rig's request and stream mapping.
Stream cutoff recovery
Rig streams tool-call names and argument deltas before the final response; OpenFlow accumulates them by Rig's internal call ID. If a Responses stream ends with response.incomplete: max_output_tokens, the provider returns AgentError::OutputTruncated with redacted partial-call metadata, rather than dropping the in-flight call or reporting a generic provider failure. Raw partial arguments stay in memory, omitted from Debug, Display, telemetry, and chat projection.
- The engine executes no call from the incomplete response. It retries the node up to three times, instructing the model to write a small valid skeleton and expand it through focused edits.
- Claude 4 models recognized by Rig keep Rig's larger model-specific output limit. Other Anthropic-compatible models get a 16K minimum fallback.
- Successful edit-tool writes use a sibling temporary file plus atomic rename, so cancellation never exposes a partially written destination.
Deterministic recovery and overseer repair
Providers own wire decoding and local jsonrepair-rs recovery. Semantic acceptance of openflow_submit_node_output lives in the engine completion protocol (execution/completion_protocol.rs).
When deterministic recovery cannot satisfy the node output schema, providers attach a redacted OutputRepairCandidate to AgentError::MalformedSubmitOutput. Orchestration wraps the run provider once in RepairingAiPort (before AiInvocationAdapter), so nodes and subagents share one overseer pass on the same provider.
Malformed wire-argument markers keep at most 64 KiB of raw UTF-8 text, ending on a character boundary. The engine caps the repair candidate independently.
WorkflowSettings.output_repair_model (outputRepairModel) optionally overrides the overseer model. Blank or absent inherits the failed worker request's model; nonblank uses that model string. Credentials stay on the run provider. Overseer output repair covers the sequence, guards, and deferred scope.
ChatGPT (Codex) OAuth
ChatGPT (Codex) authenticates with a ChatGPT account, not an OpenAI API key. OpenFlow owns login, token refresh, persistence, and one-shot unauthorized recovery. Rig's ChatGPT provider supplies the Codex Responses request and SSE mapping.
| Concern | Behavior |
|---|---|
| Browser login | PKCE S256 on http://localhost:1455/auth/callback, loopback-only listener, exact state validation |
| Port conflict | Device-code flow, only when port 1455 cannot be bound |
| Refresh | Within five minutes of expiry; concurrent refreshes serialized; rotated credentials persisted; then one unauthorized request retried |
| Cancellation | Drops the whole pending login attempt (stalled callback reads, device requests, token exchange) and releases the callback listener |
| Inference | SSE POST /backend-api/codex/responses through Rig with originator: openflow and OpenAI-Beta: responses=experimental; no WebSocket |
| Storage | Access, refresh, ID token, expiry, and account metadata in plaintext in ProviderProfile.codex_oauth inside settings.json |
| UI boundary | Settings IPC returns only tagged login state, safe email, and device instructions; never tokens or account ID |
OpenFlow implements the OAuth protocol in Rust. Its endpoint, PKCE, token-body, and request-shape details derive from oh-my-pi's MIT-licensed implementation and are pinned by fixture tests. Unlike the Codex CLI, OpenFlow intentionally falls back to device authorization, not a second callback port, when port 1455 is occupied.
Compatibility boundary
chatgpt.com/backend-api/codex and the reused Codex OAuth client ID are private, unsupported contracts. They can change independently of the public OpenAI API, and ChatGPT workspace policy may reject third-party clients or originator: openflow. Fixture tests prove the current request contract; only an interactive account smoke proves entitlement and live backend acceptance.
Never impersonate a first-party originator. If the backend rejects OpenFlow, surface the error, verify current official Codex behavior, then update the documented adapter contract.
Amazon Bedrock
Bedrock uses AWS credentials and region settings, not an API-key header.
| Concern | Source |
|---|---|
| Auth | AWS credential chain (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, SSO, shared config, instance role). Optional profile name in Settings (ProviderProfile.aws_profile) or AWS_PROFILE. |
| Region | Settings AWS region (ProviderProfile.aws_region, default us-east-1) or AWS_REGION. A Bedrock region in older settings' ProviderProfile.base_url is migrated on load. |
| Inference | Bedrock Converse API through Rig / aws-sdk-bedrockruntime, not native Anthropic Messages on bedrock-mantle. |
| Model refresh | Settings Refresh from AWS calls ListFoundationModels and keeps active text-capable on-demand models. |
| Minimum IAM | bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream, bedrock:ListFoundationModels. |
Bedrock with SSO
- In Settings → Bedrock, set AWS profile to the profile name from
~/.aws/config(e.g.bedrock). - Set AWS region to the region where your models are enabled (not necessarily
us-east-1). - Run
aws sso login --profile <name>before starting a run. SSO tokens expire (typically 8 to 12 hours); log in again when credentials fail. - On macOS, Dock-launched apps do not inherit
AWS_PROFILEfrom~/.zshrc. Enter the profile in Settings, or launch OpenFlow from a terminal where it is set (./scripts/start.sh). - Select Test AWS connection (Settings → Bedrock) to confirm the app loads credentials through the same chain runs use.
- Verify:
aws sts get-caller-identity --profile <name>andaws bedrock list-foundation-models --profile <name> --region <region>.
GUI apps and HOME
The AWS Rust SDK finds ~/.aws/config, ~/.aws/credentials, and the SSO token cache through HOME, which GUI launches often omit even when a terminal works. If HOME is missing, OpenFlow sets it from the OS user directory so shared AWS config files are discoverable in-process.
Manual smoke
Run the live workflow smoke only when intentionally checking a real provider:
export AWS_REGION=us-east-1
export STEP_WORKFLOW_LIVE_AI=1
export STEP_WORKFLOW_LIVE_MODEL=anthropic.claude-sonnet-4-20250514-v1:0
cargo nextest run -p orchestration --test live_workflow --run-ignored ignored-only --no-captureTesting workflows defines the live-AI checks and their limits.
