Skip to content

Add built-in Posit Connect provider - #81

Merged
wch merged 10 commits into
mainfrom
connect-provider
Aug 26, 2026
Merged

Add built-in Posit Connect provider#81
wch merged 10 commits into
mainfrom
connect-provider

Conversation

@ssinnott

@ssinnott ssinnott commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Make Posit Connect a built-in provider: one provider fronts every OAuth integration on a Connect server, folding their models into a single namespaced list instead of runtime-injected custom-provider entries.

  • Anthropic integrations discover their models live through the gateway; the Bedrock gateway can't list models, so those come from a declared table in ai-config.
  • Bedrock chats mint short-lived AWS credentials per request through a host callback; the bridge never stores them.
  • The server URL comes from providers.json or POSIT_CONNECT_URL; without one the provider lists no models.
  • BedrockClient now honors a per-request base URL, letting chats target the Connect gateway.
  • The real change is connect-provider.ts; the regenerated JSON schema and lockfile are churn.

Testing

New tests cover integration shaping (template allowlist, Viewer-auth filtering, prefix minting with collisions), raw discovery requests against both gateways, per-integration failure isolation, chat dispatch on both protocols including credential-mint failures, and the capability table's delegation and fallback. Bedrock's base-URL honoring gets its own routing tests.

Commits

  • 0d02975 - Honor baseUrl on Bedrock's Anthropic and Converse routes
  • df92ae1 - Add built-in Posit Connect provider

🤖 Generated with Claude Code

Only the Mantle route respected an explicit baseUrl; the other two
hardcoded the resolved AWS runtime endpoint, so a gateway-routed
caller (e.g. Connect's Bedrock gateway) fell through to AWS directly.
Convert Connect OAuth integrations from runtime-injected custom
providers into a first-class built-in connect provider.

ai-config: add "connect" to the provider/client-kind vocabulary,
map POSIT_CONNECT_GATEWAY_URL to providers.connect.baseUrl, and add
the Connect Bedrock model table with capability inference that
borrows limits from the Anthropic-on-Bedrock table.

ai-provider-bridge: add connect-provider.ts with integration
discovery against /__api__/v1/oauth/integrations, template shaping
(anthropic live-discovered via the gateway, aws declared from the
table with Viewer-auth filtering), model ids namespaced as
connect-<slug>/<modelId>, and a protocol-dispatching client that
routes anthropic-messages through AnthropicClient with the federated
token and bedrock-converse through BedrockClient with per-request
STS credentials minted via ConnectProviderCallbacks.
@ssinnott
ssinnott requested a review from wch August 24, 2026 18:17
Parse the discovery-stamped gateway baseUrl for server, template, and
guid so stamped models never depend on discovery-time cache state; the
integration cache is now replaced wholesale per discovery and consulted
only for unstamped override models on the same server. The template
selects the transport, so a protocol override can pick the wire format
but never re-route off the integration's gateway. Bedrock chats tie the
credential mint to chat cancellation, require complete minted keys
(no ambient-chain fallback), sign with the integration's sts_region,
and forward provider customHeaders. AWS-backed integrations are skipped
with a warning when no credential callback exists, and ids whose first
segment is not a connect- prefix (raw ARNs) pass through unsplit.
@wch

wch commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

I have some questions about endpoints:

  • How does it handle, say, OpenAI as a provider? Does it use the Responses protocol there?
  • Similarly, for Bedrock, there is a relatively new Mantle endpoint which is used for the OpenAI models. Is this supported?
  • I asked PA about the code, and it says that if Posit Assistant were to use Anthropic models via Bedrock via this Connect provider, it would use the Bedrock Converse protocol. But we would want to use the Anthropic Messages protocol, because there are some features (like cache markers) that may get lost in translation.
  • Bedrock has an endpoint for listing models. We use it for the regular Bedrock provider. Why not use it here?
  • How are other providers handled?

In general, it's best to speak a model's native protocol whenever possible.

Some feedback from running the /assistant-review skill from the Assistant repository, where packages/ai-lib is a submodule (using gpt 5.6 sol high):

  • critical (security) - packages/ai-provider-bridge/src/providers/connect-provider.ts:395: Connect's integrations endpoint is scoped to the calling user, but createCachedModelFetcher caches one model list per registered provider, independent of credentials, and the adjacent integration map is shared the same way. After token A populates the cache, token B on the same registry receives A's integration names, GUID-backed routes, and models for up to an hour without making its own discovery request; scope both caches by credential identity and Connect URL, or avoid the shared provider cache for this user-specific catalog, and add a two-token regression test.

  • critical (compliance) - packages/ai-provider-bridge/src/model-clients/BedrockClient.ts:259: when AWS FIPS mode is enabled, an explicit baseUrl now wins over the resolved FIPS runtime endpoint and the request proceeds after only a warning. This breaks the existing fail-closed transport policy (Mantle is rejected under FIPS for the same reason) and lets both Connect and ordinary Bedrock model overrides send traffic to an endpoint with no FIPS guarantee; reject the override unless a separate explicit policy can attest that the custom endpoint is permitted.

  • important (correctness) - packages/ai-provider-bridge/src/providers/connect-provider.ts:165: model identity is derived from mutable display text and one-pass collision order. Reversing two same-name integration records transfers the unsuffixed connect-<slug> ID to the other GUID, so a persisted (providerId, modelId) selection can silently route to a different integration after refresh; use the immutable full GUID as the namespace and keep the human name only in ModelInfo.name.

  • important (correctness) - packages/ai-provider-bridge/src/providers/connect-provider.ts:514: the documented cache fallback for user-configured, unstamped models is unreachable through the normal catalog pipeline. resolveModels assigns the provider-wide Connect root URL as resolvedBaseUrl, so chat enters this params.baseUrl branch and rejects that root as "not a Connect gateway URL" instead of consulting byPrefix; treat the configured root as unstamped (or store it outside the generic chat-endpoint field) and cover the path through resolveModels, not only a direct client call with baseUrl omitted.

  • important (correctness) - packages/ai-provider-bridge/src/model-clients/BedrockClient.ts:247: customHeaders is passed raw to all three Bedrock SDK factories, violating ApiKeyCredentials' contract that SDK-managed names such as Authorization, x-api-key, and Content-Type are ignored. Every other direct SDK client filters through safeSdkCustomHeaders; do the same once here and add a reserved-header assertion so custom Connect metadata cannot interfere with SigV4 or provider headers.

ssinnott and others added 4 commits August 24, 2026 19:48
Bedrock's client passed customHeaders straight to the SDK factories
unlike every other direct-SDK client, risking a caller-supplied header
clobbering an SDK-managed one; route it through safeSdkCustomHeaders
like the rest. Also stop silently warning-and-proceeding when an
explicit baseUrl overrides a FIPS runtime endpoint - reject it by
default, and require callers that route through a trusted gateway
(Connect) to opt in via allowBaseUrlUnderFips.
The shared model-list cache keyed only on providerId, so a registry
called with different credentials over time (e.g. distinct user
sessions against the same provider) would silently share one cached
model list across them. Add an optional cacheKey so a provider whose
credentials materially change the fetched list can partition the
cache; omitting it keeps prior single-entry behavior.
The gateway-guid cache used to route unstamped model IDs and enrich
stamped ones was keyed only by Connect server URL, so two sessions
against the same server (different API keys) could resolve each
other's integrations. Gate every read against a credential-derived
key set when the cache was populated.

Also fix baseUrl selection for unstamped models: ai-config's resolver
can fall back to the bare Connect root as a model's baseUrl when no
gateway URL was discovered, which `??` treated as a real override and
preferred over the cached integration's gateway URL. Distinguish a
genuine discovery-stamped gateway URL from that meaningless fallback
before deciding which one wins.

Simplify prefix minting to always embed the integration's guid, which
makes prefixes collision-free by construction and removes the
order-dependent takenId fallback. Declare Anthropic-model Bedrock
gateway routes as anthropic-messages instead of bedrock-converse, and
pass allowBaseUrlUnderFips to BedrockClient since Connect's gateway
redirect is a trusted, admin-configured route.
Keep Connect routing state scoped and bounded with the model cache, use opaque credential fingerprints, and cover partitioning and interleaved sessions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ssinnott

Copy link
Copy Markdown
Contributor Author

Ok chatted with the connect team. The this feature is preview. So the support is a little limited right now.

  1. Only anthropic and bedrock support today.
  2. There's no mantle support today - and bedrock only supports some of the urls today.

@wch
wch merged commit ee7dc5b into main Aug 26, 2026
4 checks passed
@wch
wch deleted the connect-provider branch August 26, 2026 01:22
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.

2 participants