-
Notifications
You must be signed in to change notification settings - Fork 42
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
35C4n0r
wants to merge
9
commits into
coder:main
Choose a base branch
from
35C4n0r:feat-codex-cli
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+709
−0
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5bbca30
feat: add codex module
35C4n0r be3bc0a
feat: bun fmt
35C4n0r 5ac3d7a
fix: fix typo
35C4n0r 37f85c6
feat: add codex svg
35C4n0r 13b776d
feat: update readme
35C4n0r 4e45ffd
chore: update icon name
35C4n0r 66bb40e
chore: rename codex_api_key to openai_api_key
35C4n0r b08aadf
chore: rename codex_api_key to openai_api_key
35C4n0r 93b16cb
chore: update README.md
35C4n0r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
--- | ||
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.0" | ||
} | ||
``` | ||
|
||
## Prerequisites | ||
|
||
- You must add the [Coder Login](https://registry.coder.com/modules/coder-login/coder) 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:`" | ||
agentapi_version = "v0.3.0" | ||
} | ||
``` | ||
|
||
- 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 = "/Users/jkmr/Documents/work/registry/registry/coder-labs/modules/codex" | ||
agent_id = coder_agent.example.id | ||
openai_api_key = "..." | ||
ai_prompt = data.coder_parameter.ai_prompt.value | ||
agentapi_version = "v0.3.0" | ||
} | ||
``` | ||
|
||
## 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 logs in `/home/coder/.codex-module/` for install/start output | ||
- 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 an `ai_prompt` `coder_parameter` and pass it's value to codex `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. | ||
> Tasks are run with `--dangerously-bypass-approvals-and-sandbox` | ||
|
||
## 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
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"; | ||
|
||
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", | ||
...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 = "1.0.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 || true`, | ||
]); | ||
expect(resp.stdout).toContain(version_to_install); | ||
}); | ||
|
||
test("codex-config-toml", async () => { | ||
const settings = '[mcp_servers.CustomMCP]\n' + | ||
'command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"\n' + | ||
'args = ["exp", "mcp", "server", "app-status-slug=codex"]\n' + | ||
'env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }\n' + | ||
'description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."\n' + | ||
'enabled = true\n' + | ||
'type = "stdio"'; | ||
const { id } = await setup({ | ||
moduleVariables: { | ||
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 = '[mcp_servers.CustomMCP]\n' + | ||
'command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64"\n' + | ||
'args = ["exp", "mcp", "server", "app-status-slug=codex"]\n' + | ||
'env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }\n' + | ||
'description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."\n' + | ||
'enabled = true\n' + | ||
'type = "stdio"'; | ||
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-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 || true`, | ||
]); | ||
expect(resp.stdout).toContain(`Every step of the way, report tasks to Coder with proper descriptions and statuses, when each part of the task is finished report with . 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"); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we instead use the module inside the module? I know we have the same requirement in other modules too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matifali So, instead of the user adding this, we pre-include it in our module ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Needs to test though how it works.