diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 16e6c105b..df486812c 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -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" @@ -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" @@ -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 @@ -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" @@ -75,8 +77,6 @@ model = "" # as configured in the module input 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`. @@ -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 @@ -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" @@ -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 diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index 41bf86ee1..dd70fdc48 100644 --- a/registry/coder-labs/modules/codex/main.tf +++ b/registry/coder-labs/modules/codex/main.tf @@ -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" { @@ -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" @@ -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 diff --git a/registry/coder-labs/modules/codex/main.tftest.hcl b/registry/coder-labs/modules/codex/main.tftest.hcl new file mode 100644 index 000000000..1237df5de --- /dev/null +++ b/registry/coder-labs/modules/codex/main.tftest.hcl @@ -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" + } +}