Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions registry/coder-labs/modules/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.2"
version = "4.2.0"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
workdir = "/home/coder/project"
Expand All @@ -32,7 +32,7 @@ module "codex" {
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.2"
version = "4.2.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
workdir = "/home/coder/project"
Expand All @@ -51,7 +51,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.2"
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
enable_aibridge = true
Expand All @@ -63,6 +63,8 @@ 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

```toml
profile = "aibridge" # sets the default profile to aibridge

[model_providers.aibridge]
name = "AI Bridge"
base_url = "https://example.coder.com/api/v2/aibridge/openai/v1"
Expand All @@ -75,8 +77,6 @@ model = "<model>" # as configured in the module input
model_reasoning_effort = "<model_reasoning_effort>" # as configured in the module input
```

Codex then runs with `--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`.

Expand All @@ -94,7 +94,7 @@ data "coder_task" "me" {}

module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.2"
version = "4.2.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_task.me.prompt
Expand All @@ -112,7 +112,7 @@ This example shows additional configuration options for custom models, MCP serve
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.1.2"
version = "4.2.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
workdir = "/home/coder/project"
Expand Down Expand Up @@ -148,6 +148,19 @@ module "codex" {
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided)
- **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.

## State Persistence

AgentAPI can save and restore its conversation state to disk across workspace restarts. This complements `continue` (which resumes the Codex CLI session) by also preserving the AgentAPI-level context. Enabled by default, requires agentapi >= v0.12.0 (older versions skip it with a warning).

To disable:

```tf
module "codex" {
# ... other config
enable_state_persistence = false
}
```

## Configuration

### Default Configuration
Expand Down
47 changes: 27 additions & 20 deletions registry/coder-labs/modules/codex/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.11.8"
default = "v0.12.1"
}

variable "codex_model" {
Expand Down Expand Up @@ -164,6 +164,12 @@ variable "continue" {
default = true
}

variable "enable_state_persistence" {
type = bool
description = "Enable AgentAPI conversation state persistence across restarts."
default = true
}

variable "codex_system_prompt" {
type = string
description = "System instructions written to AGENTS.md in the ~/.codex directory"
Expand Down Expand Up @@ -206,25 +212,26 @@ locals {

module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"

agent_id = var.agent_id
folder = local.workdir
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
web_app_icon = var.icon
web_app_display_name = var.web_app_display_name
cli_app = var.cli_app
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_subdomain = var.subdomain
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = <<-EOT
version = "2.2.0"

agent_id = var.agent_id
folder = local.workdir
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
web_app_icon = var.icon
web_app_display_name = var.web_app_display_name
cli_app = var.cli_app
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_subdomain = var.subdomain
agentapi_version = var.agentapi_version
enable_state_persistence = var.enable_state_persistence
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
Expand Down
187 changes: 187 additions & 0 deletions registry/coder-labs/modules/codex/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
run "test_codex_basic" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
openai_api_key = "test-key"
}

assert {
condition = var.agent_id == "test-agent"
error_message = "Agent ID should be set correctly"
}

assert {
condition = var.workdir == "/home/coder"
error_message = "Workdir should be set correctly"
}

assert {
condition = var.install_codex == true
error_message = "install_codex should default to true"
}

assert {
condition = var.install_agentapi == true
error_message = "install_agentapi should default to true"
}

assert {
condition = var.report_tasks == true
error_message = "report_tasks should default to true"
}

assert {
condition = var.continue == true
error_message = "continue should default to true"
}
}

run "test_enable_state_persistence_default" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
openai_api_key = "test-key"
}

assert {
condition = var.enable_state_persistence == true
error_message = "enable_state_persistence should default to true"
}
}

run "test_disable_state_persistence" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
openai_api_key = "test-key"
enable_state_persistence = false
}

assert {
condition = var.enable_state_persistence == false
error_message = "enable_state_persistence should be false when explicitly disabled"
}
}

run "test_codex_with_aibridge" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
enable_aibridge = true
}

assert {
condition = var.enable_aibridge == true
error_message = "enable_aibridge should be set to true"
}
}

run "test_aibridge_disabled_with_api_key" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
openai_api_key = "test-key"
enable_aibridge = false
}

assert {
condition = var.enable_aibridge == false
error_message = "enable_aibridge should be false"
}

assert {
condition = coder_env.openai_api_key.value == "test-key"
error_message = "OpenAI API key should be set correctly"
}
}

run "test_custom_options" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder/project"
openai_api_key = "test-key"
order = 5
group = "ai-tools"
icon = "/icon/custom.svg"
web_app_display_name = "Custom Codex"
cli_app = true
cli_app_display_name = "Codex Terminal"
subdomain = true
report_tasks = false
continue = false
codex_model = "gpt-4o"
codex_version = "0.1.0"
agentapi_version = "v0.12.0"
}

assert {
condition = var.order == 5
error_message = "Order should be set to 5"
}

assert {
condition = var.group == "ai-tools"
error_message = "Group should be set to 'ai-tools'"
}

assert {
condition = var.icon == "/icon/custom.svg"
error_message = "Icon should be set to custom icon"
}

assert {
condition = var.cli_app == true
error_message = "cli_app should be enabled"
}

assert {
condition = var.subdomain == true
error_message = "subdomain should be enabled"
}

assert {
condition = var.report_tasks == false
error_message = "report_tasks should be disabled"
}

assert {
condition = var.continue == false
error_message = "continue should be disabled"
}

assert {
condition = var.codex_model == "gpt-4o"
error_message = "codex_model should be set to 'gpt-4o'"
}
}

run "test_no_api_key_no_aibridge" {
command = plan

variables {
agent_id = "test-agent"
workdir = "/home/coder"
}

assert {
condition = var.openai_api_key == ""
error_message = "openai_api_key should be empty when not provided"
}

assert {
condition = var.enable_aibridge == false
error_message = "enable_aibridge should default to false"
}
}