OpenWOP openwop.dev
FieldValue
RFC0067
TitleProvider-catalog conventions — a stable provider-name vocabulary and a per-provider BYOK auth-mode enum (apiKey / oauth-pkce / oauth-device / none) so clients can pre-flight how a host expects a provider's credential to be supplied
StatusAccepted
Author(s)David Tufts (@davidscotttufts)
Created2026-05-26
Updated2026-06-01 (Active → Accepted) — graduated on the non-steward host MyndHyve advertising its real aiProviders catalog + the authModes map at the discovery doc root (rev workflow-runtime-00455-xus @ 100%, https://api.myndhyve.ai; steward-verified 2026-06-01). Like RFC 0088, the §B auth-mode contract is a pure function of the public /.well-known/openwop, so the steward independently re-derived it from the live doc (no host-reported numbers, no bearer key): aiProviders.supported = ["anthropic","openai","gemini","minimax"], byok = [same four], authModes = {anthropic:["apiKey"], openai:["apiKey"], gemini:["apiKey"], minimax:["apiKey"]} — and B.1 (every authModes key ∈ supported) ✓, B.2 (every apiKey provider ∈ byok) ✓, B.3 (no ["none"]-only provider in byok) ✓ all hold; B.4 does not engage (no oauth-* providers). MyndHyve's catalog is genuine — these are the providers its headless workflow-runtime AI execution path (@myndhyve/ai-providers via serverExecutionHost) actually routes to (confirmed against its structured-output switch + pricing table), superseding the prior empty supported:[] advertisement. The gated byok-auth-modes cross-field consistency leg passes non-vacuously (3 passed, no soft-skip — the authModes presence engages the gate) under OPENWOP_REQUIRE_BEHAVIOR=true vs @openwop/openwop-conformance@1.18.1 (the scenario shipped at Draft → Active — no steward prerequisite was owed). No regression: openwop-core-standard + openwop-agent-platform + auth.profiles still hold on the same host. · 2026-05-29 — promoted Draft → Active. The full wire surface already landed on main (the additive optional aiProviders.authModes map in capabilities.schema.json, the §B/§C prose in capabilities.md, and the always-on-shape + gated-cross-field byok-auth-modes.test.ts scenario). The one Active-gating Unresolved question (#3, local-endpoint addressing) is resolved below: host-config only. Comment window waived per GOVERNANCE.md lazy consensus. No host wire change; reference-host advertisement remains deferred per §Conformance.
Affectsschemas/capabilities.schema.json (aiProviders — additive optional authModes map + a non-normative convention list extending supported's description) · spec/v1/capabilities.mdaiProviders — auth-mode contract + provider-name vocabulary) · CHANGELOG.md · new conformance scenario byok-auth-modes.test.ts
Compatibilityadditive
Supersedes
Superseded by

Summary

openwop's capabilities.aiProviders advertises which AI providers a host can route to (supported) and which permit BYOK (byok), but it does not say _how_ a client is expected to supply a provider's credential. As hosts expand their catalog beyond the original handful (OpenRouter, Bedrock, Ollama, vLLM, …) the supply mechanism diverges — an API-key header, an OAuth PKCE flow, an OAuth device flow, or no credential at all (a local or platform-managed provider). This RFC adds an additive optional aiProviders.authModes map — { <providerId>: ("apiKey" | "oauth-pkce" | "oauth-device" | "none")[] } — so a client can pre-flight the credential UX without trial-and-error, and pins a non-normative provider-name vocabulary so independently-built hosts converge on the same ids. No existing required field changes; hosts that omit authModes keep today's "BYOK ⇒ ai.credentialRef" default.

Motivation

The feature-gap analysis (docs/OPENWOP-FEATURE-GAP-ANALYSIS.md row 3) calls for expanding the provider catalog from ~4 to ~20+ providers (OpenRouter, LiteLLM, Vercel/Cloudflare AI Gateway, Together, Bedrock, Qwen, Ollama, vLLM, HuggingFace, …). The schema already tolerates arbitrary provider ids in aiProviders.supported (string[], minLength: 1, vendor-prefixed extensions allowed), so the _catalog_ is not blocked. Two cross-host gaps remain:

1. Auth-mode opacity. capabilities.md §aiProviders says BYOK providers accept an opaque ai.credentialRef. That is correct for an API-key provider, but it says nothing about a provider whose credential is acquired via an OAuth flow (the host owns the grant per RFC 0047 host.oauth) or a provider that needs no credential at all (a local Ollama / vLLM endpoint, or one the host serves from platform-managed keys). A client building a "connect provider X" UX today has to special-case each provider out-of-band. The host already _knows_ the answer; it just can't advertise it. 2. Provider-id drift. Two hosts that both route to Google's Gemini might advertise gemini vs google vs vertex; OpenRouter as openrouter vs open-router. Without a recommended vocabulary, a portable client can't map a workflow's RunOptions.configurable.ai.provider across hosts.

The spec is the right place for the _advertisement shape_ and the _recommended vocabulary_ — these are interop concerns a client depends on. The per-provider OAuth flow mechanics stay host-owned (RFC 0047); this RFC only lets a host _declare which mode applies_, it does not normate a new wire flow.

Proposal

§A — capabilities.schema.json: additive optional aiProviders.authModes

   "aiProviders": {
     "type": "object",
     "properties": {
       "supported": { "...": "unchanged — string[] of provider ids" },
       "byok":      { "...": "unchanged — subset of supported permitting BYOK" },
+      "authModes": {
+        "type": "object",
+        "description": "RFC 0067. Optional per-provider advertisement of HOW the host expects a provider's credential to be supplied. Keys are provider ids appearing in `supported`; values are the auth modes the host honors for that provider. Absent ⇒ no advertisement: a provider in `byok` defaults to `apiKey` semantics (client passes `ai.credentialRef`); a provider in `supported` but not in `byok` defaults to `none` (platform-managed). This map only DESCRIBES the supply mechanism — the per-mode flow mechanics are owned elsewhere (`oauth-pkce`/`oauth-device` compose RFC 0047 `host.oauth`).",
+        "additionalProperties": {
+          "type": "array",
+          "minItems": 1,
+          "uniqueItems": true,
+          "items": {
+            "type": "string",
+            "enum": ["apiKey", "oauth-pkce", "oauth-device", "none"]
+          }
+        }
+      },
       "policies": { "...": "unchanged" },
       "maxInlineMediaBytes": { "...": "unchanged" }
     },
     "additionalProperties": false
   }

The four enum values:

ModeMeaningCredential supply
apiKeyHost accepts a stored API-key credential for this provider.Client passes RunOptions.configurable.ai.credentialRef (today's BYOK path). Provider MUST appear in byok.
oauth-pkceHost acquires a token via an OAuth 2.0 authorization-code + PKCE flow it owns (RFC 0047 host.oauth, grants: ["authorization_code"]).Client does NOT pass key material; it references the host-stored credential by ref per RFC 0046. The PKCE flow is host-driven.
oauth-deviceHost acquires a token via an OAuth 2.0 device-authorization flow it owns.Same as oauth-pkce — credential referenced, never wired.
noneProvider needs no caller-supplied credential.A local provider (Ollama / vLLM at a host-configured endpoint) or one served entirely from platform-managed keys. Client supplies neither credentialRef nor key material. Provider MUST NOT appear in byok.

§B — auth-mode contract (normative)

When a host advertises aiProviders.authModes:

1. Every key in authModes MUST also appear in aiProviders.supported. A host MUST NOT advertise an auth mode for a provider it cannot route to. 2. A provider whose mode array includes apiKey MUST also appear in aiProviders.byok (the two advertisements MUST agree — apiKey _is_ the BYOK path). 3. A provider whose mode array is exactly ["none"] MUST NOT appear in aiProviders.byok (none means no caller credential; a byok listing would contradict it). A provider MAY advertise ["apiKey", "none"] (BYOK permitted but a platform-managed fallback exists) — such a provider MUST appear in byok. 4. A provider advertising oauth-pkce or oauth-device SHOULD also advertise capabilities.oauth (RFC 0047) with a matching provider id; the OAuth flow itself is governed by RFC 0047, not this RFC. The credential is referenced by ref (RFC 0046 credential-reference), never passed as key material on ai.credentialRef. 5. Absent authModes, the default contract is unchanged: a provider in byok behaves as apiKey; a provider in supported but not in byok behaves as none. Clients MUST tolerate the field's absence and fall back to this default. 6. authModes is an advertisement of _capability_, not a per-run _requirement_. Per-provider policy enforcement (must-use-BYOK, model allowlists) remains aiProviders.policies (unchanged). A client MUST NOT infer a policy mode from an auth mode.

A client that wants to connect a provider reads authModes[providerId] to choose the UX: render an API-key field for apiKey, launch the host's OAuth flow for oauth-pkce/oauth-device, or show "ready to use" for none.

§C — provider-name vocabulary (non-normative convention)

aiProviders.supported stays an open string[]. To reduce cross-host id drift, hosts SHOULD use the following recommended ids when routing to a known provider (this list extends, and does not replace, the existing description's conventional ids). The list is advisory — a host MAY advertise any vendor-prefixed extension id, and clients MUST tolerate unknown ids.

Recommended idProvider
anthropicAnthropic
openaiOpenAI
geminiGoogle Gemini (direct API)
vertexGoogle Vertex AI
bedrockAWS Bedrock
mistralMistral
cohereCohere
openrouterOpenRouter (aggregator)
litellmLiteLLM proxy
togetherTogether AI
huggingfaceHugging Face Inference
qwenAlibaba Qwen
ollamaOllama (local)
vllmvLLM (local/self-hosted)

Aggregators (openrouter, litellm) and gateways front many upstream models; the RunOptions.configurable.ai.model string disambiguates the upstream model and is host-interpreted. This RFC does not normate a model-id grammar.

Examples

Positive. A host routing to a mix of API-key, OAuth, and local providers:

"aiProviders": {
  "supported": ["anthropic", "openai", "vertex", "ollama"],
  "byok": ["anthropic", "openai"],
  "authModes": {
    "anthropic": ["apiKey"],
    "openai": ["apiKey"],
    "vertex": ["oauth-pkce"],
    "ollama": ["none"]
  }
}

A client sees anthropic/openai → API-key field; vertex → launch the host's OAuth PKCE flow (and finds capabilities.oauth.providers[].id == "vertex"); ollama → ready to use.

Negative (fails validation / non-conformant).

"aiProviders": {
  "supported": ["anthropic"],
  "byok": [],
  "authModes": { "anthropic": ["apiKey"] }
}

Schema-valid in isolation, but non-conformant by §B rule 2: apiKey for anthropic requires anthropic to appear in byok. A schema-level negative example: "authModes": { "anthropic": [] } fails validation (minItems: 1), and "authModes": { "anthropic": ["device"] } fails validation (device not in the enum — the canonical value is oauth-device).

Compatibility

Additive. aiProviders.authModes is a new optional field on an existing optional block; aiProviders keeps additionalProperties: false but gains the property in its properties map. No existing field's type, optionality, or meaning changes. The default contract for hosts that omit authModes is exactly today's behavior (§B rule 5). Existing clients ignore the field; existing hosts don't emit it. No conformance pass is invalidated. No new error code, event type, or endpoint. The provider-name vocabulary (§C) is non-normative — it imposes no MUST on any host.

Forward-compatibility clauses:

  • New field optional with no default emitted by old hosts; consumers MUST tolerate absence and apply the §B rule-5 default.
  • The enum is closed at four values; a future mode requires an additive RFC (the array's items enum can grow additively since unknown enum values would only ever be _emitted_ by a newer host and a uniqueItems array tolerates a consumer that doesn't recognize a value — consumers MUST ignore an auth mode they don't recognize rather than reject the discovery doc).

Conformance

  • Existing coverage. conformance/src/scenarios/byok-roundtrip.test.ts (BYOK credential resolution + redaction) and policies.test.ts (per-provider policy modes) cover the adjacent surface. discovery.test.ts validates the capabilities document shape. None assert auth-mode advertisement.
  • New scenario — byok-auth-modes.test.ts (always-on shape + capability-gated cross-checks):

- Shape (always-on): when a host advertises aiProviders.authModes, the map validates against the schema — every value is a non-empty unique array of the four enum values. - Cross-field consistency (gated on aiProviders.authModes present): every authModes key appears in supported (§B rule 1); every provider with apiKey appears in byok (§B rule 2); every provider whose modes are exactly ["none"] is absent from byok (§B rule 3). - OAuth alignment (gated on authModes + capabilities.oauth.supported): every provider advertising oauth-pkce/oauth-device has a matching capabilities.oauth.providers[].id (§B rule 4 SHOULD — asserted as a soft check that reports rather than fails when oauth block absent). - Hosts that omit authModes skip cleanly (the scenario is gated on the field's presence in the discovery doc).

  • Capability gating. Gated on the presence of capabilities.aiProviders.authModes in the discovery response. No new top-level capability flag — the field's presence _is_ the gate.
  • Reference host. Deferred. The in-memory reference host MAY advertise authModes in a follow-up; this RFC ships the shape + always-on schema validation. Per the RFC 0027 §G staging precedent, the cross-field behavioral assertions soft-skip until a host advertises the field.

Alternatives considered

1. A new top-level providers capability block with a rich per-provider object ({ id, displayName, authModes, models, … }). Rejected for Draft — it duplicates aiProviders.supported and capabilities.oauth.providers, creating three places that list providers. Extending aiProviders keeps the catalog in one block. (Revisitable if model-catalog advertisement is later demanded.) 2. A generic oauth mode (no pkce/device split). Rejected — a client building the connect UX needs to know whether to open a browser redirect (PKCE) or display a device code; collapsing them re-introduces the trial-and-error this RFC removes. The two map cleanly onto RFC 0047's grant vocabulary. 3. Do nothing. Rejected — the catalog can grow today (open string[]), but every new OAuth or local provider forces clients into per-provider out-of-band special-casing. The cost of doing nothing rises linearly with catalog size; the gap analysis explicitly targets a 5× catalog expansion.

Unresolved questions

1. Model-catalog advertisement. Should a host be able to advertise which _models_ a provider exposes (so a client can populate a model picker without a separate call)? Out of scope here; would compose with aiProviders if demanded. Decide before Accepted. 2. Aggregator semantics. For openrouter/litellm, the ai.model string addresses an upstream model whose _own_ provider may have a different auth mode. This RFC treats the aggregator as one provider with one auth mode (its own gateway key). Is that sufficient, or do aggregators need a nested advertisement? Likely sufficient for v1.x; confirm with an implementer. 3. none + local-endpoint addressing.RESOLVED (2026-05-29, Active gate): host-config only — the discovery document does NOT surface a provider endpoint URL. A none provider's endpoint (an Ollama/vLLM base URL) is host-internal deployment configuration, not interop surface. A portable client addresses a provider via RunOptions.configurable.ai.provider / ai.model (host-interpreted), never the URL, so surfacing a base URL would add a field with no client-portability benefit while leaking deployment topology — an SSRF-adjacent reconnaissance signal that contradicts the discovery document's posture of advertising _capability shape_, not deployment internals. This keeps authModes purely a credential-supply advertisement and adds no endpoint field. (If endpoint observability is later demanded it composes additively with capabilities.observability, not aiProviders.) UQ #1 (model-catalog advertisement) and #2 (aggregator nested semantics) remain open but are Accepted-gated / implementer-confirmation items, not Active blockers.

Implementation notes (non-normative)

  • The reference workflow-engine backend currently dispatches four providers (anthropic, openai, google, minimax). Advertising authModes is a one-line addition to its discovery handler once it decides each provider's supply mechanism. No dispatcher change is required to _advertise_ — the field describes existing behavior.
  • The CLI (cli/) can read authModes to drive openwop providers add UX (API-key prompt vs OAuth launch vs "no credential needed"), per the gap analysis CLI track. That is implementation-only and out of this RFC's scope.

Acceptance criteria

  • [x] capabilities.md §aiProviders documents authModes + the auth-mode contract (§B) + the provider-name vocabulary (§C).
  • [x] capabilities.schema.json carries the additive optional aiProviders.authModes map.
  • [x] byok-auth-modes.test.ts covers the shape (always-on) + cross-field consistency (gated on the field's presence).
  • [x] CHANGELOG entry under [1.1.6 — unreleased].
  • [x] Reference host implements + advertises authModes, OR this RFC explicitly defers reference-host advertisement (it does — shape + always-on validation ship; cross-field assertions soft-skip until a host advertises).

References