Skip to content

feat: support codex cli #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5bbca30
feat: add codex module
35C4n0r Aug 1, 2025
be3bc0a
feat: bun fmt
35C4n0r Aug 1, 2025
5ac3d7a
fix: fix typo
35C4n0r Aug 1, 2025
37f85c6
feat: add codex svg
35C4n0r Aug 1, 2025
13b776d
feat: update readme
35C4n0r Aug 1, 2025
4e45ffd
chore: update icon name
35C4n0r Aug 2, 2025
66bb40e
chore: rename codex_api_key to openai_api_key
35C4n0r Aug 2, 2025
b08aadf
chore: rename codex_api_key to openai_api_key
35C4n0r Aug 2, 2025
93b16cb
chore: update README.md
35C4n0r Aug 2, 2025
92e6bb5
chore: bump agentapi_version to v0.3.2
35C4n0r Aug 4, 2025
77393b4
fix: update README.md
35C4n0r Aug 5, 2025
b33bbda
wip: apply suggestions
35C4n0r Aug 5, 2025
dc154d0
fix: apply suggestions
35C4n0r Aug 5, 2025
ee138e1
Update registry/coder-labs/modules/codex/scripts/start.sh
35C4n0r Aug 7, 2025
d32f5d9
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 7, 2025
d08a225
Update registry/coder-labs/modules/codex/scripts/start.sh
35C4n0r Aug 7, 2025
a4481f7
Update registry/coder-labs/modules/codex/scripts/start.sh
35C4n0r Aug 7, 2025
2b7d213
Update registry/coder-labs/modules/codex/scripts/start.sh
35C4n0r Aug 7, 2025
79a3f8e
Update registry/coder-labs/modules/codex/scripts/install.sh
35C4n0r Aug 7, 2025
1a28cd2
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 7, 2025
49eafc5
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 7, 2025
723cdc4
Update registry/coder-labs/modules/codex/README.md
35C4n0r Aug 7, 2025
d4d9b73
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 7, 2025
d0846b7
Update registry/coder-labs/modules/codex/README.md
35C4n0r Aug 7, 2025
aa8a562
Merge branch 'main' into feat-codex-cli
35C4n0r Aug 8, 2025
0c6e9f9
Update registry/coder-labs/modules/codex/README.md
35C4n0r Aug 8, 2025
b73f6dd
fix: apply suggestions
35C4n0r Aug 8, 2025
340ecee
Update registry/coder-labs/modules/codex/README.md
35C4n0r Aug 8, 2025
1c85858
fix: README.md
35C4n0r Aug 8, 2025
3624b91
fix: add test for latest version
35C4n0r Aug 8, 2025
ace0930
fix: add support for codex 0.20.0
35C4n0r Aug 11, 2025
58d675e
fix: fix tests
35C4n0r Aug 11, 2025
55b7741
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 11, 2025
ff54181
Update registry/coder-labs/modules/codex/main.tf
35C4n0r Aug 11, 2025
bb460fe
chore: update README.md
35C4n0r Aug 11, 2025
0f23e73
Merge branch 'main' into feat-codex-cli
35C4n0r Aug 11, 2025
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
2 changes: 2 additions & 0 deletions .icons/openai.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions registry/coder-labs/modules/codex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
display_name: Codex CLI
icon: ../../../../.icons/openai.svg
description: Run Codex CLI in your workspace with AgentAPI integration
verified: true
tags: [agent, codex, ai, openai, tasks]
---

# 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.

```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
agentapi_version = "v0.3.3"
folder = "/home/coder/project"
}
```

## Prerequisites

- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template
- OpenAI API key for Codex access

## Usage Example

- Simple usage Example:

```tf
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
codex_model = "o4-mini"
install_codex = true
codex_version = "latest"
folder = "/home/coder/project"
codex_system_prompt = "You are a helpful coding assistant. Start every response with `Codex says:`"
}
```

- Example usage with Tasks:

```tf
# This
data "coder_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
default = ""
description = "Initial prompt for the Codex CLI"
mutable = true
}

module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "1.0.31"
agent_id = coder_agent.example.id
}

module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
folder = "/home/coder/project"
}
```

> **Security Notice**: This module marks the workspace/folder as trusted that allows Codex to work in this workspace without asking for approval.
> Use this module _only_ in trusted environments and be aware of the security implications.

## How it Works

- **Install**: The module installs Codex CLI and sets up the environment
- **System Prompt**: If `codex_system_prompt` and `folder` are set, creates the directory (if needed) and writes the prompt to `AGENTS.md`
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
- **Environment**: Sets `OPENAI_API_KEY` and `CODEX_MODEL` for the CLI (if variables provided)

## Troubleshooting

- Check installation and startup logs in `~/.codex-module/`
- Ensure your OpenAI API key has access to the specified model

> [!IMPORTANT]
> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set, and **you create a `coder_parameter` named `"AI Prompt"` and pass its value to the codex module's `ai_prompt` variable**. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
> The module automatically configures Codex with your API key and model preferences.
> folder is a required variable for the module to function correctly.

## References

- [OpenAI API Documentation](https://platform.openai.com/docs)
- [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
283 changes: 283 additions & 0 deletions registry/coder-labs/modules/codex/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import {
test,
afterEach,
describe,
setDefaultTimeout,
beforeAll,
expect,
} from "bun:test";
import { execContainer, readFileContainer, runTerraformInit } from "~test";
import {
loadTestFile,
writeExecutable,
setup as setupUtil,
execModuleScript,
expectAgentAPIStarted,
} from "../../../coder/modules/agentapi/test-util";
import dedent from "dedent";

let cleanupFunctions: (() => Promise<void>)[] = [];
const registerCleanup = (cleanup: () => Promise<void>) => {
cleanupFunctions.push(cleanup);
};
afterEach(async () => {
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
cleanupFunctions = [];
for (const cleanup of cleanupFnsCopy) {
try {
await cleanup();
} catch (error) {
console.error("Error during cleanup:", error);
}
}
});

interface SetupProps {
skipAgentAPIMock?: boolean;
skipCodexMock?: boolean;
moduleVariables?: Record<string, string>;
agentapiMockScript?: string;
}

const setup = async (props?: SetupProps): Promise<{ id: string }> => {
const projectDir = "/home/coder/project";
const { id } = await setupUtil({
moduleDir: import.meta.dir,
moduleVariables: {
install_codex: props?.skipCodexMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
codex_model: "gpt-4-turbo",
folder: "/home/coder",
...props?.moduleVariables,
},
registerCleanup,
projectDir,
skipAgentAPIMock: props?.skipAgentAPIMock,
agentapiMockScript: props?.agentapiMockScript,
});
if (!props?.skipCodexMock) {
await writeExecutable({
containerId: id,
filePath: "/usr/bin/codex",
content: await loadTestFile(import.meta.dir, "codex-mock.sh"),
});
}
return { id };
};

setDefaultTimeout(60 * 1000);

describe("codex", async () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});

test("happy-path", async () => {
const { id } = await setup();
await execModuleScript(id);
await expectAgentAPIStarted(id);
});

test("install-codex-version", async () => {
const version_to_install = "0.10.0";
const { id } = await setup({
skipCodexMock: true,
moduleVariables: {
install_codex: "true",
codex_version: version_to_install,
},
});
await execModuleScript(id);
const resp = await execContainer(id, [
"bash",
"-c",
`cat /home/coder/.codex-module/install.log`,
]);
expect(resp.stdout).toContain(version_to_install);
});

test("check-latest-codex-version-works", async () => {
const { id } = await setup({
skipCodexMock: true,
skipAgentAPIMock: true,
moduleVariables: {
install_codex: "true",
},
});
await execModuleScript(id);
await expectAgentAPIStarted(id);
});

test("codex-config-toml", async () => {
const settings = dedent`
[mcp_servers.CustomMCP]
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
args = ["exp", "mcp", "server", "app-status-slug=codex"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
enabled = true
type = "stdio"
`.trim();
const { id } = await setup({
moduleVariables: {
extra_codex_settings_toml: settings,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]");
expect(resp).toContain("[mcp_servers.Coder]");
});

test("codex-api-key", async () => {
const apiKey = "test-api-key-123";
const { id } = await setup({
moduleVariables: {
openai_api_key: apiKey,
},
});
await execModuleScript(id);

const resp = await readFileContainer(
id,
"/home/coder/.codex-module/agentapi-start.log",
);
expect(resp).toContain("openai_api_key provided !");
});

test("pre-post-install-scripts", async () => {
const { id } = await setup({
moduleVariables: {
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
post_install_script: "#!/bin/bash\necho 'post-install-script'",
},
});
await execModuleScript(id);
const preInstallLog = await readFileContainer(
id,
"/home/coder/.codex-module/pre_install.log",
);
expect(preInstallLog).toContain("pre-install-script");
const postInstallLog = await readFileContainer(
id,
"/home/coder/.codex-module/post_install.log",
);
expect(postInstallLog).toContain("post-install-script");
});

test("folder-variable", async () => {
const folder = "/tmp/codex-test-folder";
const { id } = await setup({
skipCodexMock: false,
moduleVariables: {
folder,
},
});
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.codex-module/install.log",
);
expect(resp).toContain(folder);
});

test("additional-extensions", async () => {
const additional = dedent`
[mcp_servers.CustomMCP]
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"
args = ["exp", "mcp", "server", "app-status-slug=codex"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
enabled = true
type = "stdio"
`.trim();
const { id } = await setup({
moduleVariables: {
additional_extensions: additional,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]");
expect(resp).toContain("[mcp_servers.Coder]");
});

test("codex-system-prompt", async () => {
const prompt = "This is a system prompt for Codex.";
const { id } = await setup({
moduleVariables: {
codex_system_prompt: prompt,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
expect(resp).toContain(prompt);
});

test("codex-system-prompt-skip-append-if-exists", async () => {
const prompt_1 = "This is a system prompt for Codex.";
const prompt_2 = "This is a system prompt for Goose.";
const prompt_3 = dedent`
This is a system prompt for Codex.
This is a system prompt for Gemini.
`.trim();
const pre_install_script = dedent`
#!/bin/bash
echo -e "${prompt_3}" >> /home/coder/AGENTS.md
`.trim();

const { id } = await setup({
moduleVariables: {
pre_install_script,
codex_system_prompt: prompt_2,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md");
expect(resp).toContain(prompt_1);
expect(resp).toContain(prompt_2);

// Re-run with a prompt that already exists, it should not append again
const { id: id_2 } = await setup({
moduleVariables: {
pre_install_script,
codex_system_prompt: prompt_1,
},
});
await execModuleScript(id_2);
const resp_2 = await readFileContainer(id_2, "/home/coder/AGENTS.md");
expect(resp_2).toContain(prompt_1);
const count = (resp_2.match(new RegExp(prompt_1, "g")) || []).length;
expect(count).toBe(1);
});

test("codex-ai-task-prompt", async () => {
const prompt = "This is a system prompt for Codex.";
const { id } = await setup({
moduleVariables: {
ai_prompt: prompt,
},
});
await execModuleScript(id);
const resp = await execContainer(id, [
"bash",
"-c",
`cat /home/coder/.codex-module/agentapi-start.log`,
]);
expect(resp.stdout).toContain(
`Complete the task at hand and at every step of the way, report tasks to Coder with proper summary and statuses. Your task at hand: ${prompt}`,
);
});

test("start-without-prompt", async () => {
const { id } = await setup();
await execModuleScript(id);
const prompt = await execContainer(id, [
"ls",
"-l",
"/home/coder/AGENTS.md",
]);
expect(prompt.exitCode).not.toBe(0);
expect(prompt.stderr).toContain("No such file or directory");
});
});
Loading
Loading