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 -min-runtime-version 2.1 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 -min-runtime-version 2.1 -o kong.yaml input.yaml
# read from stdin
cat input.yaml | ./ai-deck-converter -min-runtime-version 2.1 -
# force a direction
./ai-deck-converter -direction from-deck kong.yaml
# emit Koko-style db-less output
./ai-deck-converter -direction to-dbless -min-runtime-version 2.1 input.yaml| Flag | Default | Description |
|---|---|---|
-o |
stdout | Output file path. |
-min-runtime-version |
none | Oldest AI Gateway release (MAJOR.MINOR, e.g. 2.1) that must run the output. Required for to-deck and to-dbless; not valid with from-deck. Same value as the Konnect min_runtime_version field. See Minimum runtime version. |
-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 |
Exit non-zero on warnings too. For to-deck/to-dbless, any issue fails the run. For from-deck, unresolved references and unconvertible entities become errors. |
-label-tag-prefix |
"" |
Prefix for label-derived tags, e.g. aigw/ (prepended when converting to decK, stripped when reverting). |
Conversion issues are printed to stderr, one per line, as
SEVERITY CODE kind "name" [fields]: reason. The converted config always goes
to stdout/-o. The exit code is 1 when an issue has the ERROR severity, or on
any issue with -strict. The reverse direction prints warning: lines.
Every forward conversion needs the oldest AI Gateway release that must run the
output, as MAJOR.MINOR. It is the min_runtime_version of the Konnect AI
Gateway. The converter supports 2.0 to 2.3.
- An empty value, a value that is not
MAJOR.MINOR(for example2.1.0orlatest), or a value below2.0fails the run. - A value above
2.3gives aWARNINGand converts for2.3. - A feature that needs a newer release gives an
UNSUPPORTED_IN_MIN_RUNTIME_VERSIONissue. The rules ininternal/aimap/versions.godecide whether the field is dropped with aWARNINGor the entity is left out with anERROR. Seedocs/decisions/00225-dataplane-conversion-compatability.md.
convert.Convert returns a ConversionResult: the Kong document, its
metadata, and every issue found during the run. It returns an error only when
the whole run fails, for example on invalid YAML. The caller decides which
issues stop its workflow. See
RFC 00207 for the design.
Each ConversionIssue has a Code, a Severity, a Source, and a Reason:
- Severity is
WARNING(the converter worked around the issue) orERROR(the output is invalid or diverges from the user intent). - Code is one of
MISSING_REQUIRED_CONFIG_FIELD,UNDEFINED_REFERENCE,CONFIG_FIELD_OVERRIDDEN,CONFIG_FIELD_DROPPED,CONFIG_FIELD_IGNORED,CONFIG_FIELD_INVALID,BEHAVIOR_MAY_DIFFER, orINTERNAL_ERROR. One code can occur with either severity. Handle an unknown code by its severity. - Source names the entity:
Kindis the singular form of its top-level document key (model,mcp_server,agent,policy,custom_policy,auth_strategy,consumer,consumer_group,sni, ...).Fieldsare paths relative to the entity, such astargets[0].provider. - Reason is a human-readable message. It is not API, so do not parse it.
The converter continues after every issue. These rules decide what it emits:
- An entity that cannot be converted is left out with an
ERROR. The other entities still convert. A panic in one entity givesINTERNAL_ERROR. - A nested item that cannot be converted, such as an MCP tool without a
descriptionor a model target with an unknown provider, is left out with aWARNING. A model whose targets are all left out is itself left out with anERROR. - An entity that references an unknown or unconvertible auth strategy is left out, so it is never generated unprotected.
- A global or referenced policy that cannot be converted is left out with an
ERROR. The entities that reference it still convert. - When two models conflict, for example two passthrough models on one path, the later model in the document is left out.
import (
"github.com/Kong/ai-deck-converter/convert"
"github.com/Kong/ai-deck-converter/revert"
)
// AI Gateway -> Kong decK
result, err := convert.Convert(srcYAML, convert.Options{MinRuntimeVersion: "2.1"})
for _, issue := range result.Issues {
if issue.Severity == convert.Error { /* stop, or report */ }
}
deckYAML, err := result.Output.ToYAML() // or result.Output.ToDBLess().ToYAML()
metadata := result.Metadata // or result.Output.ToDBLess().Metadata()
// Kong decK -> AI Gateway
aigwYAML, warnings, err := revert.Revert(deckYAML, revert.Options{})revert.RevertDocument is also available if you already hold a parsed
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, as the top-level
key and as the access reference list on models, agents, and MCP servers. The
converter rejects it with an error. Use 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 — one per config.route.model.values entry when the model declares several aliases, each with its own ai-proxy-advanced (and policy/ACL) copy scoped to that alias, so requests naming any alias select the 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) and the top-level token_vault lower to the plugin's auth record (mutually exclusive). A conversion-only server serves no MCP traffic of its own and cannot declare access itself, so its route gets a pre-function gate (tagged aigw-generated:mcp-toolset-gate) that answers 404 to any request not arriving over the unix socket ai-mcp-proxy re-enters Kong through to execute a listener's tool call: its tools are reachable only through the listeners that name it in config.sources, on their access terms. The gate is dropped again on revert. A conversion-only server that no listener names is dropped entirely, with a warning: it reaches no client. key-auth access on an MCP server is always emitted with hide_credentials: false, so the client's key survives onto the internal request ai-mcp-proxy issues when executing a tool. |
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. |
MCP Server / Agent config.certificates, config.ca_certificates |
Set the generated Service's client_certificate and ca_certificates to the IDs of the named Certificate and CA Certificates. A Service presents one client certificate, so only the first entry of certificates is used. A referenced entity without an id gets the id that db-less mode derives for it. The fields are ignored with a warning if the Service URL is not https, because Kong rejects them there. An unknown name warns and is dropped; -strict makes it an error. |
| Policy | Kong plugin (name = policy type, config passed through). global: true -> one top-level plugin; otherwise instantiated per referencing entity. |
| Datastore | Not a standalone entity. Referenced by name from a Policy's datastores field, or from a Model's datastores field (only when that model's balancer is semantic); its config is merged into that policy's plugin config at a path fixed by the plugin's type (config.redis for most; config.storage_config.redis for acme; config.resources.cache.redis for datakit; config.vectordb.<redis|pgvector> for the VectorDB family, sub-block and strategy chosen by the Datastore's type). On a Model it replaces the connection sub-block of ai-proxy-advanced config.vectordb — the model keeps its own dimensions/distance_metric/threshold, and only redis-ee or vectordb types are accepted. Rejected if the plugin type doesn't support a Datastore, or if the Datastore's type isn't one that plugin accepts. An unknown reference gives an UNDEFINED_REFERENCE error, and the Policy or Model converts without the connection. |
| 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 with formats: [{type: passthrough}] |
Requires AI Gateway 2.2+. One route on the bare base path (every method, no endpoint suffix), whatever capabilities it declares — [] is fine — with one route-scoped ai-proxy-advanced whose targets carry route_type: passthrough and no model_alias; llm_format is the provider's native format (openai for providers without their own). No ai-model-selector, no video lifecycle routes, and the model's policies/ACLs are route-scoped rather than model:-scoped, since nothing can select an ai-models row for a body of unknown shape (the row is still emitted). No anthropic_version default is applied. |
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)
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.
Most capabilities map to a single canonical endpoint, but a few are reachable
through more than one and get a route per endpoint: bedrock generate emits
both bedrock-converse (Converse) and bedrock-invoke (InvokeModel), each
carrying the model's target(s). Likewise every gemini capability is served on
both its Gemini API path and its Gemini Enterprise path (e.g. gemini-generate
and gemini-enterprise-generate), whichever provider type serves it; the Gemini
Enterprise-only
image (:predict), video (:predictLongRunning) and rerank (:rank)
capabilities are available to gemini-format models too. These extra endpoints live in the capability's
own internal/aimap.EndpointTable entry, as EndpointEntry.Secondary
alongside the canonical Primary spec — read both together via
aimap.EndpointsFor / aimap.SectionEndpoints — never look up a capability's
endpoint(s) any other way, or the two directions can drift. Bedrock's invoke
spec for generate is reused verbatim from audio/speech (the same
endpoint), so a model declaring both capabilities gets only one
bedrock-invoke route/target, not two; see "Multi-modal routes" below for the
resulting revert-side ambiguity.
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.- Service
client_certificate/ca_certificates→config.certificates/config.ca_certificateson the MCP Server or Agent. Each ID becomes the name of the matching reverted Certificate or CA Certificate. - 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. - Realtime routes use WebSocket. Kong selects its WebSocket proxy path from
the Service protocol, so
realtimeroutes nest under a secondai-gateway-websocketService (ws://ai-gateway.upstream.local). They usews/wss(an authoredhttp/httpsmaps tows/wss) and no methods, and each model gets its own realtime route.ai-model-selectordoes not run on WebSocket, so the realtime route'sai-proxy-advanced, policy, ACL, and auth-strategy plugins are route-scoped and carry the route'sprotocols, and its targets carry nomodel_alias. Policy plugins whose schema rejectsws/wss(every AI policy plugin, such asai-prompt-guard) are left off the realtime route with a warning; they still apply to the model's HTTP routes. The anonymous consumer'srequest-terminationcannot run on WebSocket either, so a realtime auth plugin has noanonymousfallback, a model with more than one auth strategy cannot declarerealtime, and an auth strategy whose plugin type rejectsws/wss(e.g.jwt) leaves out the model rather than silently leaving the route unprotected. A global authentication policy cannot protect a WebSocket route either, so it leaves out every model with therealtimecapability. The global policy stays. - Mostly one endpoint per capability. Each (section, capability) maps to an
aimap.EndpointEntrywith a primary canonical endpoint, plus any secondaries in itsSecondaryfield (currently just bedrockgenerate, also served by/invoke).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). Bedrock'sgenerateinvoke spec is identical toaudio/speech's, so on revert abedrock-invokeroute/target with no other distinguishing signal comes back asaudio/speech(alphabetically first), even if it was originally created by ageneratecapability — seeconvert/testdata/58_bedrock_generate_and_speech. This never breaks round-tripping a converter-produced config, since re- converting the recovered model reproduces the same route/target either way. - 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). An unsupported auth type leaves out the agent or MCP server with an error. The plugin'saws_iam.bearer_tokenhas no AI Gateway representation, so the reverse direction warns and drops it. - Token Vault. An MCP Server's top-level
token_vault(directory,provider, optionalredis+encryption_secrets) lowers to theai-mcp-proxyauthrecord (provider: token_vault, nestedtoken_vaultoptions) and round-trips in both directions. The nestedredisblock (Konnect'sAIGatewayRedisCloudConfigurationshape) flattens into the plugin redis config's prefixed keys (keepalive.pool_size→keepalive_pool_size,sentinel.master→sentinel_master,cluster.nodes→cluster_nodes,cloud_authentication.type→auth_providerwith provider-prefixed fields, …).token_vaultandconfig.upstream.authare mutually exclusive (both lower to the same plugin auth record) — declaring both is a hard error, as isrediswithoutencryption_secrets(the plugin's own entity check). - 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
:. - Passthrough models. A passthrough model cannot share its base path with
another passthrough model, be combined with another format, or use the
semanticbalancer. Each leaves out the model with an error. A databricks target withoutupstream_urlis left out with a warning. Policies that read the normalized LLM shape (guardrails, prompt decorators/templates/compressor, RAG injector, semantic cache, LLM-as-judge, andai-sanitizerwhenanonymizeincludes credentials, which is its default) produce a warning when attached to the model or global; other AI policies work on raw bytes, and consumer/consumer-group policies are not checked. A target whose provider has no nativellm_format(azure, mistral, sagemaker, …) also warns: usage and cost extraction may find nothing. Revert recovers the model with nocapabilities.
Please review our CODE_OF_CONDUCT.md for community guidelines.