Translates between an AI Gateway entity-model YAML configuration and a
Kong Gateway decK declarative configuration (_format_version: "3.0"), in
either direction.
The AI Gateway model is a higher-level abstraction; its entities are realized in
Kong via Gateway entities plus the AI plugins (ai-proxy-advanced,
ai-mcp-proxy, ai-a2a-proxy). This tool performs that lowering so you can
author at the AI-Gateway level and deploy with decK — and the reverse lifting,
so an existing decK config (including hand-written ones) can be recovered into
the AI Gateway entity model.
make build# AI Gateway -> Kong decK (direction auto-detected from the input)
./ai-deck-converter input.yaml
# Kong decK -> AI Gateway (auto-detected: decK docs carry _format_version)
./ai-deck-converter kong.yaml
# write to a file
./ai-deck-converter -o kong.yaml input.yaml
# read from stdin
cat input.yaml | ./ai-deck-converter -
# force a direction
./ai-deck-converter -direction from-deck kong.yaml
# emit Koko-style db-less output
./ai-deck-converter -direction to-dbless input.yaml| Flag | Default | Description |
|---|---|---|
-o |
stdout | Output file path. |
-direction |
auto |
Conversion direction: auto, to-deck (AI Gateway → decK), to-dbless (AI Gateway → db-less), or from-deck (decK → AI Gateway). Auto-detection keys off _format_version, which only decK documents carry. |
-strict |
false |
Treat unresolved references and unconvertible entities as errors instead of warnings. |
-label-tag-prefix |
"" |
Prefix for label-derived tags, e.g. aigw/ (prepended when converting to decK, stripped when reverting). |
-model-selector-sources |
true |
Target the ai-model-selector config.sources schema (Kong/kong-ee#20858), merging models with different selector shapes onto one shared route instead of one route per shape. Only for data planes new enough to support config.sources — they don't accept the legacy config.source it replaces. Set to false to keep targeting the legacy schema for data planes that don't support config.sources yet. |
Warnings (unresolved references, unsupported features, placeholders, dropped
entities) are printed to stderr; the converted config still goes to stdout/-o.
import (
"github.com/Kong/ai-deck-converter/convert"
"github.com/Kong/ai-deck-converter/revert"
)
// AI Gateway -> Kong decK
out, warnings, err := convert.Convert(srcYAML, convert.Options{})
// Kong decK -> AI Gateway
out, warnings, err = revert.Revert(deckYAML, revert.Options{})convert.ConvertDocument / revert.RevertDocument are also available if you
already hold a parsed aigw.Document / kong.Document.
A single YAML document grouping AI Gateway entities by kind. Credentials are nested under their consumer.
models: [ ... ] # -> routes (per capability/format) + ai-proxy-advanced + ai-models
model_providers: [ ... ] # folded into ai-proxy-advanced targets (not standalone)
mcp_servers: [ ... ] # -> Service + Route + ai-mcp-proxy
agents: [ ... ] # -> Service + Route (+ ai-a2a-proxy when type: a2a)
policies: [ ... ] # -> Kong plugins (global, or scoped per reference)
auth_strategies: [ ... ] # -> key-auth / openid-connect plugins on guarded routes
consumers: [ ... ] # -> consumers (+ nested keyauth_credentials, groups)
consumer_groups: [ ... ] # -> consumer_groups
vaults: [ ... ] # -> vaults
ca_certificates: [ ... ] # -> ca_certificatesA Model's config.route.paths[0] provides the base path (e.g. /ai); the
full route paths are derived per capability/format from the endpoint table.
identity_providers is the former name of auth_strategies, both as the
top-level key and as the access reference list on models, agents, and MCP
servers. It is deprecated but still accepted on input — entries under both
spellings are merged, current key first — and the reverse direction only ever
emits auth_strategies.
See convert/testdata/*/input.yaml for worked examples.
| AI Gateway source | Kong decK output |
|---|---|
| Model | One route per (provider endpoint, capability) under a single shared ai-gateway Service, with the path derived from the model's formats[0].type (llm_format) + capability via the endpoint table. Each route gets an ai-proxy-advanced plugin (route: FK) — models that resolve to the same endpoint share one route, contributing one targets[] entry each. Body-model routes also get an ai-model-selector plugin. One ai-models entry (name + alias) is emitted per model. |
| Provider | Not a standalone entity. Its type and config.auth populate each referencing target's model.provider, model.options, and auth. |
| MCP Server | Service + Route + ai-mcp-proxy (config.mode = source type). Server ACLs / per-tool ACLs are written into the plugin config (default_acl, tools[].acl), not Kong acl plugins. access.auth_strategies + access.metadata (openid-connect) add an ai-mcp-oauth2 plugin and append metadata.endpoint to the route (listener / conversion-listener / passthrough-listener only); a key-auth strategy adds a key-auth plugin (and is rejected if metadata is set). config.upstream.auth (AWS SigV4) lowers to the plugin's auth record. |
Agent (a2a) |
Service (config.url) + Route + ai-a2a-proxy plugin (logging). config.upstream.auth (AWS SigV4) lowers to the plugin's auth record, and config.proxy to proxy_config. |
Agent (http) |
Service (config.url) + Route, no AI plugin. |
| Policy | Kong plugin (name = policy type, config passed through). global: true -> one top-level plugin; otherwise instantiated per referencing entity. |
| Auth Strategy | Kong authentication plugin (name = strategy type: key-auth or openid-connect, config passed through) on the route of each entity whose access.auth_strategies references it, with config.anonymous pointing at a synthesized anonymous consumer that a request-termination plugin rejects with a 401. Models with different auth-strategy sets never share a route. |
| Consumer | Consumer (username = name, custom_id), groups membership, nested keyauth_credentials, scoped policy plugins. |
| Consumer Group | consumer_groups entry + scoped policy plugins. |
| Credential | keyauth_credentials nested under the consumer (key from api_key, ttl). |
| Vault | vaults entry (prefix = name, name = backend type, config passed through). |
| CA Certificate | ca_certificates entry (cert, cert_digest); name preserved as an ai-gateway-name: tag (Kong's entity has no name field). |
| Certificate | certificates entry (cert/key and the optional cert_alt/key_alt pair passed through). Kong certificates have no name, so the source name is not represented in the output; in db-less mode it still seeds the generated id. |
| SNI | Nested under its referenced Certificate's snis list (hostname → name, labels → tags); decK's file format has no standalone SNI entity. The SNI's own name/display_name have no Kong counterpart and are dropped. In db-less mode SNIs become a top-level snis entry referencing the certificate's generated id. |
Model policies/acls |
Top-level plugins scoped to the ai-models entity via a model: FK. |
Agent access.acls |
Kong acl plugin on the agent's Route. |
labels |
tags flattened to sorted key:value strings. |
A model's capabilities choose which routes are created. The mapping (path,
methods, route_type, genai_category) is defined per provider section in
convert/endpoints.go, derived from ref/supported-endpoints.md. Loose
spellings are normalized (chat→generate, batch→batches); bare audio
fans out to speech/transcription/translation. Native formats (bedrock, gemini,
vertex) emit regex routes (~/ai/...); capabilities that share an upstream
endpoint (e.g. bedrock embeddings/image/audio/video → /invoke) collapse into
one route with multiple targets.
The revert package lifts a decK config back into the entity model. It is
best-effort generic: recognition is by AI plugin (ai-proxy-advanced,
ai-mcp-proxy, ai-a2a-proxy) anywhere in the config, with the forward
converter's conventions (route names, ai-models entries) used as hints when
present. Converter-produced configs round-trip byte-for-byte
(revert/roundtrip_test.go enforces this for every forward golden case).
How Kong entities come back:
- ai-proxy-advanced routes → Models. Targets group into one Model per
model_alias(theai-modelsentry supplies the name); capabilities are recovered from the route name /route_type/genai_category/ path shape via the shared endpoint table, and the base path is recovered from the route path. Alias-less targets fall back to positionalai-modelsmatching, then the route name. - Providers are synthesized from each target's auth/options (deduped by
fingerprint) since Kong has no standalone provider entity. Names derive from
the vault prefix in the credential (
openai-env) or a per-type counter. - ai-mcp-proxy → MCP Servers, ai-a2a-proxy → a2a Agents, plain
services with routes → http Agents. On an MCP route, an
ai-mcp-oauth2plugin →access.metadata+ a synthesized openid-connect auth strategy (the.well-knownpath is stripped back off the route), andkey-auth/openid-connect→access.auth_strategies. key-auth/openid-connectplugins → Auth Strategies, deduped by (type, config minus the synthesizedanonymousfallback) and named<type>-<n>(openid-connect-1), emitted underauth_strategies:and referenced from the owning entity'saccess.auth_strategies.certificates→ Certificates, passed straight back through. Kong has no certificate name, so one is synthesized positionally (certificate-1); the name is absent from the decK output, so this never changes a round trip.- Certificate
snis→ SNIs, one AI Gateway SNI per nested entry. Both the SNI name and itscertificatereference are synthesized positionally (sni-1,certificate-1, ...), so this never changes a round trip either. - Unknown plugins → Policies (global when top-level and unscoped, otherwise
referenced from the owning entity);
aclplugins →acls. - Anything unconvertible is warned about and dropped;
-strictmakes those drops fatal.
Lossy by design (the forward direction never emits them): display_name,
enabled, original provider names, certificate names, SNI names, capability
spellings (chat comes back as generate; bare audio stays fanned out),
and formats beyond the first.
ai-models/ai-model-selector. Output uses theai-modelsentity andai-model-selectorplugin shown inref/examples/models/. If your decK/Kong build doesn't recognize these yet, sync the rest and add them when available.- Shared gateway Service. All model routes nest under one
ai-gatewayService with the nominal urlhttp://ai-gateway.upstream.local;ai-proxy-advancedoverrides the real upstream per target. - One primary endpoint per capability. Each (section, capability) maps to a
single canonical endpoint.
rerankhas no OpenAI-formatroute_type(native-only) and falls back tollm/v1/chat. - Multi-modal routes. When several capabilities share one upstream endpoint
(e.g. bedrock
/invoke), the route'sgenai_categoryis taken from the first contributor (a plugin-level field can hold only one value). - Credentials. Only
api-key(keyauth_credentials) is generated; other credential types are warned about and skipped. - MCP upstream. Passthrough MCP servers without an
upstream_urlget a placeholder host and a warning. - Upstream auth. Agents and MCP Servers carry
config.upstream.auth(AWS SigV4,type: aws), which maps to theai-a2a-proxy/ai-mcp-proxyauthrecord (provider: aws_iam, nestedaws_iamoptions). Unsupported auth types are warned about and dropped. The plugin'saws_iam.bearer_tokenhas no AI Gateway representation, so the reverse direction warns and drops it. - MCP OAuth2. MCP
access.auth_strategies/access.metadataround-trips in both directions. An openid-connect auth strategy lowers its client credentials plus the identically-typed / unambiguous fields onto theai-mcp-oauth2plugin:client_alg,client_auth,introspection_endpoint,mtls_introspection_endpoint,cache_introspection,jwks_endpoint,leeway(→jwt_claims_leeway),ssl_verify,consumer_by,consumer_claims(→consumer_claim),consumer_optional,consumer_groups_claim,consumer_groups_optional,credential_claim,keepalive,timeout, andhttp_version. OIDC's flathttp(s)_proxy*/no_proxyfields lower to the plugin's structuredproxy_configrecord; this requires a shared proxy scheme and Basic credentials, because the target has one shared credential pair. The reverse reconstructs an equivalent OIDC proxy configuration. The reverse synthesizes an openid-connect provider carrying exactly those fields; array-valued OIDC fields collapsed to a plugin scalar (client_id,client_secret,client_alg,client_auth) or single path (consumer_claim) are re-wrapped into a one-element array, so a multi-element source is lossy in the intermediate model but re-converts byte-identically. Two further fields map by semantic derivation rather than a plain copy: the provider'shide_credentials: falsebecomes the plugin'spassthrough_credentials: true(logical inverse; defaults agree, so only the non-default is emitted), andinsecure_relaxed_audience_validationis always emitted to mirror OIDC audience enforcement —falsewhen the provider'saudience_requiredis set,trueotherwise (the reverse reconstructsaudience_requiredfrom the metadataresource, its RFC 8707 audience, when the flag isfalse). Deliberately not mapped (documented non-conversions):token_exchange(a false friend — OIDC's legacy grant vs the plugin's RFC-8693 upstream exchange object),client_jwk(OIDC JWK object array vs plugin serialized string),introspection_endpoint_auth_method(would collide withclient_auth), the downstream/upstream header-mapping fields,tls_client_auth_cert_id(cert-entity UUID vs inline PEM), andextra_jwks_uris(no plugin target). Provider config the plugin doesn't carry (e.g.cache_tokens_salt,auth_methods) is not recovered, andauthorization_servers/scopes_supportedare always attributed to the metadata rather than the provider's issuer/scopes. - Labels are lossy as tags when a value contains
:.
Please review our CODE_OF_CONDUCT.md for community guidelines.