From 6cc9c164bd34d9facc1255b226cda218f459876f Mon Sep 17 00:00:00 2001 From: jbaross Date: Fri, 21 Aug 2026 13:48:44 +0100 Subject: [PATCH 01/17] add codex case --- app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md | 4 +++- app/_includes/how-tos/validations/codex/snippet.md | 10 +++++++++- app/_plugins/drops/validations/codex.rb | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 615c3c8a4d..0e96eddf17 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -119,8 +119,10 @@ OPENAI_BASE_URL: http://localhost:8000/codex Run a simple command to confirm traffic flows through {{site.ai_gateway}} to OpenAI: {% validation codex %} +open_api_key: sk-placeholder +base_url: http://localhost:8000/codex model: gpt-5.4 -prompt: Hello +prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} When prompted for network access, select **Yes, proceed**. Codex routes the request through {{site.ai_gateway}} to the OpenAI Responses API and returns the model's response, giving you monitoring and control over all Codex LLM traffic. \ No newline at end of file diff --git a/app/_includes/how-tos/validations/codex/snippet.md b/app/_includes/how-tos/validations/codex/snippet.md index 2c83b3bb75..5021c634af 100644 --- a/app/_includes/how-tos/validations/codex/snippet.md +++ b/app/_includes/how-tos/validations/codex/snippet.md @@ -1,6 +1,14 @@ -```sh +{%- capture env_exports -%} +{% if include.config.open_api_key -%} +export OPENAI_API_KEY={{include.config.open_api_key}} +{% endif -%} {% if include.config.base_url -%} export OPENAI_BASE_URL={{include.config.base_url}} +{% endif -%} +{%- endcapture -%} +```sh +{% if env_exports != empty -%} +{{ env_exports | strip }} {% endif -%} {{include.config.base_command}} diff --git a/app/_plugins/drops/validations/codex.rb b/app/_plugins/drops/validations/codex.rb index 10c6f9d7f2..a80db8168f 100644 --- a/app/_plugins/drops/validations/codex.rb +++ b/app/_plugins/drops/validations/codex.rb @@ -41,6 +41,7 @@ def command def env_vars [ + ("OPENAI_API_KEY=#{self['open_api_key']}" if self['open_api_key']), ("OPENAI_BASE_URL=#{self['base_url']}" if self['base_url']) ].compact end From fe19c1bbf89a02cfc33d491921714a0b85e49772 Mon Sep 17 00:00:00 2001 From: jbaross Date: Fri, 21 Aug 2026 13:52:48 +0100 Subject: [PATCH 02/17] codex cleanup --- .../ai-gateway/use-codex-with-ai-gateway.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 0e96eddf17..874b9209e7 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -105,15 +105,6 @@ In this example, we're setting up the AI Model with: If you are a new Codex user, you must Initialise the tool first by running `codex` and following the steps provided. {% endwarning %} -## Point Codex CLI at {{site.ai_gateway}} - -Open a new terminal and set `OPENAI_BASE_URL` to the local {{site.ai_gateway}} endpoint. The Codex CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the gateway, so a placeholder is fine: - -{% env_variables %} -OPENAI_API_KEY: sk-placeholder -OPENAI_BASE_URL: http://localhost:8000/codex -{% endenv_variables %} - ## Start and use Codex CLI Run a simple command to confirm traffic flows through {{site.ai_gateway}} to OpenAI: @@ -125,4 +116,7 @@ model: gpt-5.4 prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} -When prompted for network access, select **Yes, proceed**. Codex routes the request through {{site.ai_gateway}} to the OpenAI Responses API and returns the model's response, giving you monitoring and control over all Codex LLM traffic. \ No newline at end of file +When prompted for network access, select **Yes, proceed**. Codex routes the request through {{site.ai_gateway}} to the OpenAI Responses API and returns the model's response, giving you monitoring and control over all Codex LLM traffic. + +{:.info} +> The Codex CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. From 716ce759a089bf431f53b44d1b1f386ff8d02ae4 Mon Sep 17 00:00:00 2001 From: jbaross Date: Fri, 21 Aug 2026 14:01:28 +0100 Subject: [PATCH 03/17] qwen case --- .../use-qwen-code-with-ai-gateway-qwen.md | 16 ++++++---------- .../how-tos/validations/qwen/snippet.md | 14 ++++++++++++++ app/_plugins/drops/validations/qwen.rb | 10 ++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md index 224439f922..9ab56b8cb9 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md @@ -105,23 +105,19 @@ This example uses the following settings: * `targets[0].config.international: true`: Uses DashScope's international endpoint (`dashscope-intl.aliyuncs.com`); set it to `false` if your DashScope key belongs to a mainland China account. * `capabilities: [generate]`: Exposes the model at a `/qwen/chat/completions` endpoint. -## Point Qwen Code CLI at {{site.ai_gateway}} - -Open a new terminal and set `OPENAI_BASE_URL` to the local {{site.ai_gateway}} endpoint. Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the gateway, so a placeholder is fine: - -{% env_variables %} -OPENAI_API_KEY: sk-placeholder -OPENAI_BASE_URL: http://localhost:8000/qwen-dashscope/chat/completions -{% endenv_variables %} - ## Run Qwen Code CLI Run Qwen Code CLI against the model configured in the AI Model entity's `targets`: {% validation qwen %} +open_api_key: sk-placeholder +base_url: http://localhost:8000/qwen-dashscope/chat/completions model: my-qwen-dashscope auth-type: openai prompt: Explain the singleton pattern in Python. {% endvalidation %} -Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the upstream DashScope Qwen model. \ No newline at end of file +Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the upstream DashScope Qwen model. + +{:.info} +> The Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. \ No newline at end of file diff --git a/app/_includes/how-tos/validations/qwen/snippet.md b/app/_includes/how-tos/validations/qwen/snippet.md index ac26b3d911..a49ba35fb3 100644 --- a/app/_includes/how-tos/validations/qwen/snippet.md +++ b/app/_includes/how-tos/validations/qwen/snippet.md @@ -1,4 +1,18 @@ +{%- capture env_exports -%} +{% if include.config.open_api_key -%} +export OPENAI_API_KEY={{include.config.open_api_key}} +{% endif -%} +{% if include.config.base_url -%} +export OPENAI_BASE_URL={{include.config.base_url}} +{% endif -%} +{%- endcapture -%} + ```sh +{% if env_exports != empty -%} +{{ env_exports | strip }} + +{% endif -%} + {{include.config.base_command}} ``` diff --git a/app/_plugins/drops/validations/qwen.rb b/app/_plugins/drops/validations/qwen.rb index 20fcc793a9..bdb6e224f1 100644 --- a/app/_plugins/drops/validations/qwen.rb +++ b/app/_plugins/drops/validations/qwen.rb @@ -31,12 +31,22 @@ def base_command def command @command ||= [ + *env_vars, configuration.fetch('command'), "--model \"#{@yaml.fetch('model')}\"", "--auth-type \"#{@yaml.fetch('auth-type')}\"", "--prompt \"#{@yaml.fetch('prompt')}\"" ].join(' ') end + + private + + def env_vars + [ + ("OPENAI_API_KEY=#{self['open_api_key']}" if self['open_api_key']), + ("OPENAI_BASE_URL=#{self['base_url']}" if self['base_url']) + ].compact + end end end end From be4f772af83c81762221863bcaaad1c72aa1571c Mon Sep 17 00:00:00 2001 From: jbaross Date: Fri, 21 Aug 2026 14:02:56 +0100 Subject: [PATCH 04/17] qwen case --- .../use-qwen-code-with-ai-gateway-openai.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md index 9932233278..5c6ae499a2 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md @@ -95,23 +95,19 @@ This example uses the following settings: * `targets`: Sends requests to `gpt-5-mini` through the `generic-openai` provider. * `capabilities: [generate]`: Exposes the model at a `/qwen/chat/completions` endpoint. -## Point Qwen Code CLI at {{site.ai_gateway}} - -Open a new terminal and set `OPENAI_BASE_URL` to the local {{site.ai_gateway}} endpoint. Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the gateway, so a placeholder is fine: - -{% env_variables %} -OPENAI_API_KEY: sk-placeholder -OPENAI_BASE_URL: http://localhost:8000/qwen/chat/completions -{% endenv_variables %} - ## Run Qwen Code CLI Run Qwen Code CLI against the model configured in the AI Model entity's `targets`: {% validation qwen %} +open_api_key: sk-placeholder +base_url: http://localhost:8000/qwen/chat/completions model: my-qwen-openai auth-type: openai prompt: Explain the singleton pattern in Python. {% endvalidation %} Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the OpenAI model. + +{:.info} +> The Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. From 3b3ebb94e4e1edf55db08038c77afcbe670e5314 Mon Sep 17 00:00:00 2001 From: jbaross Date: Mon, 24 Aug 2026 10:16:45 +0100 Subject: [PATCH 05/17] vale fixes --- app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md | 2 ++ app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md | 2 ++ app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 874b9209e7..c5cb8322bf 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -109,12 +109,14 @@ If you are a new Codex user, you must Initialise the tool first by running `code Run a simple command to confirm traffic flows through {{site.ai_gateway}} to OpenAI: + {% validation codex %} open_api_key: sk-placeholder base_url: http://localhost:8000/codex model: gpt-5.4 prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} + When prompted for network access, select **Yes, proceed**. Codex routes the request through {{site.ai_gateway}} to the OpenAI Responses API and returns the model's response, giving you monitoring and control over all Codex LLM traffic. diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md index 5c6ae499a2..53fc3516a6 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md @@ -99,6 +99,7 @@ This example uses the following settings: Run Qwen Code CLI against the model configured in the AI Model entity's `targets`: + {% validation qwen %} open_api_key: sk-placeholder base_url: http://localhost:8000/qwen/chat/completions @@ -106,6 +107,7 @@ model: my-qwen-openai auth-type: openai prompt: Explain the singleton pattern in Python. {% endvalidation %} + Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the OpenAI model. diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md index 9ab56b8cb9..4755984299 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-qwen.md @@ -109,6 +109,7 @@ This example uses the following settings: Run Qwen Code CLI against the model configured in the AI Model entity's `targets`: + {% validation qwen %} open_api_key: sk-placeholder base_url: http://localhost:8000/qwen-dashscope/chat/completions @@ -116,6 +117,7 @@ model: my-qwen-dashscope auth-type: openai prompt: Explain the singleton pattern in Python. {% endvalidation %} + Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the upstream DashScope Qwen model. From a17fc41327f0c17a1c6e11caaef2debf4e932ac0 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 11:19:51 +0100 Subject: [PATCH 06/17] use correct model name --- app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index c5cb8322bf..840086d38f 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -113,7 +113,7 @@ Run a simple command to confirm traffic flows through {{site.ai_gateway}} to Ope {% validation codex %} open_api_key: sk-placeholder base_url: http://localhost:8000/codex -model: gpt-5.4 +model: codex-openai prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} From b5a73eff580125f7437175dedabfe6b8f772d407 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 13:47:41 +0100 Subject: [PATCH 07/17] fix config --- app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 840086d38f..7720a5644f 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -78,8 +78,13 @@ ai_gateway_models: enabled: true formats: [{ type: openai }] config: - route: { paths: [/codex], methods: [GET, POST], model: { body_param: model, values: [gpt-5.4] } } - model: { name_header: true } + route: + paths: + - /codex + model: + body_param: model + values: + - codex-openai capabilities: [agentic] targets: - name: gpt-5.4 From ecb75f6407fd708240950bb8da9d48eb2a6d33c2 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 15:14:16 +0100 Subject: [PATCH 08/17] basic working run command --- .../ai-gateway/use-codex-with-ai-gateway.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 7720a5644f..c0e7ac71ad 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -37,7 +37,8 @@ prereqs: Get an API key from [platform.openai.com/api-keys](https://platform.openai.com/api-keys) and export it as the **full `Authorization` header value** (including the `Bearer ` prefix): ```sh - export OPENAI_AUTH_HEADER="Bearer your_api_key" + export OPENAI_API_KEY="your_api_key" + export OPENAI_AUTH_HEADER="Bearer $OPENAI_API_KEY" ``` - title: Codex CLI icon_url: /assets/icons/openai.svg @@ -80,7 +81,7 @@ ai_gateway_models: config: route: paths: - - /codex + - / model: body_param: model values: @@ -114,6 +115,18 @@ If you are a new Codex user, you must Initialise the tool first by running `code Run a simple command to confirm traffic flows through {{site.ai_gateway}} to OpenAI: + +```sh +codex \ + --config 'model="codex-openai"' \ + --config 'model_provider="my-gateway"' \ + --config 'model_providers.my-gateway.name="AI Quickstart"' \ + --config 'model_providers.my-gateway.base_url="http://localhost:8000/"' \ + --config 'model_providers.my-gateway.env_key="OPENAI_API_KEY"' \ + --config 'model_providers.my-gateway.wire_api="responses"' +``` + + {% validation codex %} open_api_key: sk-placeholder From d54dfc6c1fa7b7cb95ed0ba558789a80fb4f16bb Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 15:33:36 +0100 Subject: [PATCH 09/17] example validation block --- .../ai-gateway/use-codex-with-ai-gateway.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index c0e7ac71ad..6e9858d09b 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -31,15 +31,9 @@ tldr: prereqs: inline: - - title: OpenAI + - title: OpenAI API key + include_content: md/ai-gateway/v2/prereqs/openai-kongctl icon_url: /assets/icons/openai.svg - content: | - Get an API key from [platform.openai.com/api-keys](https://platform.openai.com/api-keys) and export it as the **full `Authorization` header value** (including the `Bearer ` prefix): - - ```sh - export OPENAI_API_KEY="your_api_key" - export OPENAI_AUTH_HEADER="Bearer $OPENAI_API_KEY" - ``` - title: Codex CLI icon_url: /assets/icons/openai.svg content: | @@ -129,9 +123,12 @@ codex \ {% validation codex %} -open_api_key: sk-placeholder -base_url: http://localhost:8000/codex model: codex-openai +model_provider: my-gateway +model_provider_name: AI Quickstart +model_provider_base_url: http://localhost:8000/ +model_provider_env_key: OPENAI_API_KEY +model_provider_wire_api: responses prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} From f438c69705a6df8cd467d45b60b9e9e8827c1fd9 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 15:34:17 +0100 Subject: [PATCH 10/17] revert env variables in validation blocks --- app/_includes/how-tos/validations/codex/snippet.md | 10 +--------- app/_plugins/drops/validations/codex.rb | 10 ---------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/app/_includes/how-tos/validations/codex/snippet.md b/app/_includes/how-tos/validations/codex/snippet.md index 5021c634af..2c83b3bb75 100644 --- a/app/_includes/how-tos/validations/codex/snippet.md +++ b/app/_includes/how-tos/validations/codex/snippet.md @@ -1,14 +1,6 @@ -{%- capture env_exports -%} -{% if include.config.open_api_key -%} -export OPENAI_API_KEY={{include.config.open_api_key}} -{% endif -%} +```sh {% if include.config.base_url -%} export OPENAI_BASE_URL={{include.config.base_url}} -{% endif -%} -{%- endcapture -%} -```sh -{% if env_exports != empty -%} -{{ env_exports | strip }} {% endif -%} {{include.config.base_command}} diff --git a/app/_plugins/drops/validations/codex.rb b/app/_plugins/drops/validations/codex.rb index a80db8168f..07be0d25b7 100644 --- a/app/_plugins/drops/validations/codex.rb +++ b/app/_plugins/drops/validations/codex.rb @@ -29,22 +29,12 @@ def base_command def command @command ||= [ - *env_vars, configuration.fetch('command'), "exec \"#{@yaml.fetch('prompt')}\"", "--model \"#{@yaml.fetch('model')}\"", '--skip-git-repo-check' ].join(' ') end - - private - - def env_vars - [ - ("OPENAI_API_KEY=#{self['open_api_key']}" if self['open_api_key']), - ("OPENAI_BASE_URL=#{self['base_url']}" if self['base_url']) - ].compact - end end end end From d9850380ae640e1ad60269b0cfaebc04665f5eb4 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 16:00:29 +0100 Subject: [PATCH 11/17] working validation block --- .../ai-gateway/use-codex-with-ai-gateway.md | 12 ----- .../how-tos/validations/codex/snippet.md | 4 -- app/_plugins/drops/validations/codex.rb | 45 +++++++++++++++---- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index 6e9858d09b..e29b1f6d0f 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -109,18 +109,6 @@ If you are a new Codex user, you must Initialise the tool first by running `code Run a simple command to confirm traffic flows through {{site.ai_gateway}} to OpenAI: - -```sh -codex \ - --config 'model="codex-openai"' \ - --config 'model_provider="my-gateway"' \ - --config 'model_providers.my-gateway.name="AI Quickstart"' \ - --config 'model_providers.my-gateway.base_url="http://localhost:8000/"' \ - --config 'model_providers.my-gateway.env_key="OPENAI_API_KEY"' \ - --config 'model_providers.my-gateway.wire_api="responses"' -``` - - {% validation codex %} model: codex-openai diff --git a/app/_includes/how-tos/validations/codex/snippet.md b/app/_includes/how-tos/validations/codex/snippet.md index 2c83b3bb75..af6111b573 100644 --- a/app/_includes/how-tos/validations/codex/snippet.md +++ b/app/_includes/how-tos/validations/codex/snippet.md @@ -1,8 +1,4 @@ ```sh -{% if include.config.base_url -%} -export OPENAI_BASE_URL={{include.config.base_url}} - -{% endif -%} {{include.config.base_command}} ``` diff --git a/app/_plugins/drops/validations/codex.rb b/app/_plugins/drops/validations/codex.rb index 07be0d25b7..1a61ee7841 100644 --- a/app/_plugins/drops/validations/codex.rb +++ b/app/_plugins/drops/validations/codex.rb @@ -7,9 +7,15 @@ module Jekyll module Drops module Validations class Codex < Base # rubocop:disable Style/Documentation + REQUIRED_FIELDS = %w[ + prompt model model_provider model_provider_name + model_provider_base_url model_provider_env_key model_provider_wire_api + ].freeze + def validate_yaml! - raise ArgumentError, "Missing `prompt` in {% validation #{id} %}." unless @yaml.key?('prompt') - raise ArgumentError, "Missing `model` in {% validation #{id} %}." unless @yaml.key?('model') + REQUIRED_FIELDS.each do |field| + raise ArgumentError, "Missing `#{field}` in {% validation #{id} %}." unless @yaml.key?(field) + end end def data_validate @@ -21,19 +27,40 @@ def config end def base_command - @base_command ||= [ - configuration.fetch('command'), - "--model \"#{@yaml.fetch('model')}\"" - ].join(' ') + @base_command ||= to_multiline([configuration.fetch('command'), *config_flags]) end def command - @command ||= [ + parts = [ configuration.fetch('command'), "exec \"#{@yaml.fetch('prompt')}\"", - "--model \"#{@yaml.fetch('model')}\"", + *config_flags, '--skip-git-repo-check' - ].join(' ') + ] + @command ||= to_multiline(parts) + end + + private + + # Renders as `line1 \\\n line2 \\\n line3`, matching the multi-line `--config` + # examples shown elsewhere in the docs; `bash -c` treats it identically to one line. + def to_multiline(parts) + parts.join(" \\\n ") + end + + # Codex CLI has no env-var override for a custom upstream, so a named + # `model_provider` must be declared via repeated `--config` flags instead. + def config_flags + provider = @yaml.fetch('model_provider') + + [ + "--config 'model=\"#{@yaml.fetch('model')}\"'", + "--config 'model_provider=\"#{provider}\"'", + "--config 'model_providers.#{provider}.name=\"#{@yaml.fetch('model_provider_name')}\"'", + "--config 'model_providers.#{provider}.base_url=\"#{@yaml.fetch('model_provider_base_url')}\"'", + "--config 'model_providers.#{provider}.env_key=\"#{@yaml.fetch('model_provider_env_key')}\"'", + "--config 'model_providers.#{provider}.wire_api=\"#{@yaml.fetch('model_provider_wire_api')}\"'" + ] end end end From 5f053ebb95d2cbeb9f22dc6d5c1f3c9f39738a45 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 16:05:17 +0100 Subject: [PATCH 12/17] simplify comments --- app/_plugins/drops/validations/codex.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/_plugins/drops/validations/codex.rb b/app/_plugins/drops/validations/codex.rb index 1a61ee7841..d9b46608d7 100644 --- a/app/_plugins/drops/validations/codex.rb +++ b/app/_plugins/drops/validations/codex.rb @@ -42,14 +42,12 @@ def command private - # Renders as `line1 \\\n line2 \\\n line3`, matching the multi-line `--config` - # examples shown elsewhere in the docs; `bash -c` treats it identically to one line. + # Renders as a multi-line `--config` def to_multiline(parts) parts.join(" \\\n ") end - # Codex CLI has no env-var override for a custom upstream, so a named - # `model_provider` must be declared via repeated `--config` flags instead. + # Codex CLI `model_provider` must be declared via repeated `--config` flags instead. def config_flags provider = @yaml.fetch('model_provider') From 9518c46f0edb4d2df0367c21b114c7d3efdb7808 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 16:28:47 +0100 Subject: [PATCH 13/17] describe updated config --- app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md index e29b1f6d0f..0c885f5bde 100644 --- a/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md +++ b/app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md @@ -98,12 +98,8 @@ In this example, we're setting up the AI Model with: * `capabilities: [agentic]`: Routes requests to the OpenAI Responses API, which the Codex CLI uses. * `formats: [{ type: openai }]`: Accepts OpenAI-format requests. -* `config.route.model: { body_param: model, values: [gpt-5.4] }`: The model name the Codex CLI sends in each request. -* `route.paths: [/codex]`: The base path Codex points at; the Responses API is served at `/codex/responses`. - -{% warning %} -If you are a new Codex user, you must Initialise the tool first by running `codex` and following the steps provided. -{% endwarning %} +* `config.route.model: { body_param: model, values: [codex-openai] }`: The model name the Codex CLI sends in each request. +* `route.paths: [/]`: The base path Codex points at; the Responses API is served at `/responses`. ## Start and use Codex CLI @@ -124,4 +120,4 @@ prompt: Tell me about the Madrid Skylitzes manuscript. When prompted for network access, select **Yes, proceed**. Codex routes the request through {{site.ai_gateway}} to the OpenAI Responses API and returns the model's response, giving you monitoring and control over all Codex LLM traffic. {:.info} -> The Codex CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. +> The Codex CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. You may be prompted to confirm this in the TUI interface. From bd06ceb2d467d5b791d217d2f356a9886ec5fc08 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 16:36:44 +0100 Subject: [PATCH 14/17] already set in prereqs/openai-kongctl --- app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md index 53fc3516a6..9078e9cc38 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md @@ -101,7 +101,6 @@ Run Qwen Code CLI against the model configured in the AI Model entity's `targets {% validation qwen %} -open_api_key: sk-placeholder base_url: http://localhost:8000/qwen/chat/completions model: my-qwen-openai auth-type: openai From 06c67d7e62cfa23390ea140b911052888866a002 Mon Sep 17 00:00:00 2001 From: jbaross Date: Tue, 25 Aug 2026 16:41:32 +0100 Subject: [PATCH 15/17] qwen on openai clarify warning --- app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md index 9078e9cc38..1ff61d30b3 100644 --- a/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md +++ b/app/_how-tos/ai-gateway/use-qwen-code-with-ai-gateway-openai.md @@ -111,4 +111,4 @@ prompt: Explain the singleton pattern in Python. Qwen Code CLI returns a response, proxied through {{site.ai_gateway}} to the OpenAI model. {:.info} -> The Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}, so setting a placeholder is fine. +> The Qwen Code CLI requires `OPENAI_API_KEY` to be set even though the real key lives on the {{site.ai_gateway}}. From 7f134b1ac193fda28a13bb11f33d4977e7adbded Mon Sep 17 00:00:00 2001 From: Fabian Rodriguez Date: Tue, 25 Aug 2026 19:39:22 +0200 Subject: [PATCH 16/17] refactor(codex): codex validation block --- .../how-tos/validations/codex/snippet.md | 4 +- app/_plugins/drops/validations/codex.rb | 59 ++++++++++----- .../_plugins/drops/validations/codex_spec.rb | 73 ++++++++++--------- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/app/_includes/how-tos/validations/codex/snippet.md b/app/_includes/how-tos/validations/codex/snippet.md index af6111b573..86df354854 100644 --- a/app/_includes/how-tos/validations/codex/snippet.md +++ b/app/_includes/how-tos/validations/codex/snippet.md @@ -1,5 +1,7 @@ ```sh -{{include.config.base_command}} +{{include.config.base_command}} \ +{%- for flag in include.config.flags %} + --config {{flag}}{%- unless forloop.last %} \{% endunless %}{%- endfor %} ``` And ask a question to confirm that requests reach {{site.ai_gateway}}. diff --git a/app/_plugins/drops/validations/codex.rb b/app/_plugins/drops/validations/codex.rb index d9b46608d7..13e98f84b2 100644 --- a/app/_plugins/drops/validations/codex.rb +++ b/app/_plugins/drops/validations/codex.rb @@ -23,42 +23,61 @@ def data_validate end def config - @config ||= configuration.merge('command' => command, 'base_command' => base_command) + @config ||= configuration.merge('command' => command, 'base_command' => base_command, 'flags' => flags) end def base_command - @base_command ||= to_multiline([configuration.fetch('command'), *config_flags]) + @base_command ||= configuration.fetch('command') end def command - parts = [ + @command ||= [ configuration.fetch('command'), - "exec \"#{@yaml.fetch('prompt')}\"", - *config_flags, + "exec \"#{prompt}\"", + *flags.map { |flag| "-c #{flag}" }, '--skip-git-repo-check' + ].join(' ') + end + + def flags + [ + "model=\"#{model}\"", + "model_provider=\"#{provider}\"", + "model_providers.#{provider}.name=\"#{name}\"", + "model_providers.#{provider}.base_url=\"#{base_url}\"", + "model_providers.#{provider}.env_key=\"#{env_key}\"", + "model_providers.#{provider}.wire_api=\"#{wire_api}\"" ] - @command ||= to_multiline(parts) end private - # Renders as a multi-line `--config` - def to_multiline(parts) - parts.join(" \\\n ") + def prompt + @prompt ||= @yaml.fetch('prompt') end - # Codex CLI `model_provider` must be declared via repeated `--config` flags instead. - def config_flags - provider = @yaml.fetch('model_provider') + def model + @model ||= @yaml.fetch('model') + end - [ - "--config 'model=\"#{@yaml.fetch('model')}\"'", - "--config 'model_provider=\"#{provider}\"'", - "--config 'model_providers.#{provider}.name=\"#{@yaml.fetch('model_provider_name')}\"'", - "--config 'model_providers.#{provider}.base_url=\"#{@yaml.fetch('model_provider_base_url')}\"'", - "--config 'model_providers.#{provider}.env_key=\"#{@yaml.fetch('model_provider_env_key')}\"'", - "--config 'model_providers.#{provider}.wire_api=\"#{@yaml.fetch('model_provider_wire_api')}\"'" - ] + def provider + @provider ||= @yaml.fetch('model_provider') + end + + def name + @name ||= @yaml.fetch('model_provider_name') + end + + def base_url + @base_url ||= @yaml.fetch('model_provider_base_url') + end + + def env_key + @env_key ||= @yaml.fetch('model_provider_env_key') + end + + def wire_api + @wire_api ||= @yaml.fetch('model_provider_wire_api') end end end diff --git a/spec/app/_plugins/drops/validations/codex_spec.rb b/spec/app/_plugins/drops/validations/codex_spec.rb index 8a59bddb66..a7ce5e0193 100644 --- a/spec/app/_plugins/drops/validations/codex_spec.rb +++ b/spec/app/_plugins/drops/validations/codex_spec.rb @@ -3,14 +3,37 @@ RSpec.describe Jekyll::Drops::Validations::Codex do let(:configured_command) { 'codex' } let(:validations_config) do - [{ 'id' => 'codex', 'command' => 'codex', 'expected' => { 'return_code' => 0 } }] + [{ 'id' => 'codex', 'command' => configured_command, 'expected' => { 'return_code' => 0 } }] end let(:site_data) { { 'how-tos' => { 'config' => { 'validations' => validations_config } } } } let(:site) { instance_double(Jekyll::Site, data: site_data) } - let(:yaml) { { 'model' => 'gpt-5.4', 'prompt' => 'Explain this error message.' } } - let(:base_command) { 'codex --model "gpt-5.4"' } + let(:yaml) do + { + 'model' => 'gpt-5.4', + 'prompt' => 'Explain this error message.', + 'model_provider' => 'kong-ai-gateway', + 'model_provider_name' => 'Kong AI Gateway', + 'model_provider_base_url' => 'http://localhost:9000/codex', + 'model_provider_env_key' => 'OPENAI_API_KEY', + 'model_provider_wire_api' => 'chat' + } + end + let(:flags) do + [ + 'model="gpt-5.4"', + 'model_provider="kong-ai-gateway"', + 'model_providers.kong-ai-gateway.name="Kong AI Gateway"', + 'model_providers.kong-ai-gateway.base_url="http://localhost:9000/codex"', + 'model_providers.kong-ai-gateway.env_key="OPENAI_API_KEY"', + 'model_providers.kong-ai-gateway.wire_api="chat"' + ] + end + let(:expected_base_command) { configured_command } let(:full_command) do - 'codex exec "Explain this error message." --model "gpt-5.4" --skip-git-repo-check' + [ + configured_command, 'exec "Explain this error message."', *flags.map { |flag| "-c #{flag}" }, + '--skip-git-repo-check' + ].join(' ') end before { allow(Jekyll).to receive(:sites).and_return([site]) } @@ -18,46 +41,29 @@ subject(:drop) { described_class.new(id: 'codex', yaml:) } describe '#validate_yaml!' do - context 'when model and prompt are present' do + context 'when all required fields are present' do it { expect { drop }.not_to raise_error } end - context 'when prompt is missing' do - let(:yaml) { { 'model' => 'gpt-5.4' } } - - it { expect { drop }.to raise_error(ArgumentError, 'Missing `prompt` in {% validation codex %}.') } - end - - context 'when model is missing' do - let(:yaml) { { 'prompt' => 'Explain this error message.' } } + described_class::REQUIRED_FIELDS.each do |field| + context "when #{field} is missing" do + let(:yaml) { super().except(field) } - it { expect { drop }.to raise_error(ArgumentError, 'Missing `model` in {% validation codex %}.') } + it { expect { drop }.to raise_error(ArgumentError, "Missing `#{field}` in {% validation codex %}.") } + end end end - it 'builds the base command from the configured command, exec, and quoted model' do - expect(drop.base_command).to eq(base_command) + it 'builds the base command from the configured command alone' do + expect(drop.base_command).to eq(expected_base_command) end - it 'builds the command from the base command plus quoted prompt and skip flag' do + it 'builds the command from the configured command, exec, -c config flags, and skip flag' do expect(drop.command).to eq(full_command) end - context 'when base_url is present' do - let(:yaml) do - { - 'model' => 'gpt-5.4', - 'prompt' => 'Explain this error message.', - 'base_url' => 'http://localhost:9000/codex' - } - end - let(:full_command) do - 'OPENAI_BASE_URL=http://localhost:9000/codex codex exec "Explain this error message." --model "gpt-5.4" --skip-git-repo-check' - end - - it 'prefixes the command with OPENAI_BASE_URL' do - expect(drop.command).to eq(full_command) - end + it 'exposes the raw, unprefixed config flags' do + expect(drop.flags).to eq(flags) end it 'serializes the id and merged config in #data_validate' do @@ -65,7 +71,8 @@ 'name' => 'codex', 'config' => { 'command' => full_command, - 'base_command' => drop.base_command, + 'base_command' => expected_base_command, + 'flags' => flags, 'expected' => { 'return_code' => 0 } } } From cce725b0bc3f68f0ed9a8c06ba63242b2ba026ee Mon Sep 17 00:00:00 2001 From: Fabian Rodriguez Date: Wed, 26 Aug 2026 10:28:47 +0200 Subject: [PATCH 17/17] fix(specs): update specs to match the new implementation --- .../how-tos/validations/codex/snippet_spec.rb | 79 ++++++++----------- .../_plugins/blocks/validations/codex_spec.rb | 46 ++++++++--- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/spec/app/_includes/how-tos/validations/codex/snippet_spec.rb b/spec/app/_includes/how-tos/validations/codex/snippet_spec.rb index 488cd43549..24a361cf5c 100644 --- a/spec/app/_includes/how-tos/validations/codex/snippet_spec.rb +++ b/spec/app/_includes/how-tos/validations/codex/snippet_spec.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -# The base case (no base_url) is adapted from the real {% validation codex %} block in -# app/_how-tos/ai-gateway/use-codex-with-ai-gateway.md. That's the only real-doc usage of -# this validation, and it never sets `base_url`, so the base_url branch is synthetic, built -# directly from the snippet.md logic, to get full branch coverage. RSpec.describe 'how-tos/validations/codex/snippet.md' do let(:validations_config) do [{ 'id' => 'codex', 'command' => 'codex', 'expected' => { 'return_code' => 0 } }] @@ -13,57 +9,44 @@ before { allow(Jekyll).to receive(:sites).and_return([site]) } + let(:yaml) do + { + 'model' => 'codex-openai', + 'model_provider' => 'my-gateway', + 'model_provider_name' => 'AI Quickstart', + 'model_provider_base_url' => 'http://localhost:8000/', + 'model_provider_env_key' => 'OPENAI_API_KEY', + 'model_provider_wire_api' => 'responses', + 'prompt' => 'Tell me about the Madrid Skylitzes manuscript.' + } + end let(:config) { Jekyll::Drops::Validations::Codex.new(id: 'codex', yaml: yaml) } let(:template) { '{% include how-tos/validations/codex/snippet.md config=config %}' } subject(:rendered) { render_liquid(template, locals: { 'config' => config }) } let(:code) { bash_code_block(rendered, lang: 'sh') } - shared_examples 'a valid shell command' do - it 'renders syntactically valid shell' do - validate_bash_syntax!(code) - end - end - - context 'with no base_url (use-codex-with-ai-gateway.md)' do - let(:yaml) { { 'model' => 'gpt-5.4', 'prompt' => 'Hello' } } - - include_examples 'a valid shell command' - - it 'renders the base command with no env export and no leading blank line' do - expect(rendered).to eq(<<~MD) - ```sh - codex --model "gpt-5.4" - ``` - - And ask a question to confirm that requests reach AI Gateway. - - ```text - Hello - ``` - MD - end + it 'renders syntactically valid shell' do + validate_bash_syntax!(code) end - context 'with base_url (synthetic)' do - let(:yaml) { { 'model' => 'gpt-5.4', 'prompt' => 'Hello', 'base_url' => 'http://localhost:9000/codex' } } - - include_examples 'a valid shell command' - - it 'renders the OPENAI_BASE_URL export before the base command' do - expect(rendered).to eq(<<~MD) - ```sh - export OPENAI_BASE_URL=http://localhost:9000/codex - - codex --model "gpt-5.4" - ``` - - And ask a question to confirm that requests reach AI Gateway. - - ```text - Hello - ``` - MD - end + it 'renders the base command with a --config flag per model provider setting' do + expect(rendered).to eq(<<~MD) + ```sh + codex \\ + --config model="codex-openai" \\ + --config model_provider="my-gateway" \\ + --config model_providers.my-gateway.name="AI Quickstart" \\ + --config model_providers.my-gateway.base_url="http://localhost:8000/" \\ + --config model_providers.my-gateway.env_key="OPENAI_API_KEY" \\ + --config model_providers.my-gateway.wire_api="responses" + ``` + + And ask a question to confirm that requests reach AI Gateway. + + ```text + Tell me about the Madrid Skylitzes manuscript. + ``` + MD end end diff --git a/spec/app/_plugins/blocks/validations/codex_spec.rb b/spec/app/_plugins/blocks/validations/codex_spec.rb index 95fa69524c..8e82208582 100644 --- a/spec/app/_plugins/blocks/validations/codex_spec.rb +++ b/spec/app/_plugins/blocks/validations/codex_spec.rb @@ -14,8 +14,13 @@ let(:template) do <<~LIQUID {% validation codex %} - model: gpt-5.4 - prompt: Hello + model: codex-openai + model_provider: my-gateway + model_provider_name: AI Quickstart + model_provider_base_url: http://localhost:8000/ + model_provider_env_key: OPENAI_API_KEY + model_provider_wire_api: responses + prompt: Tell me about the Madrid Skylitzes manuscript. {% endvalidation %} LIQUID end @@ -34,17 +39,34 @@ end end - context 'when model or prompt is missing' do - let(:template) do - <<~LIQUID - {% validation codex %} - model: gpt-5.4 - {% endvalidation %} - LIQUID - end + describe 'yaml validation' do + %w[ + prompt model model_provider model_provider_name + model_provider_base_url model_provider_env_key model_provider_wire_api + ].each do |field| + context "when #{field} is missing" do + let(:template) do + fields = { + 'model' => 'model: codex-openai', + 'model_provider' => 'model_provider: my-gateway', + 'model_provider_name' => 'model_provider_name: AI Quickstart', + 'model_provider_base_url' => 'model_provider_base_url: http://localhost:8000/', + 'model_provider_env_key' => 'model_provider_env_key: OPENAI_API_KEY', + 'model_provider_wire_api' => 'model_provider_wire_api: responses', + 'prompt' => 'prompt: Tell me about the Madrid Skylitzes manuscript.' + }.except(field) + + <<~LIQUID + {% validation codex %} + #{fields.values.join("\n")} + {% endvalidation %} + LIQUID + end - it 'raises an error' do - expect { rendered }.to raise_error(ArgumentError, 'Missing `prompt` in {% validation codex %}.') + it 'raises an error' do + expect { rendered }.to raise_error(ArgumentError, "Missing `#{field}` in {% validation codex %}.") + end + end end end