Compare commits
11 Commits
main
...
codex-impr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb128fa077 | ||
|
|
7c5f9b2adc | ||
|
|
0a92c5c18f | ||
|
|
a443767ef3 | ||
|
|
316269e437 | ||
|
|
9af62366b7 | ||
|
|
46726a903d | ||
|
|
8be5d5e01c | ||
|
|
fa5fb31454 | ||
|
|
52603754cd | ||
|
|
e18caa5a46 |
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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]");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user