Compare commits

...

11 Commits

Author SHA1 Message Date
Atif Ali
bb128fa077 docs(codex): clarify standalone and AI Bridge usage 2026-02-04 12:45:55 +00:00
Atif Ali
7c5f9b2adc style(codex): remove trailing whitespace in test file 2026-02-04 12:39:29 +00:00
Atif Ali
0a92c5c18f fix(codex): update tests to work with conditional agentapi module
- Only set install_agentapi when explicitly installing real AgentAPI
- Let enable_tasks (defaults to true) control agentapi module in tests
- This fixes tests that were inadvertently triggering standalone mode

All 18 tests now pass 
2026-02-04 11:58:07 +00:00
Atif Ali
a443767ef3 chore(codex): apply bun fmt to README 2026-02-04 11:43:32 +00:00
Atif Ali
316269e437 fix(codex): only remove top-level profile key, preserve profiles sections
Use awk to track when we enter a TOML section and only remove 'profile ='
lines that appear at the top level (before any [section] headers). This
ensures user-provided [profiles.*] sections are preserved intact.
2026-02-04 10:32:21 +00:00
Atif Ali
9af62366b7 fix(codex): P2 - override profile when enable_aibridge=true
The previous fix would skip setting profile when one already existed,
breaking enable_aibridge=true with custom base configs. Now when
enable_aibridge=true, we explicitly remove any existing profile line
and set profile="aibridge" (since enable_aibridge=true is an explicit
user intention to use AI Bridge).
2026-02-04 10:29:54 +00:00
Atif Ali
46726a903d style(codex): terraform fmt 2026-02-04 07:24:09 +00:00
Atif Ali
8be5d5e01c fix(codex): address review comments
- P1: Make agentapi module conditional when enable_tasks=false
  - Add standalone coder_script for install when tasks disabled
  - Prevents AgentAPI requirement errors in standalone mode
- P2: Guard against duplicate profile key in config.toml
  - Check if profile already exists before prepending
  - Prevents TOML parsing errors with custom base configs
2026-02-04 07:23:56 +00:00
Atif Ali
fa5fb31454 chore(codex): add TODO for removing deprecated install_agentapi in 5.0.0 2026-02-04 07:20:22 +00:00
Atif Ali
52603754cd docs(codex): simplify AI Bridge section in README 2026-02-04 07:19:19 +00:00
Atif Ali
e18caa5a46 feat(codex): optimize for standalone usage with optional workdir and default AI Bridge profile
- Add 'enable_tasks' variable (defaults to true) for better discoverability
- Make 'workdir' optional when enable_tasks=false for standalone CLI usage
- Keep 'install_agentapi' as deprecated alias for backward compatibility
- Prepend 'profile = "aibridge"' to config.toml when AI Bridge is enabled
  so users can run 'codex' directly without --profile flag
- Update README with standalone usage examples and version 4.2.0
- Add tests for aibridge profile at config top and standalone mode
2026-02-04 07:12:00 +00:00
4 changed files with 144 additions and 42 deletions

View File

@ -1,19 +1,19 @@
--- ---
display_name: Codex CLI display_name: Codex CLI
icon: ../../../../.icons/openai.svg icon: ../../../../.icons/openai.svg
description: Run Codex CLI in your workspace with AgentAPI integration description: Run Codex CLI in your workspace with optional Tasks integration
verified: true verified: true
tags: [agent, codex, ai, openai, tasks, aibridge] tags: [agent, codex, ai, openai, tasks, aibridge]
--- ---
# Codex CLI # Codex CLI
Run Codex CLI in your workspace to access OpenAI's models through the Codex interface, with custom pre/post install scripts. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for Coder Tasks compatibility. Install Codex CLI in your workspace with optional Coder Tasks integration via [AgentAPI](https://github.com/coder/agentapi). The module supports AI Bridge, custom install scripts, and MCP server configuration.
```tf ```tf
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.0" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key openai_api_key = var.openai_api_key
workdir = "/home/coder/project" workdir = "/home/coder/project"
@ -22,47 +22,51 @@ module "codex" {
## Prerequisites ## Prerequisites
- OpenAI API key for Codex access - OpenAI API key for Codex access (not required when `enable_aibridge = true`)
## Examples ## Examples
### Run standalone ### Standalone (no Tasks UI)
Use `enable_tasks = false` to install Codex without AgentAPI/Tasks. `workdir` is optional in this mode.
```tf ```tf
module "codex" { module "codex" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.0" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = "..." openai_api_key = "..."
workdir = "/home/coder/project" enable_tasks = false
report_tasks = false # workdir not required in standalone mode
} }
``` ```
### Usage with AI Bridge ### Usage with AI Bridge
[AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) is a Premium Coder feature that provides centralized LLM proxy management. To use AI Bridge, set `enable_aibridge = true`. Requires Coder version 2.30+ [AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) is a Premium Coder feature that provides centralized LLM proxy management. Set `enable_aibridge = true` to use it (requires Coder 2.30+). When AI Bridge is enabled, authentication uses the workspace owner session token, so `openai_api_key` should be omitted.
For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage with Tasks](#usage-with-tasks) example below.
#### Standalone usage with AI Bridge
```tf ```tf
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.0" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
workdir = "/home/coder/project"
enable_aibridge = true enable_aibridge = true
enable_tasks = false # Standalone mode - just CLI, no Tasks UI
# workdir not required in standalone mode
} }
``` ```
For Tasks integration, add `enable_aibridge = true` to the [Usage with Tasks](#usage-with-tasks) example below.
When `enable_aibridge = true`, the module: When `enable_aibridge = true`, the module:
- Configures Codex to use the AI Bridge profile with `base_url` pointing to `${data.coder_workspace.me.access_url}/api/v2/aibridge/openai/v1` and `env_key` pointing to the workspace owner's session token - Configures Codex to use the AI Bridge profile with `base_url` pointing to `${data.coder_workspace.me.access_url}/api/v2/aibridge/openai/v1` and `env_key` pointing to the workspace owner's session token
- Sets `profile = "aibridge"` at the top of `config.toml` so Codex uses AI Bridge by default
```toml ```toml
profile = "aibridge"
[model_providers.aibridge] [model_providers.aibridge]
name = "AI Bridge" name = "AI Bridge"
base_url = "https://example.coder.com/api/v2/aibridge/openai/v1" base_url = "https://example.coder.com/api/v2/aibridge/openai/v1"
@ -75,9 +79,7 @@ model = "<model>" # as configured in the module input
model_reasoning_effort = "<model_reasoning_effort>" # as configured in the module input model_reasoning_effort = "<model_reasoning_effort>" # as configured in the module input
``` ```
Codex then runs with `--profile aibridge` Codex uses the AI Bridge profile by default, so running `codex` manually does not require `--profile aibridge`.
This allows Codex to route API requests through Coder's AI Bridge instead of directly to OpenAI's API.
Template build will fail if `openai_api_key` is provided alongside `enable_aibridge = true`. Template build will fail if `openai_api_key` is provided alongside `enable_aibridge = true`.
### Usage with Tasks ### Usage with Tasks
@ -94,7 +96,7 @@ data "coder_task" "me" {}
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.0" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = "..." openai_api_key = "..."
ai_prompt = data.coder_task.me.prompt ai_prompt = data.coder_task.me.prompt
@ -112,7 +114,7 @@ This example shows additional configuration options for custom models, MCP serve
```tf ```tf
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.0" version = "4.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = "..." openai_api_key = "..."
workdir = "/home/coder/project" workdir = "/home/coder/project"
@ -142,11 +144,11 @@ module "codex" {
## How it Works ## How it Works
- **Install**: The module installs Codex CLI and sets up the environment - **Install**: Installs Codex CLI and prepares configuration.
- **System Prompt**: If `codex_system_prompt` is set, writes the prompt to `AGENTS.md` in the `~/.codex/` directory - **System Prompt**: If `codex_system_prompt` is set, writes it to `~/.codex/AGENTS.md`.
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI - **Start**: When `enable_tasks = true`, launches Codex via AgentAPI in the selected `workdir`. When `enable_tasks = false`, only the install script runs.
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided) - **Configuration**: Writes `OPENAI_API_KEY` when provided, and sets the AI Bridge profile when `enable_aibridge = true`.
- **Session Continuity**: When `continue = true` (default), the module automatically tracks task sessions in `~/.codex-module/.codex-task-session`. On workspace restart, it resumes the existing session with full conversation history. Set `continue = false` to always start fresh sessions. - **Session Continuity**: When `continue = true` (default), task sessions are tracked in `~/.codex-module/.codex-task-session` for resume on restart. Set `continue = false` to always start fresh sessions.
## Configuration ## Configuration
@ -168,13 +170,14 @@ network_access = true
## Troubleshooting ## Troubleshooting
- Check installation and startup logs in `~/.codex-module/` - Tasks mode: check installation/startup logs in `~/.codex-module/`.
- Ensure your OpenAI API key has access to the specified model - Standalone mode: review the workspace script output for the "Install Codex" script.
- Ensure your OpenAI API key has access to the specified model (unless using AI Bridge).
> [!IMPORTANT] > [!IMPORTANT]
> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker). > To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
> The module automatically configures Codex with your API key and model preferences. > The module automatically configures Codex with your API key and model preferences.
> workdir is a required variable for the module to function correctly. > `workdir` is required when `enable_tasks = true` (default). For standalone CLI usage, set `enable_tasks = false` and `workdir` becomes optional.
## References ## References

View File

@ -41,15 +41,25 @@ interface SetupProps {
const setup = async (props?: SetupProps): Promise<{ id: string }> => { const setup = async (props?: SetupProps): Promise<{ id: string }> => {
const projectDir = "/home/coder/project"; const projectDir = "/home/coder/project";
const { id } = await setupUtil({
moduleDir: import.meta.dir, const moduleVars: Record<string, string> = {
moduleVariables: {
install_codex: props?.skipCodexMock ? "true" : "false", install_codex: props?.skipCodexMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
codex_model: "gpt-4-turbo", codex_model: "gpt-4-turbo",
workdir: "/home/coder", workdir: "/home/coder",
...props?.moduleVariables, ...props?.moduleVariables,
}, };
// For backward compatibility: install_agentapi takes precedence over enable_tasks
// Only set install_agentapi when explicitly installing real AgentAPI
if (props?.skipAgentAPIMock) {
moduleVars.install_agentapi = "true";
}
// Otherwise, let enable_tasks control whether agentapi module runs
// (defaults to true unless explicitly disabled in moduleVariables)
const { id } = await setupUtil({
moduleDir: import.meta.dir,
moduleVariables: moduleVars,
registerCleanup, registerCleanup,
projectDir, projectDir,
skipAgentAPIMock: props?.skipAgentAPIMock, skipAgentAPIMock: props?.skipAgentAPIMock,
@ -481,5 +491,28 @@ describe("codex", async () => {
expect(configToml).toContain( expect(configToml).toContain(
"[profiles.aibridge]\n" + 'model_provider = "aibridge"', "[profiles.aibridge]\n" + 'model_provider = "aibridge"',
); );
// Verify profile = "aibridge" is set at the top of the config
expect(configToml.startsWith('profile = "aibridge"')).toBe(true);
});
test("codex-standalone-mode", async () => {
// Test standalone mode without tasks (enable_tasks = false)
// workdir should default to /home/coder when not explicitly provided
const { id } = await setup({
moduleVariables: {
enable_tasks: "false",
enable_aibridge: "true",
},
});
await execModuleScript(id);
const configToml = await readFileContainer(
id,
"/home/coder/.codex/config.toml",
);
// In standalone mode, config should still have aibridge profile set as default
expect(configToml.startsWith('profile = "aibridge"')).toBe(true);
expect(configToml).toContain("[profiles.aibridge]");
}); });
}); });

View File

@ -38,7 +38,19 @@ variable "icon" {
variable "workdir" { variable "workdir" {
type = string type = string
description = "The folder to run Codex in." description = "The folder to run Codex in. Required when enable_tasks is true."
default = null
validation {
condition = var.workdir != null || !local.tasks_enabled
error_message = "workdir is required when enable_tasks is true. Set workdir or set enable_tasks = false for standalone CLI usage."
}
}
variable "enable_tasks" {
type = bool
description = "Enable Tasks UI for Codex (requires workdir). When false, only installs Codex CLI with config for standalone usage."
default = true
} }
variable "report_tasks" { variable "report_tasks" {
@ -122,10 +134,11 @@ variable "openai_api_key" {
default = "" default = ""
} }
# TODO: Remove install_agentapi in next major version (5.0.0)
variable "install_agentapi" { variable "install_agentapi" {
type = bool type = bool
description = "Whether to install AgentAPI." description = "DEPRECATED: Use enable_tasks instead. Whether to install AgentAPI."
default = true default = null
} }
variable "agentapi_version" { variable "agentapi_version" {
@ -184,7 +197,9 @@ resource "coder_env" "coder_aibridge_session_token" {
} }
locals { locals {
workdir = trimsuffix(var.workdir, "/") # Use enable_tasks, but fall back to install_agentapi if explicitly set (for backward compat)
tasks_enabled = var.install_agentapi != null ? var.install_agentapi : var.enable_tasks
workdir = var.workdir != null ? trimsuffix(var.workdir, "/") : "/home/coder"
app_slug = "codex" app_slug = "codex"
install_script = file("${path.module}/scripts/install.sh") install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh") start_script = file("${path.module}/scripts/start.sh")
@ -204,6 +219,7 @@ locals {
} }
module "agentapi" { module "agentapi" {
count = local.tasks_enabled ? 1 : 0
source = "registry.coder.com/coder/agentapi/coder" source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0" version = "2.0.0"
@ -218,7 +234,7 @@ module "agentapi" {
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
module_dir_name = local.module_dir_name module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi install_agentapi = true
agentapi_subdomain = var.subdomain agentapi_subdomain = var.subdomain
agentapi_version = var.agentapi_version agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script pre_install_script = var.pre_install_script
@ -262,6 +278,37 @@ module "agentapi" {
EOT EOT
} }
output "task_app_id" {
value = module.agentapi.task_app_id # Standalone installation (when tasks are disabled)
resource "coder_script" "standalone_install" {
count = local.tasks_enabled ? 0 : 1
agent_id = var.agent_id
display_name = "Install Codex"
icon = var.icon
run_on_start = true
start_blocks_login = false
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_OPENAI_API_KEY='${var.openai_api_key}' \
ARG_REPORT_TASKS='false' \
ARG_INSTALL='${var.install_codex}' \
ARG_CODEX_VERSION='${var.codex_version}' \
ARG_BASE_CONFIG_TOML='${base64encode(var.base_config_toml)}' \
ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \
ARG_AIBRIDGE_CONFIG='${base64encode(var.enable_aibridge ? local.aibridge_config : "")}' \
ARG_ADDITIONAL_MCP_SERVERS='${base64encode(var.additional_mcp_servers)}' \
ARG_CODER_MCP_APP_STATUS_SLUG='' \
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = local.tasks_enabled ? module.agentapi[0].task_app_id : null
} }

View File

@ -151,6 +151,25 @@ function populate_config_toml() {
write_minimal_default_config "$CONFIG_PATH" write_minimal_default_config "$CONFIG_PATH"
fi fi
# Set aibridge as default profile when AI Bridge is enabled
# This allows users to run `codex` without --profile flag
if [ "$ARG_ENABLE_AIBRIDGE" = "true" ]; then
printf "Setting aibridge as default profile\n"
# Remove any existing top-level profile line (before first section header)
# This only removes profile = ... at top level, not inside [profiles.*] sections
awk '
BEGIN { in_top_level = 1 }
/^\[/ { in_top_level = 0 }
in_top_level && /^profile[ \t]*=/ { next }
{ print }
' "$CONFIG_PATH" > "${CONFIG_PATH}.tmp"
mv "${CONFIG_PATH}.tmp" "$CONFIG_PATH"
# Prepend profile = "aibridge" to the config
local temp_config
temp_config=$(cat "$CONFIG_PATH")
echo -e "profile = \"aibridge\"\n\n$temp_config" > "$CONFIG_PATH"
fi
append_mcp_servers_section "$CONFIG_PATH" append_mcp_servers_section "$CONFIG_PATH"
if [ "$ARG_ENABLE_AIBRIDGE" = "true" ]; then if [ "$ARG_ENABLE_AIBRIDGE" = "true" ]; then