summaryrefslogtreecommitdiff
path: root/gnu/packages/codex.scm
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/packages/codex.scm')
-rw-r--r--gnu/packages/codex.scm411
1 files changed, 411 insertions, 0 deletions
diff --git a/gnu/packages/codex.scm b/gnu/packages/codex.scm
new file mode 100644
index 00000000000..70f8bafd7d8
--- /dev/null
+++ b/gnu/packages/codex.scm
@@ -0,0 +1,411 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Danny Milosavljevic <dannym@friendly-machines.com>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19;;; This module is separate from (gnu packages rust-apps) to avoid a
20;;; circular module dependency: (gnu packages rust-sources), which
21;;; defines rust-codex-0.98.0, transitively loads (gnu packages
22;;; rust-apps) through its #:use-module chain. If the codex package
23;;; lived in rust-apps.scm, loading rust-sources would trigger loading
24;;; rust-apps before rust-codex-0.98.0 is defined, causing an unbound
25;;; variable error.
26
27(define-module (gnu packages codex)
28 #:use-module ((guix licenses) #:prefix license:)
29 #:use-module (guix gexp)
30 #:use-module (guix packages)
31 #:use-module (guix download)
32 #:use-module (guix git-download)
33 #:use-module (guix build-system cargo)
34 #:use-module (gnu packages)
35 #:use-module (gnu packages bash)
36 #:use-module (gnu packages base)
37 #:use-module (gnu packages cmake)
38 #:use-module (gnu packages compression)
39 #:use-module (gnu packages libunwind)
40 #:use-module (gnu packages llvm)
41 #:use-module (gnu packages perl)
42 #:use-module (gnu packages pkg-config)
43 #:use-module (gnu packages python)
44 #:use-module (gnu packages rust-sources)
45 #:use-module (gnu packages sqlite)
46 #:use-module (gnu packages tls)
47 #:use-module (gnu packages version-control))
48
49(define-public codex
50 (package
51 (name "codex")
52 (version (package-version rust-codex-0.98.0))
53 (source
54 (origin
55 (inherit (package-source rust-codex-0.98.0))
56 (patches (search-patches
57 "codex-0.98.0-remove-patch-sections.patch"
58 "rust-codex-0.98.0-test-shebangs.patch"
59 "rust-codex-0.98.0-test-timeout.patch"))))
60 (build-system cargo-build-system)
61 (arguments
62 (list
63 #:install-source? #f
64 #:cargo-install-paths '(list "cli" "exec" "exec-server"
65 "linux-sandbox" "mcp-server" "network-proxy"
66 "app-server" "tui")
67 ;; schema_fixtures_match_generated (upstream fixture is stale:
68 ;; FileChange::Update in codex-protocol gained old_content,
69 ;; new_content, move_path fields but the committed JSON schema
70 ;; fixture was not regenerated).
71 #:cargo-test-flags '(list "--workspace"
72 "--exclude" "codex-app-server-protocol"
73 "--"
74 ;; These tests exercise sandbox denial and
75 ;; escalation, which requires Landlock to
76 ;; cleanly deny filesystem access. Inside the
77 ;; build container Landlock returns NotEnforced
78 ;; and the sandbox binary panics instead.
79 ;; Disabling Landlock would not help either,
80 ;; since these tests need a working sandbox to
81 ;; have anything to deny and escalate.
82 "--skip" "sandbox_denied_shell_returns_original_output"
83 "--skip" "shell_escalated_permissions_rejected_then_ok"
84 "--skip" "unified_exec_runs_under_sandbox"
85 ;; These tests (in codex-exec) directly call
86 ;; spawn_command_under_linux_sandbox to verify
87 ;; that python and bash work correctly inside
88 ;; the Landlock sandbox. The sandbox binary
89 ;; (codex-exec) panics with LandlockRestrict
90 ;; (exit code 101) before the inner command
91 ;; even starts.
92 "--skip" "python_getpwuid_works_under_sandbox"
93 "--skip" "python_multiprocessing_lock_works_under_sandbox"
94 "--skip" "sandbox_distinguishes_command_and_policy_cwds"
95 ;; These linux-sandbox tests directly invoke
96 ;; the Landlock sandbox via
97 ;; process_exec_tool_call; same root cause.
98 "--skip" "test_writable_root"
99 "--skip" "test_timeout"
100 "--skip" "test_root_read"
101 "--skip" "test_dev_null_write"
102 "--skip" "test_no_new_privs_is_enabled"
103 ;; This test iterates many approval scenarios;
104 ;; one of them
105 ;; (danger_full_access_on_request_allows_network)
106 ;; runs a command through the Landlock sandbox
107 ;; binary, which panics with LandlockRestrict
108 ;; inside the build container. Cargo --skip
109 ;; cannot target individual scenarios, so we
110 ;; skip the entire matrix.
111 "--skip" "approval_matrix_covers_all_modes"
112 ;; This test verifies session-level patch
113 ;; approval caching: approve once, skip
114 ;; future prompts for the same file. When
115 ;; Landlock is unavailable (as in the Guix
116 ;; build container) the sandbox binary panics,
117 ;; triggering the escalation-retry path, which
118 ;; interferes with the approval cache and
119 ;; causes a spurious re-prompt on the second
120 ;; patch.
121 "--skip" "approving_apply_patch_for_session_skips_future_prompts_for_same_file"
122 ;; These tests expect to interrupt a
123 ;; long-running 'sleep 60' and receive
124 ;; TurnAborted. Default test config is
125 ;; OnRequest + ReadOnly. What happens:
126 ;;
127 ;; 1. ReadOnly wraps the command with
128 ;; codex-linux-sandbox (Landlock-based).
129 ;; 2. Landlock is unavailable in the Guix
130 ;; build container, so the sandbox
131 ;; binary exits instantly (~1 ms).
132 ;; 3. Orchestrator gets SandboxErr::Denied.
133 ;; wants_no_sandbox_approval(OnRequest)
134 ;; returns false (sandboxing.rs:222),
135 ;; so no escalation -- denial returned
136 ;; directly.
137 ;; 4. ToolEmitter::finish sends the error
138 ;; to the mock model as
139 ;; function_call_output.
140 ;; 5. Second mock SSE response fires,
141 ;; turn finishes with TurnComplete.
142 ;; 6. Op::Interrupt arrives 100 ms later,
143 ;; but the turn is already done --
144 ;; TurnAborted is never emitted,
145 ;; test times out.
146 ;;
147 ;; The similar interrupt_long_running_tool_
148 ;; emits_turn_aborted passes because it
149 ;; sends the interrupt with no delay and
150 ;; has only one mock response (so the turn
151 ;; cannot complete first).
152 "--skip" "interrupt_persists_turn_aborted_marker_in_next_request"
153 "--skip" "interrupt_tool_records_history_entries"
154 ;; Upstream bug: test hardcodes "0.0.0" in the
155 ;; expected user-agent string but the workspace
156 ;; version is "0.98.0".
157 "--skip" "get_user_agent_returns_current_codex_user_agent"
158 ;; Same upstream bug: mcp-server tests
159 ;; check the initialize response which
160 ;; includes "version": "0.0.0" but the
161 ;; server returns "0.98.0".
162 "--skip" "test_codex_tool_passes_base_instructions"
163 "--skip" "test_shell_command_approval_triggers_elicitation"
164 "--skip" "test_patch_approval_triggers_elicitation"
165 ;; These codex-exec-server tests need
166 ;; "dotslash", a Meta tool that lazily
167 ;; downloads pre-built binaries from a
168 ;; JSON manifest. The test helper
169 ;; create_transport runs
170 ;; `dotslash -- fetch <path>` to obtain a
171 ;; custom bash binary described in
172 ;; exec-server/tests/suite/bash.
173 ;; dotslash is not available in the build
174 ;; container.
175 "--skip" "list_tools"
176 "--skip" "accept_elicitation_for_prompt_rule"
177 ;;; Test isolation bug: each test in
178 ;;; state/src/runtime.rs calls
179 ;;; unique_temp_dir() to get its own
180 ;;; temporary directory (and thus its
181 ;;; own SQLite database). That function
182 ;;; names directories using the current
183 ;;; nanosecond timestamp, so when tests
184 ;;; run in parallel several can receive
185 ;;; the same name and open the same
186 ;;; database. The initial SQLite
187 ;;; migration runs CREATE TABLE threads
188 ;;; (without IF NOT EXISTS), so any init
189 ;;; after the first panics with "table
190 ;;; threads already exists". Any of
191 ;;; these tests
192 ;;; can be the victim.
193 "--skip" "init_removes_legacy_state_db_files"
194 "--skip" "upsert_and_get_thread_memory"
195 "--skip" "get_last_n_thread_memories_for_cwd_matches_exactly"
196 "--skip" "upsert_thread_memory_errors_for_unknown_thread"
197 "--skip" "get_last_n_thread_memories_for_cwd_zero_returns_empty"
198 "--skip" "get_last_n_thread_memories_for_cwd_does_not_prefix_match"
199 "--skip" "deleting_thread_cascades_thread_memory")
200 #:cargo-package-crates
201 ''(;;; Tier 0: No internal deps.
202 "codex-async-utils"
203 "codex-client"
204 "codex-execpolicy"
205 "codex-file-search"
206 "codex-git"
207 "codex-keyring-store"
208 "codex-utils-absolute-path"
209 "codex-utils-cache"
210 "codex-utils-cargo-bin"
211 "codex-utils-home-dir"
212 "codex-utils-json-to-toml"
213 "codex-utils-pty"
214 "codex-utils-readiness"
215 "codex-utils-string"
216 "codex-backend-openapi-models"
217 "codex-process-hardening"
218 "codex-ansi-escape"
219 ;;; Tier 1: Depends on tier 0.
220 "codex-utils-image"
221 "codex-apply-patch"
222 "codex-protocol"
223 "codex-windows-sandbox"
224 "codex-api"
225 "codex-experimental-api-macros"
226 "codex-secrets"
227 "codex-execpolicy-legacy"
228 "codex-debug-client"
229 ;;; Tier 2.
230 "codex-app-server-protocol"
231 "codex-rmcp-client"
232 "codex-otel"
233 "codex-state"
234 "codex-core"
235 "codex-linux-sandbox"
236 "codex-feedback"
237 ;;; Tier 3.
238 "codex-arg0"
239 "codex-lmstudio"
240 "codex-login"
241 "codex-ollama"
242 "codex-common"
243 "codex-mcp-server"
244 "codex-backend-client"
245 "codex-responses-api-proxy"
246 ;;; Tier 4.
247 "codex-cloud-requirements"
248 "codex-exec"
249 "codex-exec-server"
250 "codex-stdio-to-uds"
251 "codex-network-proxy"
252 "codex-chatgpt"
253 "codex-cloud-tasks-client"
254 ;;; Tier 5.
255 "codex-app-server"
256 "codex-app-server-test-client"
257 "codex-tui"
258 ;;; Tier 6.
259 "codex-cloud-tasks"
260 ;; The main executable.
261 "codex-cli")
262 #:phases
263 #~(modify-phases %standard-phases
264 (add-after 'unpack 'chdir-to-workspace
265 (lambda _
266 (chdir "codex-rs")))
267 (add-after 'chdir-to-workspace 'update-version-in-snapshots
268 (lambda _
269 ;; Snapshot test files contain hardcoded v0.0.0 version strings.
270 ;; Update them to match the actual package version.
271 (let ((snap-files (find-files "." "\\.snap$")))
272 (substitute* snap-files
273 (("\\(v0\\.0\\.0\\) ") "(v0.98.0)")))))
274 (add-after 'chdir-to-workspace 'patch-git-deps-to-vendor
275 (lambda _
276 ;; Replace git dependencies with version references so cargo
277 ;; resolves them from the vendored sources.
278 (substitute* "Cargo.toml"
279 (("nucleo = \\{ git = [^}]+\\}")
280 "nucleo = \"0.5.0\"")
281 (("runfiles = \\{ git = [^}]+\\}")
282 "runfiles = \"0.1.0\""))))
283 (add-after 'chdir-to-workspace 'add-version-to-workspace-deps
284 (lambda _
285 ;; cargo package requires all dependencies to have versions.
286 ;; cargo package requires all dependencies to have versions.
287 ;; Add version = "0.98.0" to internal path dependencies.
288 (let ((cargo-files (find-files "." "^Cargo\\.toml$")))
289 (substitute* cargo-files
290 ;; Handle inline deps: name = { path = "..." }
291 (("(codex-[a-z0-9-]+) = \\{ path = " all name)
292 (string-append name " = { version = \"0.98.0\", path = "))
293 ;; Handle inline deps with package: name = { package = "...", path = "..." }
294 (("(codex-[a-z0-9-]+) = \\{ package = " all name)
295 (string-append name " = { version = \"0.98.0\", package = "))
296 ;; Handle section deps: [dependencies.X] with path = "..."
297 (("^(path = \"\\.\\./[^\"]*\")" all path-line)
298 (string-append path-line "\nversion = \"0.98.0\""))))))
299 (add-after 'chdir-to-workspace 'patch-hardcoded-paths
300 (lambda* (#:key inputs #:allow-other-keys)
301 (let ((bash-bin (string-append
302 (assoc-ref inputs "bash-minimal") "/bin"))
303 (coreutils-bin (string-append
304 (assoc-ref inputs "coreutils") "/bin"))
305 (git-bin (string-append
306 (assoc-ref inputs "git-minimal") "/bin"))
307 (sed-bin (string-append
308 (assoc-ref inputs "sed") "/bin"))
309 ;; Include .policy files: the execpolicy-legacy
310 ;; crate embeds default.policy via include_str!
311 ;; at compile time, so its paths must also be
312 ;; patched.
313 (rs-files (find-files "." "\\.(rs|policy)$")))
314 (substitute* rs-files
315 (("\"/bin/bash\"")
316 (string-append "\"" bash-bin "/bash\""))
317 (("\"/bin/sh\"")
318 (string-append "\"" bash-bin "/sh\""))
319 (("\"/usr/bin/bash\"")
320 (string-append "\"" bash-bin "/bash\""))
321 (("\"/usr/bin/sh\"")
322 (string-append "\"" bash-bin "/sh\""))
323 ;;; bash/sh with inline arguments, e.g. "/bin/bash -i".
324 (("\"/bin/bash ")
325 (string-append "\"" bash-bin "/bash "))
326 (("\"/bin/sh ")
327 (string-append "\"" bash-bin "/sh "))
328 ;; coreutils.
329 (("\"/bin/(cat|cp|date|echo|head|ls|rm|sleep|true|touch)\"" all cmd)
330 (string-append "\"" coreutils-bin "/" cmd "\""))
331 ;; coreutils.
332 (("\"/usr/bin/(cat|cp|head|ls|touch|true)\"" all cmd)
333 (string-append "\"" coreutils-bin "/" cmd "\""))
334 ;; coreutils with inline arguments
335 ;; like "/bin/echo END-EVENT".
336 (("\"/bin/(cat|cp|date|echo|head|ls|rm|sleep|true|touch) " all cmd)
337 (string-append "\"" coreutils-bin "/" cmd " "))
338 (("\"/usr/bin/git\"")
339 (string-append "\"" git-bin "/git\""))
340 (("\"/usr/bin/sed\"")
341 (string-append "\"" sed-bin "/sed\"")))
342 ;; @SHELL@ placeholder from test-shebangs patch
343 (substitute*
344 (list "rmcp-client/src/program_resolver.rs"
345 "tui/src/external_editor.rs")
346 (("@SHELL@")
347 (string-append bash-bin "/sh")))
348 ;; shebang in test-only file
349 (substitute*
350 "core/tests/suite/user_notification.rs"
351 (("#!/bin/bash")
352 (string-append "#!" bash-bin "/bash"))))))
353 (add-before 'check 'set-home
354 (lambda _
355 (setenv "HOME" "/tmp")
356 (setenv "USER" "nixbld"))))))
357 (native-inputs (list clang ;bindgen uses libclang to parse BoringSSL's C headers
358 cmake-minimal ;BoringSSL is compiled from C source
359 libunwind ;BoringSSL tests verify stack unwinding in assembly
360 perl python-minimal ;for tests
361 pkg-config))
362 (inputs (cons* bash-minimal coreutils git-minimal sed
363 openssl sqlite `(,zstd "lib")
364 (cargo-inputs 'codex)))
365 (home-page "https://github.com/openai/codex")
366 (synopsis "AI-assisted coding CLI and TUI")
367 (description
368 "Codex is an AI-powered coding assistant that runs in the terminal.
369It provides an interactive TUI for conversations with AI models, with
370support for shell command execution, file editing, and code generation.
371Configure providers via @file{~/.codex/config.toml}.")
372 (license license:asl2.0)))
373
374(define-public codex-acp
375 (package
376 (name "codex-acp")
377 (version "0.9.2")
378 (source
379 (origin
380 (method git-fetch)
381 (uri (git-reference
382 (url "https://github.com/zed-industries/codex-acp")
383 (commit (string-append "v" version))))
384 (file-name (git-file-name name version))
385 (sha256
386 (base32 "190sq6s6jfz8dkj1y8305r7x6ln86qqr2j1bnfjci7f1x2wyzmsj"))
387 (patches (search-patches "codex-acp-0.9.2-remove-patch-sections.patch"
388 "codex-acp-0.9.2-replace-result-flatten.patch"))))
389 (build-system cargo-build-system)
390 (arguments
391 (list
392 #:install-source? #f
393 #:phases
394 #~(modify-phases %standard-phases
395 (add-after 'unpack 'patch-codex-deps
396 (lambda _
397 ;; Rewrite git dependencies to use vendored sources from rust-codex
398 (substitute* "Cargo.toml"
399 (("git = \"https://github.com/zed-industries/codex\", branch = \"acp\"")
400 "version = \"0.0.0\"")))))))
401 (native-inputs (list pkg-config))
402 (inputs (cons* openssl sqlite `(,zstd "lib") (cargo-inputs 'codex-acp)))
403 (home-page "https://github.com/zed-industries/codex-acp")
404 (synopsis "ACP-compatible agent bridging Zed Codex with ACP clients")
405 (description
406 "This package provides an Agent Client Protocol (ACP) compatible agent
407that bridges the Zed Codex runtime with ACP clients over stdio. It
408supports multiple LLM providers through configuration in
409@file{~/.codex/config.toml} and integrates with MCP servers for filesystem
410operations.")
411 (license license:asl2.0)))