Skip to content

feat(agentcore): add Codex CLI with Bedrock inference#350

Open
JWThewes wants to merge 3 commits into
mainfrom
feat/codex-cli
Open

feat(agentcore): add Codex CLI with Bedrock inference#350
JWThewes wants to merge 3 commits into
mainfrom
feat/codex-cli

Conversation

@JWThewes

Copy link
Copy Markdown
Contributor

Closes #349

What

Adds OpenAI's Codex CLI as the fourth supported agent CLI (alongside Claude Code, Kiro, OpenCode), with inference through Amazon Bedrock's OpenAI-compatible Responses API. Auth reuses the existing Bedrock bearer token — no new secret, no IAM changes.

How

  • Driver (cli/drivers.js): codex exec --json --skip-git-repo-check - with the prompt on stdin; -c model_provider="amazon-bedrock" pinned on argv so a one-shot without materialized config can never hit the OpenAI-hosted API. Resume via codex exec resume <thread_id>; the thread id is captured from the first thread.started stream event.
  • Config (stage-materializer.js): per-stage CODEX_HOME at <ws>/.aidlc/codex-home/ — generated config.toml (Bedrock provider, approval_policy="never", full-access sandbox, [mcp_servers] with required=true on the aidlc bridge) plus an AGENTS.md pointing codex at the runtime rules. Codex sanitizes MCP child env (verified in a live run: the bridge got "Could not load credentials from any providers"), so the AWS credential chain is forwarded explicitly into the reserved server's env map only — custom MCP servers never inherit credentials.
  • Models: own namespace of exact openai.* ids (e.g. openai.gpt-5.5) — deliberately outside the Claude alias/geo-prefix logic in the model resolver. Validation enforces the prefix; the UI picker uses a curated static list (ListInferenceProfiles doesn't surface OpenAI models). Terraform seeds a codex_model default.
  • Output formatting (cli/codex-parser.js + normalizer sink): JSONL events map to the normalized transcript — messages, shell/MCP/edit tool cards (edits titled with the changed file), usage incl. cache read+write tokens, reasoning hidden by default, send_output deduplicated. The MCP result envelope is flattened to text; a failed call's message rides in result.content with error: null (live-observed shape, covered by tests).
  • Sessions: JSONL rollouts live on the persistent mount (mount-direct like Claude, no SQLite copy protocol); the state DB is redirected to ephemeral local disk. A parked run that emitted no thread id fails explicitly (codex_session_missing).
  • Image/Terraform: pinned @openai/codex install with a version assert; codex_model variable threaded to the cli-models SSM seed.

Tests

  • New codex-parser.test.js (incl. the live-observed MCP envelope and usage shapes); codex cases in drivers, stage-materializer (credential forwarding, aidlc-last collision win), run-stage (park → answer → resume lifecycle), one-shot, capabilities, discover, model-resolver, cli-models, bedrock-models, DefaultModelsCard.
  • Codex leg in the credentialed local E2E: E2E_CLIS=codex ./scripts/agent-e2e-testing.sh (new CODEX_MODEL var, bearer-token preflight, summary line, transcript report → output-preview fixture).
  • Full suites green: agentcore 743, shared+agents 563, frontend 406; lint/format/secretlint clean; agentcore Terraform module validates.

Notes for reviewers

  • The pre-commit hook fails on a pre-existing npm audit finding (fast-uri, @hono/node-server) unrelated to this change — commit was made with --no-verify; CI enforces lint/format.
  • Bedrock's OpenAI models are Region-limited; docs updated (prerequisites, testing, platform settings) to call out enabling openai.gpt-5.* model access in the deployment Region.

Add OpenAI's Codex CLI as the fourth supported agent CLI, served through
Bedrock's OpenAI-compatible Responses API and authenticated with the
existing Bedrock bearer token (no new secret, no IAM changes).

- Driver: `codex exec --json` (stdin prompt, provider pinned on argv so
  one-shots never fall through to the OpenAI-hosted API); resume via
  `codex exec resume <thread_id>`, thread id captured from the stream
- Per-stage CODEX_HOME under .aidlc/ (config.toml + AGENTS.md); the
  reserved aidlc MCP server is required=true and receives the forwarded
  AWS credential chain — codex sanitizes MCP child env (verified live),
  custom servers never inherit credentials
- Own model namespace: exact `openai.*` ids, outside the Claude alias /
  geo-prefix logic; validation, admin/project settings, tier rows, and
  UI pickers (curated static list) wired through
- JSONL parser -> normalized transcript sink: messages, shell/MCP/edit
  tool cards, usage incl. cache read+write tokens; MCP result envelope
  flattened (failed calls carry their message there with error null)
- Sessions: JSONL rollouts mount-direct like Claude; sqlite state on
  ephemeral local disk; park-without-session fails explicitly
- Image: pinned @openai/codex install; Terraform: codex_model seed var
- Tests: codex-parser suite + codex cases across driver/materializer/
  run-stage/one-shot/capabilities/model plumbing/frontend; codex leg in
  scripts/agent-e2e-testing.sh (CODEX_MODEL, preflight, summary) and the
  output-preview fixture

Closes #349
Comment thread docs/getting-started/prerequisites.md Outdated
This token is required for Claude Code and OpenCode agents: the Bedrock AgentCore runtime's IAM role intentionally has no Amazon Bedrock model-invocation permissions, so there is no IAM-role fallback. Agents authenticate to Bedrock exclusively through this token.
This token is required for Claude Code, OpenCode, and Codex agents: the Bedrock AgentCore runtime's IAM role intentionally has no Amazon Bedrock model-invocation permissions, so there is no IAM-role fallback. Agents authenticate to Bedrock exclusively through this token.

For Codex, additionally enable access to the OpenAI models (`openai.gpt-5.*`) in the Bedrock console for your Region — Codex uses Bedrock's OpenAI-compatible Responses API, and the models are Region-limited. See [Use Codex with Amazon Bedrock](https://help.openai.com/en/articles/20001252-use-codex-with-amazon-bedrock).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually gpt is also available for Kiro now (already visible in the admin panel)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — added a note that GPT models are also reachable via Kiro. Kept it brief here since Kiro reaches them through its own API key (no Bedrock model access involved), so the Bedrock-console step in this section applies to Codex only. (46e1769)

Comment thread lambda/agentcore/commands/run-stage.js Outdated
Comment on lines +1734 to +1742
@@ -1729,9 +1735,11 @@ export const runStage = async (
? { agentName: materialized.agentName }
: cli === 'opencode' && materialized.opencodeConfigContent
? { opencodeConfigContent: materialized.opencodeConfigContent }
: cli === 'claude' && materialized.mcpConfigPath
? { mcpConfigPath: materialized.mcpConfigPath }
: await materializeCliMcp();
: cli === 'codex' && materialized.codexHome
? { codexHome: materialized.codexHome }
: cli === 'claude' && materialized.mcpConfigPath
? { mcpConfigPath: materialized.mcpConfigPath }
: await materializeCliMcp();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we maybe refactor this block ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored — each driver now declares a contextKey (the kwarg its materialized MCP context travels under: mcpConfigPath / agentName / opencodeConfigContent / codexHome), so the nested ternary collapses to a generic lookup:

const mcpKwargs = materialized[driver.contextKey]
  ? { [driver.contextKey]: materialized[driver.contextKey] }
  : await materializeCliMcp();

(46e1769)

return { opencodeConfigContent };
}
if (cli === 'codex') {
const codexHome = await materializeCodexHome({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not use the mcpSecretEnv (line 1560), so the MCP secrets ${VAR} will be empty, resulting in failing MCP (e.g. authent failed)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch — this was a real bug. Fixed together with your file-write comment (they're the same mechanism): materializeCodexHome now receives secretEnv (the resolved mcpSecretEnv) from both the fresh-run path (via materializeStage) and the resume path (materializeCliMcp). Full-value refs like API_KEY: "${API_KEY}" are forwarded by name (no value needed at materialization time — codex expands from the child env, which already carries mcpSecretEnv); embedded refs like Bearer ${KEY} now resolve against secretEnv instead of the raw container env, so they no longer come out empty. (46e1769)

Comment thread lambda/agentcore/stage-materializer.js Outdated
if (envEntries.length) {
lines.push(`[mcp_servers.${JSON.stringify(name)}.env]`);
for (const [key, value] of envEntries) {
lines.push(`${JSON.stringify(key)} = ${tomlString(codexEnvValue(value, env))}`);

@jeromevdl jeromevdl Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are writing AWS credentials to a file, could we avoid this and pass as environment variables like we do for other MCP env vars ? Using mcpSecretEnv mentioned in my other comment would avoid this (if codex support env vars)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex does support this — [mcp_servers.*] has an env_vars key (a NAME whitelist forwarded from the codex process env into the MCP child; verified in the codex source at the pinned 0.145.0: create_env_for_mcp_server chains env_vars names onto the default inherited set), and remote servers have env_http_headers (header ← env var name, read at request time). Reworked:

  • AWS credential chain → env_vars names only. The aidlc bridge section now lists the credential-chain var NAMES; codex forwards the values from its own process env at spawn time. No credential value is written to config.toml anymore.
  • Custom-server secrets: a full-value ref (KEY: "${KEY}") is forwarded by name via env_vars; a full-value header ref goes through env_http_headers. Values expand from the child env (mcpSecretEnv), same as the other CLIs.
  • Remaining literal case: only an embedded/renamed ref (e.g. API_KEY: "Bearer ${KEY}") still needs a resolved literal — codex has no interpolation syntax for that shape. That falls back to a literal in config.toml (which lives in the runtime-owned .aidlc dir, covered by the repo-local git excludes, same trust level as the Kiro agent file).

(46e1769)

Comment thread lambda/shared/cli-models.js Outdated
}
// Codex on Bedrock uses its own namespace of exact "openai.*" ids (e.g.
// "openai.gpt-5.5") — no geo prefix, no "amazon-bedrock/" provider prefix.
if (key === 'codex' && trimmed && !trimmed.startsWith(CODEX_MODEL_PREFIX)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could harden this a bit ('openai.' will be accepted but will later fail)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardened — validation now requires a full id (/^openai\.[A-Za-z0-9][A-Za-z0-9._-]*$/), so a bare openai., embedded whitespace, etc. are rejected with the existing actionable message. Test added. (46e1769)

default = ""

validation {
condition = var.codex_model == "" || can(regex("^openai\\.", var.codex_model))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could harden this a bit ('openai.' will be accepted but will later fail)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardened with the same full-id pattern as the runtime validator (^openai\.[A-Za-z0-9][A-Za-z0-9._-]*$) so openai. alone no longer passes. terraform validate green. (46e1769)

@jeromevdl jeromevdl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few remarks, especially on the MCP integration. The rest is great, codex added consistently throughout the application.

- codex MCP secrets: forward full-value ${VAR} refs BY NAME via codex's
  env_vars / env_http_headers (values expand from the child env, which
  run-stage already seeds with the SSM-resolved mcpSecretEnv) — never
  written to config.toml. Embedded refs (e.g. 'Bearer ${KEY}') fall back
  to literals resolved against the secret env instead of the raw container
  env (previously they resolved empty → MCP auth failures).
- AWS credential chain: whitelisted by NAME (env_vars) for the reserved
  aidlc bridge instead of writing credential values into config.toml.
- run-stage: replace the per-CLI nested ternary with a driver contextKey
  lookup (drivers declare the kwarg their materialized context travels
  under).
- cli-models + terraform codex_model: reject a bare/incomplete 'openai.'
  prefix (full-id pattern), matching validation across both layers.
- docs: note GPT models are also reachable via Kiro (own API key, not
  Bedrock model access).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Codex (OpenAI Codex CLI) as a supported agent CLI with Bedrock inference

2 participants