## Summary This PR hardens package-manager usage across the repo to reduce dependency supply-chain risk. It also removes the stale `codex-cli` Docker path, which was already broken on `main`, instead of keeping a bitrotted container workflow alive. ## What changed - Updated pnpm package manager pins and workspace install settings. - Removed stale `codex-cli` Docker assets instead of trying to keep a broken local container path alive. - Added uv settings and lockfiles for the Python SDK packages. - Updated Python SDK setup docs to use `uv sync`. ## Why This is primarily a security hardening change. It reduces package-install and supply-chain risk by ensuring dependency installs go through pinned package managers, committed lockfiles, release-age settings, and reviewed build-script controls. For `codex-cli`, the right follow-up was to remove the local Docker path rather than keep patching it: - `codex-cli/Dockerfile` installed `codex.tgz` with `npm install -g`, which bypassed the repo lockfile and age-gated pnpm settings. - The local `codex-cli/scripts/build_container.sh` helper was already broken on `main`: it called `pnpm run build`, but `codex-cli/package.json` does not define a `build` script. - The container path itself had bitrotted enough that keeping it would require extra packaging-specific behavior that was not otherwise needed by the repo. ## Gaps addressed - Global npm installs bypassed the repo lockfile in Docker and CLI reinstall paths, including `codex-cli/Dockerfile` and `codex-cli/bin/codex.js`. - CI and Docker pnpm installs used `--frozen-lockfile`, but the repo was missing stricter pnpm workspace settings for dependency build scripts. - Python SDK projects had `pyproject.toml` metadata but no committed `uv.lock` coverage or uv age/index settings in `sdk/python` and `sdk/python-runtime`. - The secure devcontainer install path used npm/global install behavior without a local locked package-manager boundary. - The local `codex-cli` Docker helper was already broken on `main`, so this PR removes that stale Docker path instead of preserving a broken surface. - pnpm was already pinned, but not to the current repo-wide pnpm version target. ## Verification - `pnpm install --frozen-lockfile` - `.devcontainer/codex-install`: `pnpm install --prod --frozen-lockfile` - `.devcontainer/codex-install`: `./node_modules/.bin/codex --version` - `sdk/python`: `uv lock --check`, `uv sync --locked --all-extras --dry-run`, `uv build` - `sdk/python-runtime`: `uv lock --check`, `uv sync --locked --dry-run`, `uv build --wheel` - `pnpm -r --filter ./sdk/typescript run build` - `pnpm -r --filter ./sdk/typescript run lint` - `pnpm -r --filter ./sdk/typescript run test` - `node --check codex-cli/bin/codex.js` - `docker build -f .devcontainer/Dockerfile.secure -t codex-secure-test .` - `cargo build -p codex-cli` - repo-wide package-manager audit
Codex SDK
Embed the Codex agent in your workflows and apps.
The TypeScript SDK wraps the codex CLI from @openai/codex. It spawns the CLI and exchanges JSONL events over stdin/stdout.
Installation
npm install @openai/codex-sdk
Requires Node.js 18+.
Quickstart
import { Codex } from "@openai/codex-sdk";
const codex = new Codex();
const thread = codex.startThread();
const turn = await thread.run("Diagnose the test failure and propose a fix");
console.log(turn.finalResponse);
console.log(turn.items);
Call run() repeatedly on the same Thread instance to continue that conversation.
const nextTurn = await thread.run("Implement the fix");
Streaming responses
run() buffers events until the turn finishes. To react to intermediate progress—tool calls, streaming responses, and file change notifications—use runStreamed() instead, which returns an async generator of structured events.
const { events } = await thread.runStreamed("Diagnose the test failure and propose a fix");
for await (const event of events) {
switch (event.type) {
case "item.completed":
console.log("item", event.item);
break;
case "turn.completed":
console.log("usage", event.usage);
break;
}
}
Structured output
The Codex agent can produce a JSON response that conforms to a specified schema. The schema can be provided for each turn as a plain JSON object.
const schema = {
type: "object",
properties: {
summary: { type: "string" },
status: { type: "string", enum: ["ok", "action_required"] },
},
required: ["summary", "status"],
additionalProperties: false,
} as const;
const turn = await thread.run("Summarize repository status", { outputSchema: schema });
console.log(turn.finalResponse);
You can also create a JSON schema from a Zod schema using the zod-to-json-schema package and setting the target to "openAi".
const schema = z.object({
summary: z.string(),
status: z.enum(["ok", "action_required"]),
});
const turn = await thread.run("Summarize repository status", {
outputSchema: zodToJsonSchema(schema, { target: "openAi" }),
});
console.log(turn.finalResponse);
Attaching images
Provide structured input entries when you need to include images alongside text. Text entries are concatenated into the final prompt while image entries are passed to the Codex CLI via --image.
const turn = await thread.run([
{ type: "text", text: "Describe these screenshots" },
{ type: "local_image", path: "./ui.png" },
{ type: "local_image", path: "./diagram.jpg" },
]);
Resuming an existing thread
Threads are persisted in ~/.codex/sessions. If you lose the in-memory Thread object, reconstruct it with resumeThread() and keep going.
const savedThreadId = process.env.CODEX_THREAD_ID!;
const thread = codex.resumeThread(savedThreadId);
await thread.run("Implement the fix");
Working directory controls
Codex runs in the current working directory by default. To avoid unrecoverable errors, Codex requires the working directory to be a Git repository. You can skip the Git repository check by passing the skipGitRepoCheck option when creating a thread.
const thread = codex.startThread({
workingDirectory: "/path/to/project",
skipGitRepoCheck: true,
});
Controlling the Codex CLI environment
By default, the Codex CLI inherits the Node.js process environment. Provide the optional env parameter when instantiating the
Codex client to fully control which variables the CLI receives—useful for sandboxed hosts like Electron apps.
const codex = new Codex({
env: {
PATH: "/usr/local/bin",
},
});
The SDK still injects its required variables (such as CODEX_API_KEY) on top of the environment you provide. If you set
baseUrl, the SDK passes it as a --config openai_base_url=... override.
Passing --config overrides
Use the config option to provide additional Codex CLI configuration overrides. The SDK accepts a JSON object, flattens it
into dotted paths, and serializes values as TOML literals before passing them as repeated --config key=value flags.
const codex = new Codex({
config: {
show_raw_agent_reasoning: true,
sandbox_workspace_write: { network_access: true },
},
});
Thread options still take precedence for overlapping settings because they are emitted after these global overrides.