CredentialEndpointResolver

Says where a user's key should be sent, for the providers this application knows about.

Application API, not SPI: you implement it and register it as a bean, and the platform calls it. The extension point for BYOK against a provider the framework does not ship. Register as many as you like: the first non-null answer wins, in org.springframework.core.Ordered order, so a resolver answers for the providers it knows and returns null for the rest. What reads them is the embabel-agent-starter-byok machinery that builds services from user keys, so a deployment without that starter can register these and never be asked.

What these beans say is the first word, and the endpoints embabel-agent-starter-byok knows for Anthropic, OpenAI, DeepSeek, Mistral, Gemini and Atlas Cloud are the last: answering for one of those overrides it, for a proxy or a custom base URL, with no @Order needed. The shipped endpoints are not beans, so they cannot tie with yours - @Order decides only which of your resolvers is asked first, and ties between them fall back to bean registration order.

@Bean
fun ourGatewayEndpoint() = CredentialEndpointResolver { credential, _ ->
if (!credential.provider.equals("OurGateway", ignoreCase = true)) null
else CredentialEndpoint.OpenAiCompatible(provider = "OurGateway", baseUrl = GATEWAY_URL)
}

Return null rather than an endpoint for a provider you do not handle: a resolver that answers for everything would point someone else's key at your gateway.

The platform caches the service it builds, per (provider, key, model), so a resolver is consulted on a cache miss rather than on every call - though possibly more than once within one, as each wire protocol's builder gets its turn. Implementations must be thread-safe, and should be pure and cheap for the same reason.

That cache is keyed on (provider, key, model) and not on what you return here, so a resolver that answers differently for the same three - per tenant, say, read from some ambient context - will have its first answer serve every later caller sharing them.

Functions

Link copied to clipboard
abstract fun resolve(credential: ProviderCredential, model: String): CredentialEndpoint?