mirror of
https://github.com/openai/codex.git
synced 2026-04-23 22:24:57 +00:00
Compare commits
372 Commits
caseych/fi
...
dev/jlewi/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78cdff9465 | ||
|
|
fe2ff29bef | ||
|
|
64f78a3dc5 | ||
|
|
45cfcb0c7a | ||
|
|
b6cc089db5 | ||
|
|
2b362f9c51 | ||
|
|
c97c42d317 | ||
|
|
60a80828e7 | ||
|
|
a050c5ee91 | ||
|
|
dfbd21e294 | ||
|
|
e8cdf68bdd | ||
|
|
54d765635a | ||
|
|
11e16ef2e8 | ||
|
|
bb02604501 | ||
|
|
609b6168b3 | ||
|
|
5bc4c5c997 | ||
|
|
ee4138c363 | ||
|
|
3ffd99cd3d | ||
|
|
fa5d14a81f | ||
|
|
032b8dddb6 | ||
|
|
03b2465591 | ||
|
|
b09b58ce2d | ||
|
|
285f4ea817 | ||
|
|
4c72e62d0b | ||
|
|
1fc8aa0e16 | ||
|
|
2b8d29ac0d | ||
|
|
ec21e1fd01 | ||
|
|
25fbd7e40e | ||
|
|
873e466549 | ||
|
|
20f43c1e05 | ||
|
|
0071968829 | ||
|
|
ea650a91b3 | ||
|
|
19f0d196d1 | ||
|
|
390b644b21 | ||
|
|
28a9807f84 | ||
|
|
9313c49e4c | ||
|
|
258ba436f1 | ||
|
|
716f7b0428 | ||
|
|
c74190a622 | ||
|
|
213756c9ab | ||
|
|
bb95ec3ec6 | ||
|
|
af568afdd5 | ||
|
|
38e648ca67 | ||
|
|
54d3ad1ede | ||
|
|
7880414a27 | ||
|
|
3807807f91 | ||
|
|
3bbc1ce003 | ||
|
|
4e119a3b38 | ||
|
|
46b653e73c | ||
|
|
f7ef9599ed | ||
|
|
a16a9109d7 | ||
|
|
2238c16a91 | ||
|
|
c25c0d6e9e | ||
|
|
313fb95989 | ||
|
|
4e27a87ec6 | ||
|
|
ae8a3be958 | ||
|
|
bc53d42fd9 | ||
|
|
178d2b00b1 | ||
|
|
48144a7fa4 | ||
|
|
fce0f76d57 | ||
|
|
65f631c3d6 | ||
|
|
61429a6c10 | ||
|
|
3d1abf3f3d | ||
|
|
bede1d9e23 | ||
|
|
e39ddc61b1 | ||
|
|
b94366441e | ||
|
|
e02fd6e1d3 | ||
|
|
f4d0cbfda6 | ||
|
|
343d1af3da | ||
|
|
5037a2d199 | ||
|
|
142681ef93 | ||
|
|
71923f43a7 | ||
|
|
61dfe0b86c | ||
|
|
ed977b42ac | ||
|
|
8e24d5aaea | ||
|
|
2ffb32db98 | ||
|
|
f4f6eca871 | ||
|
|
d65deec617 | ||
|
|
307e427a9b | ||
|
|
5b71e5104f | ||
|
|
465897dd0f | ||
|
|
c5778dfca2 | ||
|
|
16d4ea9ca8 | ||
|
|
82e8031338 | ||
|
|
81abb44f68 | ||
|
|
8002594ee3 | ||
|
|
95845cf6ce | ||
|
|
15fbf9d4f5 | ||
|
|
caee620a53 | ||
|
|
2616c7cf12 | ||
|
|
617475e54b | ||
|
|
ec089fd22a | ||
|
|
426f28ca99 | ||
|
|
2b71717ccf | ||
|
|
f044ca64df | ||
|
|
37b057f003 | ||
|
|
2c85ca6842 | ||
|
|
7d5d9f041b | ||
|
|
270b7655cd | ||
|
|
6a0c4709ca | ||
|
|
2ef91b7140 | ||
|
|
2e849703cd | ||
|
|
47a9e2e084 | ||
|
|
dd30c8eedd | ||
|
|
21a03f1671 | ||
|
|
41fe98b185 | ||
|
|
be5afc65d3 | ||
|
|
d838c23867 | ||
|
|
d76124d656 | ||
|
|
81fa04783a | ||
|
|
e6e2999209 | ||
|
|
44d28f500f | ||
|
|
a27cd2d281 | ||
|
|
c264c6eef9 | ||
|
|
aea82c63ea | ||
|
|
5906c6a658 | ||
|
|
b52abff279 | ||
|
|
609019c6e5 | ||
|
|
dfb36573cd | ||
|
|
b23789b770 | ||
|
|
86764af684 | ||
|
|
9736fa5e3d | ||
|
|
b3e069e8cb | ||
|
|
b6050b42ae | ||
|
|
3360f128f4 | ||
|
|
25134b592c | ||
|
|
2c54d4b160 | ||
|
|
970386e8b2 | ||
|
|
0bd34c28c7 | ||
|
|
af04273778 | ||
|
|
e36ebaa3da | ||
|
|
e7139e14a2 | ||
|
|
8d479f741c | ||
|
|
0d44bd708e | ||
|
|
352f37db03 | ||
|
|
c9214192c5 | ||
|
|
6d2f4aaafc | ||
|
|
a5824e37db | ||
|
|
26c66f3ee1 | ||
|
|
01fa4f0212 | ||
|
|
6dcac41d53 | ||
|
|
7dac332c93 | ||
|
|
4a5635b5a0 | ||
|
|
b00a05c785 | ||
|
|
7ef3cfe63e | ||
|
|
937cb5081d | ||
|
|
6d0525ae70 | ||
|
|
1ff39b6fa8 | ||
|
|
b565f05d79 | ||
|
|
4b50446ffa | ||
|
|
c4d9887f9a | ||
|
|
78799c1bcf | ||
|
|
d7e35e56cf | ||
|
|
2794e27849 | ||
|
|
8fa88fa8ca | ||
|
|
f24c55f0d5 | ||
|
|
eee692e351 | ||
|
|
b6524514c1 | ||
|
|
2c67a27a71 | ||
|
|
9dbe098349 | ||
|
|
e9996ec62a | ||
|
|
6124564297 | ||
|
|
91337399fe | ||
|
|
79359fb5e7 | ||
|
|
6566ab7e02 | ||
|
|
d273efc0f3 | ||
|
|
2bb1027e37 | ||
|
|
ad74543a6f | ||
|
|
6b10e186c4 | ||
|
|
fba3c79885 | ||
|
|
303d0190c5 | ||
|
|
14c35a16a8 | ||
|
|
c6ffe9abab | ||
|
|
f190a95a4f | ||
|
|
504aeb0e09 | ||
|
|
178c3b15b4 | ||
|
|
32c4993c8a | ||
|
|
047ea642d2 | ||
|
|
f5dccab5cf | ||
|
|
e590fad50b | ||
|
|
c0ffd000dd | ||
|
|
95ba762620 | ||
|
|
8c62829a2b | ||
|
|
0bff38c54a | ||
|
|
fece9ce745 | ||
|
|
2250508c2e | ||
|
|
0b08d89304 | ||
|
|
d72fa2a209 | ||
|
|
2e03d8b4d2 | ||
|
|
ea3f3467e2 | ||
|
|
38b638d89d | ||
|
|
05b967c79a | ||
|
|
4a210faf33 | ||
|
|
24c4ecaaac | ||
|
|
6323f0104d | ||
|
|
301b17c2a1 | ||
|
|
062fa7a2bb | ||
|
|
0b619afc87 | ||
|
|
b32d921cd9 | ||
|
|
4b91a7b391 | ||
|
|
b364faf4ec | ||
|
|
c023e9d959 | ||
|
|
1b86377635 | ||
|
|
989e513969 | ||
|
|
3ba0e85edd | ||
|
|
0f957a93cd | ||
|
|
fc97092f75 | ||
|
|
e89e5136bd | ||
|
|
363b373979 | ||
|
|
2d61357c76 | ||
|
|
88694e8417 | ||
|
|
7dc2cd2ebe | ||
|
|
621862a7d1 | ||
|
|
773fbf56a4 | ||
|
|
d61c03ca08 | ||
|
|
daf5e584c2 | ||
|
|
bb7e9a8171 | ||
|
|
66edc347ae | ||
|
|
f1658ab642 | ||
|
|
1ababa7016 | ||
|
|
85a17a70f7 | ||
|
|
48ba256cbd | ||
|
|
4cbc4894f9 | ||
|
|
b76630f2af | ||
|
|
074b06929d | ||
|
|
3c0c571012 | ||
|
|
4b8425b64b | ||
|
|
910cf49269 | ||
|
|
b51d5f18c7 | ||
|
|
0f90a34676 | ||
|
|
2d5a3bfe76 | ||
|
|
68baac7cf4 | ||
|
|
d7343486da | ||
|
|
f49eb8e9d7 | ||
|
|
45f68843b8 | ||
|
|
1db6cb9789 | ||
|
|
95e1d59939 | ||
|
|
38c088ba8d | ||
|
|
567832c6fe | ||
|
|
f9545278e2 | ||
|
|
79577355c1 | ||
|
|
c850607129 | ||
|
|
9deb8ce3fc | ||
|
|
a10960e41c | ||
|
|
c2410060ea | ||
|
|
431af0807c | ||
|
|
2227248cd6 | ||
|
|
db8bb7236d | ||
|
|
f547b79bd0 | ||
|
|
84fb180eeb | ||
|
|
527244910f | ||
|
|
0b5ba25b46 | ||
|
|
4605c65308 | ||
|
|
0f34b14b41 | ||
|
|
67c1c7c054 | ||
|
|
191fd9fd16 | ||
|
|
73bbb07ba8 | ||
|
|
18f1a08bc9 | ||
|
|
7eb9e75b86 | ||
|
|
7b92a90612 | ||
|
|
9a33e5c0a0 | ||
|
|
332edba78e | ||
|
|
450dc289c3 | ||
|
|
b5d0a5518d | ||
|
|
f55f5c258f | ||
|
|
37ac0c093c | ||
|
|
e838645fa2 | ||
|
|
54801634e1 | ||
|
|
2887f16cb9 | ||
|
|
d1088158b8 | ||
|
|
d807d44ae7 | ||
|
|
5e3793def2 | ||
|
|
85065ea1b8 | ||
|
|
e830000e41 | ||
|
|
31728dd460 | ||
|
|
19702e190e | ||
|
|
cf0223887f | ||
|
|
c23566b3af | ||
|
|
b0236501e2 | ||
|
|
0d9bb8ea58 | ||
|
|
06e06ab173 | ||
|
|
e4eedd6170 | ||
|
|
ec32866c37 | ||
|
|
60c59a7799 | ||
|
|
7754dd1b89 | ||
|
|
9eef2e91fc | ||
|
|
10a936d127 | ||
|
|
3431f01776 | ||
|
|
ea8b07e680 | ||
|
|
dd88ed767b | ||
|
|
1350477150 | ||
|
|
a941d8439d | ||
|
|
9e31aeadce | ||
|
|
79ad7b247b | ||
|
|
4ddde54c19 | ||
|
|
b9fa08ec61 | ||
|
|
4f28b64abc | ||
|
|
ba85a58039 | ||
|
|
e5f4d1fef5 | ||
|
|
461ba012fc | ||
|
|
b3a4da84da | ||
|
|
b1570d6c23 | ||
|
|
cc192763e1 | ||
|
|
f7201e5a9f | ||
|
|
fa2a2f0be9 | ||
|
|
96a86710c3 | ||
|
|
2e22885e79 | ||
|
|
35f8b87a5b | ||
|
|
a3e59e9e85 | ||
|
|
0a344e4fab | ||
|
|
2aa4873802 | ||
|
|
ded7854f09 | ||
|
|
403b397e4e | ||
|
|
6b8175c734 | ||
|
|
9e695fe830 | ||
|
|
2bee37fe69 | ||
|
|
2254ec4f30 | ||
|
|
27977d6716 | ||
|
|
69750a0b5a | ||
|
|
7eb19e5319 | ||
|
|
668330acc1 | ||
|
|
60cd0cf75e | ||
|
|
fe287ac467 | ||
|
|
1d210f639e | ||
|
|
b87ba0a3cc | ||
|
|
1837038f4e | ||
|
|
267499bed8 | ||
|
|
5ec121ba12 | ||
|
|
859c58f07d | ||
|
|
2cf4d5ef35 | ||
|
|
dee03da508 | ||
|
|
32d2df5c1e | ||
|
|
70cdb17703 | ||
|
|
db5781a088 | ||
|
|
01df50cf42 | ||
|
|
10eb3ec7fc | ||
|
|
42e932d7bf | ||
|
|
b14689df3b | ||
|
|
20f2a216df | ||
|
|
903660edba | ||
|
|
4fd2774614 | ||
|
|
825d09373d | ||
|
|
81996fcde6 | ||
|
|
dcd5e08269 | ||
|
|
56d0c6bf67 | ||
|
|
3590e181fa | ||
|
|
b306885bd8 | ||
|
|
bb30432421 | ||
|
|
ebbbc52ce4 | ||
|
|
7b37a0350f | ||
|
|
86982ca1f9 | ||
|
|
e5de13644d | ||
|
|
5cada46ddf | ||
|
|
88e5382fc4 | ||
|
|
392347d436 | ||
|
|
334164a6f7 | ||
|
|
580f32ad2a | ||
|
|
606d85055f | ||
|
|
7ae99576a6 | ||
|
|
347c6b12ec | ||
|
|
58ac2a8773 | ||
|
|
a265d6043e | ||
|
|
0f9484dc8a | ||
|
|
40a7d1d15b | ||
|
|
84f4e7b39d | ||
|
|
a3613035f3 | ||
|
|
6fef421654 | ||
|
|
226241f035 | ||
|
|
3ce879c646 | ||
|
|
770616414a | ||
|
|
d950543e65 | ||
|
|
6fe8a05dcb |
61
.bazelrc
61
.bazelrc
@@ -20,9 +20,6 @@ common:windows --host_platform=//:local_windows
|
||||
common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False
|
||||
common --@llvm//config:experimental_stub_libgcc_s
|
||||
|
||||
# We need to use the sh toolchain on windows so we don't send host bash paths to the linux executor.
|
||||
common:windows --@rules_rust//rust/settings:experimental_use_sh_toolchain_for_bootstrap_process_wrapper
|
||||
|
||||
# TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec...
|
||||
# common --@rules_rust//rust/settings:pipelined_compilation
|
||||
|
||||
@@ -60,3 +57,61 @@ common:remote --jobs=800
|
||||
# Enable pipelined compilation since we are not bound by local CPU count.
|
||||
#common:remote --@rules_rust//rust/settings:pipelined_compilation
|
||||
|
||||
# GitHub Actions CI configs.
|
||||
common:ci --remote_download_minimal
|
||||
common:ci --keep_going
|
||||
common:ci --verbose_failures
|
||||
common:ci --build_metadata=REPO_URL=https://github.com/openai/codex.git
|
||||
common:ci --build_metadata=ROLE=CI
|
||||
common:ci --build_metadata=VISIBILITY=PUBLIC
|
||||
|
||||
# Disable disk cache in CI since we have a remote one and aren't using persistent workers.
|
||||
common:ci --disk_cache=
|
||||
|
||||
# Shared config for the main Bazel CI workflow.
|
||||
common:ci-bazel --config=ci
|
||||
common:ci-bazel --build_metadata=TAG_workflow=bazel
|
||||
|
||||
# Shared config for Bazel-backed Rust linting.
|
||||
build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect
|
||||
build:clippy --output_groups=+clippy_checks
|
||||
build:clippy --@rules_rust//rust/settings:clippy.toml=//codex-rs:clippy.toml
|
||||
|
||||
# Shared config for Bazel-backed argument-comment-lint.
|
||||
build:argument-comment-lint --aspects=//tools/argument-comment-lint:lint_aspect.bzl%rust_argument_comment_lint_aspect
|
||||
build:argument-comment-lint --output_groups=argument_comment_lint_checks
|
||||
build:argument-comment-lint --@rules_rust//rust/toolchain/channel=nightly
|
||||
|
||||
# Rearrange caches on Windows so they're on the same volume as the checkout.
|
||||
common:ci-windows --config=ci-bazel
|
||||
common:ci-windows --build_metadata=TAG_os=windows
|
||||
common:ci-windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache
|
||||
common:ci-windows --repository_cache=D:/a/.cache/bazel-repo-cache
|
||||
|
||||
# We prefer to run the build actions entirely remotely so we can dial up the concurrency.
|
||||
# We have platform-specific tests, so we want to execute the tests on all platforms using the strongest sandboxing available on each platform.
|
||||
|
||||
# On linux, we can do a full remote build/test, by targeting the right (x86/arm) runners, so we have coverage of both.
|
||||
# Linux crossbuilds don't work until we untangle the libc constraint mess.
|
||||
common:ci-linux --config=ci-bazel
|
||||
common:ci-linux --build_metadata=TAG_os=linux
|
||||
common:ci-linux --config=remote
|
||||
common:ci-linux --strategy=remote
|
||||
common:ci-linux --platforms=//:rbe
|
||||
|
||||
# On mac, we can run all the build actions remotely but test actions locally.
|
||||
common:ci-macos --config=ci-bazel
|
||||
common:ci-macos --build_metadata=TAG_os=macos
|
||||
common:ci-macos --config=remote
|
||||
common:ci-macos --strategy=remote
|
||||
common:ci-macos --strategy=TestRunner=darwin-sandbox,local
|
||||
|
||||
# Linux-only V8 CI config.
|
||||
common:ci-v8 --config=ci
|
||||
common:ci-v8 --build_metadata=TAG_workflow=v8
|
||||
common:ci-v8 --build_metadata=TAG_os=linux
|
||||
common:ci-v8 --config=remote
|
||||
common:ci-v8 --strategy=remote
|
||||
|
||||
# Optional per-user local overrides.
|
||||
try-import %workspace%/user.bazelrc
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new,*meriyah.umd.min.js
|
||||
check-hidden = true
|
||||
ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b
|
||||
ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE
|
||||
ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE,PASE,SEH
|
||||
|
||||
@@ -29,14 +29,15 @@ Accept any of the following:
|
||||
4. If `diagnose_ci_failure` is present, inspect failed run logs and classify the failure.
|
||||
5. If the failure is likely caused by the current branch, patch code locally, commit, and push.
|
||||
6. If `process_review_comment` is present, inspect surfaced review items and decide whether to address them.
|
||||
7. If a review item is actionable and correct, patch code locally, commit, and push.
|
||||
8. If the failure is likely flaky/unrelated and `retry_failed_checks` is present, rerun failed jobs with `--retry-failed-now`.
|
||||
9. If both actionable review feedback and `retry_failed_checks` are present, prioritize review feedback first; a new commit will retrigger CI, so avoid rerunning flaky checks on the old SHA unless you intentionally defer the review change.
|
||||
10. On every loop, verify mergeability / merge-conflict status (for example via `gh pr view`) in addition to CI and review state.
|
||||
11. After any push or rerun action, immediately return to step 1 and continue polling on the updated SHA/state.
|
||||
12. If you had been using `--watch` before pausing to patch/commit/push, relaunch `--watch` yourself in the same turn immediately after the push (do not wait for the user to re-invoke the skill).
|
||||
13. Repeat polling until the PR is green + review-clean + mergeable, `stop_pr_closed` appears, or a user-help-required blocker is reached.
|
||||
14. Maintain terminal/session ownership: while babysitting is active, keep consuming watcher output in the same turn; do not leave a detached `--watch` process running and then end the turn as if monitoring were complete.
|
||||
7. If a review item is actionable and correct, patch code locally, commit, push, and then mark the associated review thread/comment as resolved once the fix is on GitHub.
|
||||
8. If a review item from another author is non-actionable, already addressed, or not valid, post one reply on the comment/thread explaining that decision (for example answering the question or explaining why no change is needed). If the watcher later surfaces your own reply, treat that self-authored item as already handled and do not reply again.
|
||||
9. If the failure is likely flaky/unrelated and `retry_failed_checks` is present, rerun failed jobs with `--retry-failed-now`.
|
||||
10. If both actionable review feedback and `retry_failed_checks` are present, prioritize review feedback first; a new commit will retrigger CI, so avoid rerunning flaky checks on the old SHA unless you intentionally defer the review change.
|
||||
11. On every loop, verify mergeability / merge-conflict status (for example via `gh pr view`) in addition to CI and review state.
|
||||
12. After any push or rerun action, immediately return to step 1 and continue polling on the updated SHA/state.
|
||||
13. If you had been using `--watch` before pausing to patch/commit/push, relaunch `--watch` yourself in the same turn immediately after the push (do not wait for the user to re-invoke the skill).
|
||||
14. Repeat polling until the PR is green + review-clean + mergeable, `stop_pr_closed` appears, or a user-help-required blocker is reached.
|
||||
15. Maintain terminal/session ownership: while babysitting is active, keep consuming watcher output in the same turn; do not leave a detached `--watch` process running and then end the turn as if monitoring were complete.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -94,10 +95,11 @@ When you agree with a comment and it is actionable:
|
||||
1. Patch code locally.
|
||||
2. Commit with `codex: address PR review feedback (#<n>)`.
|
||||
3. Push to the PR head branch.
|
||||
4. Resume watching on the new SHA immediately (do not stop after reporting the push).
|
||||
5. If monitoring was running in `--watch` mode, restart `--watch` immediately after the push in the same turn; do not wait for the user to ask again.
|
||||
4. After the push succeeds, mark the associated GitHub review thread/comment as resolved.
|
||||
5. Resume watching on the new SHA immediately (do not stop after reporting the push).
|
||||
6. If monitoring was running in `--watch` mode, restart `--watch` immediately after the push in the same turn; do not wait for the user to ask again.
|
||||
|
||||
If you disagree or the comment is non-actionable/already addressed, record it as handled by continuing the watcher loop (the script de-duplicates surfaced items via state after surfacing them).
|
||||
If you disagree or the comment is non-actionable/already addressed, reply once directly on the GitHub comment/thread so the reviewer gets an explicit answer, then continue the watcher loop. If the watcher later surfaces your own reply because the authenticated operator is treated as a trusted review author, treat that self-authored item as already handled and do not reply again.
|
||||
If a code review comment/thread is already marked as resolved in GitHub, treat it as non-actionable and safely ignore it unless new unresolved follow-up feedback appears.
|
||||
|
||||
## Git Safety Rules
|
||||
@@ -124,13 +126,14 @@ Use this loop in a live Codex session:
|
||||
3. First check whether the PR is now merged or otherwise closed; if so, report that terminal state and stop polling immediately.
|
||||
4. Check CI summary, new review items, and mergeability/conflict status.
|
||||
5. Diagnose CI failures and classify branch-related vs flaky/unrelated.
|
||||
6. Process actionable review comments before flaky reruns when both are present; if a review fix requires a commit, push it and skip rerunning failed checks on the old SHA.
|
||||
7. Retry failed checks only when `retry_failed_checks` is present and you are not about to replace the current SHA with a review/CI fix commit.
|
||||
8. If you pushed a commit or triggered a rerun, report the action briefly and continue polling (do not stop).
|
||||
9. After a review-fix push, proactively restart continuous monitoring (`--watch`) in the same turn unless a strict stop condition has already been reached.
|
||||
10. If everything is passing, mergeable, not blocked on required review approval, and there are no unaddressed review items, report success and stop.
|
||||
11. If blocked on a user-help-required issue (infra outage, exhausted flaky retries, unclear reviewer request, permissions), report the blocker and stop.
|
||||
12. Otherwise sleep according to the polling cadence below and repeat.
|
||||
6. For each surfaced review item from another author, either reply once with an explanation if it is non-actionable or patch/commit/push and then resolve it if it is actionable. If a later snapshot surfaces your own reply, treat it as informational and continue without responding again.
|
||||
7. Process actionable review comments before flaky reruns when both are present; if a review fix requires a commit, push it and skip rerunning failed checks on the old SHA.
|
||||
8. Retry failed checks only when `retry_failed_checks` is present and you are not about to replace the current SHA with a review/CI fix commit.
|
||||
9. If you pushed a commit, resolved a review thread, replied to a review comment, or triggered a rerun, report the action briefly and continue polling (do not stop).
|
||||
10. After a review-fix push, proactively restart continuous monitoring (`--watch`) in the same turn unless a strict stop condition has already been reached.
|
||||
11. If everything is passing, mergeable, not blocked on required review approval, and there are no unaddressed review items, report success and stop.
|
||||
12. If blocked on a user-help-required issue (infra outage, exhausted flaky retries, unclear reviewer request, permissions), report the blocker and stop.
|
||||
13. Otherwise sleep according to the polling cadence below and repeat.
|
||||
|
||||
When the user explicitly asks to monitor/watch/babysit a PR, prefer `--watch` so polling continues autonomously in one command. Use repeated `--once` snapshots only for debugging, local testing, or when the user explicitly asks for a one-shot check.
|
||||
Do not stop to ask the user whether to continue polling; continue autonomously until a strict stop condition is met or the user explicitly interrupts.
|
||||
|
||||
16
.codex/skills/remote-tests/SKILL.md
Normal file
16
.codex/skills/remote-tests/SKILL.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: remote-tests
|
||||
description: How to run tests using remote executor.
|
||||
---
|
||||
|
||||
Some codex integration tests support a running against a remote executor.
|
||||
This means that when CODEX_TEST_REMOTE_ENV environment variable is set they will attempt to start an executor process in a docker container CODEX_TEST_REMOTE_ENV points to and use it in tests.
|
||||
|
||||
Docker container is built and initialized via ./scripts/test-remote-env.sh
|
||||
|
||||
Currently running remote tests is only supported on Linux, so you need to use a devbox to run them
|
||||
|
||||
You can list devboxes via `applied_devbox ls`, pick the one with `codex` in the name.
|
||||
Connect to devbox via `ssh <devbox_name>`.
|
||||
Reuse the same checkout of codex in `~/code/codex`. Reset files if needed. Multiple checkouts take longer to build and take up more space.
|
||||
Check whether the SHA and modified files are in sync between remote and local.
|
||||
4
.github/actions/macos-code-sign/action.yml
vendored
4
.github/actions/macos-code-sign/action.yml
vendored
@@ -132,9 +132,11 @@ runs:
|
||||
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
|
||||
fi
|
||||
|
||||
entitlements_path="$GITHUB_ACTION_PATH/codex.entitlements.plist"
|
||||
|
||||
for binary in codex codex-responses-api-proxy; do
|
||||
path="codex-rs/target/${TARGET}/release/${binary}"
|
||||
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
|
||||
codesign --force --options runtime --timestamp --entitlements "$entitlements_path" --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
|
||||
done
|
||||
|
||||
- name: Notarize macOS binaries
|
||||
|
||||
8
.github/actions/macos-code-sign/codex.entitlements.plist
vendored
Normal file
8
.github/actions/macos-code-sign/codex.entitlements.plist
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
133
.github/actions/setup-bazel-ci/action.yml
vendored
Normal file
133
.github/actions/setup-bazel-ci/action.yml
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
name: setup-bazel-ci
|
||||
description: Prepare a Bazel CI runner with shared caches and optional test prerequisites.
|
||||
inputs:
|
||||
target:
|
||||
description: Target triple used for cache namespacing.
|
||||
required: true
|
||||
install-test-prereqs:
|
||||
description: Install Node.js and DotSlash for Bazel-backed test jobs.
|
||||
required: false
|
||||
default: "false"
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether the Bazel repository cache key was restored exactly.
|
||||
value: ${{ steps.cache_bazel_repository_restore.outputs.cache-hit }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set up Node.js for js_repl tests
|
||||
if: inputs.install-test-prereqs == 'true'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: codex-rs/node-version.txt
|
||||
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
# See https://github.com/openai/codex/pull/7617.
|
||||
- name: Install DotSlash
|
||||
if: inputs.install-test-prereqs == 'true'
|
||||
uses: facebook/install-dotslash@v2
|
||||
|
||||
- name: Make DotSlash available in PATH (Unix)
|
||||
if: inputs.install-test-prereqs == 'true' && runner.os != 'Windows'
|
||||
shell: bash
|
||||
run: cp "$(which dotslash)" /usr/local/bin
|
||||
|
||||
- name: Make DotSlash available in PATH (Windows)
|
||||
if: inputs.install-test-prereqs == 'true' && runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe"
|
||||
|
||||
- name: Set up Bazel
|
||||
uses: bazelbuild/setup-bazelisk@v3
|
||||
|
||||
# Restore bazel repository cache so we don't have to redownload all the external dependencies
|
||||
# on every CI run.
|
||||
- name: Restore bazel repository cache
|
||||
id: cache_bazel_repository_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-${{ inputs.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
bazel-cache-${{ inputs.target }}
|
||||
|
||||
- name: Configure Bazel output root (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Use the shortest available drive to reduce argv/path length issues,
|
||||
# but avoid the drive root because some Windows test launchers mis-handle
|
||||
# MANIFEST paths there.
|
||||
$hasDDrive = Test-Path 'D:\'
|
||||
$bazelOutputUserRoot = if ($hasDDrive) { 'D:\b' } else { 'C:\b' }
|
||||
$repoContentsCache = Join-Path $env:RUNNER_TEMP "bazel-repo-contents-cache-$env:GITHUB_RUN_ID-$env:GITHUB_JOB"
|
||||
"BAZEL_OUTPUT_USER_ROOT=$bazelOutputUserRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
"BAZEL_REPO_CONTENTS_CACHE=$repoContentsCache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
if (-not $hasDDrive) {
|
||||
$repositoryCache = Join-Path $env:USERPROFILE '.cache\bazel-repo-cache'
|
||||
"BAZEL_REPOSITORY_CACHE=$repositoryCache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
}
|
||||
|
||||
- name: Expose MSVC SDK environment (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Bazel exec-side Rust build scripts do not reliably inherit the MSVC developer
|
||||
# shell on GitHub-hosted Windows runners, so discover the latest VS install and
|
||||
# ask `VsDevCmd.bat` to materialize the x64/x64 compiler + SDK environment.
|
||||
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $vswhere)) {
|
||||
throw "vswhere.exe not found"
|
||||
}
|
||||
|
||||
$installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null
|
||||
if (-not $installPath) {
|
||||
throw "Could not locate a Visual Studio installation with VC tools"
|
||||
}
|
||||
|
||||
$vsDevCmd = Join-Path $installPath 'Common7\Tools\VsDevCmd.bat'
|
||||
if (-not (Test-Path $vsDevCmd)) {
|
||||
throw "VsDevCmd.bat not found at $vsDevCmd"
|
||||
}
|
||||
|
||||
# Keep the export surface explicit: these are the paths and SDK roots that the
|
||||
# MSVC toolchain probes need later when Bazel runs Windows exec-platform build
|
||||
# scripts such as `aws-lc-sys`.
|
||||
$varsToExport = @(
|
||||
'INCLUDE',
|
||||
'LIB',
|
||||
'LIBPATH',
|
||||
'PATH',
|
||||
'UCRTVersion',
|
||||
'UniversalCRTSdkDir',
|
||||
'VCINSTALLDIR',
|
||||
'VCToolsInstallDir',
|
||||
'WindowsLibPath',
|
||||
'WindowsSdkBinPath',
|
||||
'WindowsSdkDir',
|
||||
'WindowsSDKLibVersion',
|
||||
'WindowsSDKVersion'
|
||||
)
|
||||
|
||||
# `VsDevCmd.bat` is a batch file, so invoke it under `cmd.exe`, suppress its
|
||||
# banner, then dump the resulting environment with `set`. Re-export only the
|
||||
# approved keys into `GITHUB_ENV` so later steps inherit the same MSVC context.
|
||||
$envLines = & cmd.exe /c ('"{0}" -no_logo -arch=x64 -host_arch=x64 >nul && set' -f $vsDevCmd)
|
||||
foreach ($line in $envLines) {
|
||||
if ($line -notmatch '^(.*?)=(.*)$') {
|
||||
continue
|
||||
}
|
||||
|
||||
$name = $matches[1]
|
||||
$value = $matches[2]
|
||||
if ($varsToExport -contains $name) {
|
||||
"$name=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
}
|
||||
}
|
||||
|
||||
- name: Enable Git long paths (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: git config --global core.longpaths true
|
||||
24
.github/dotslash-argument-comment-lint-config.json
vendored
Normal file
24
.github/dotslash-argument-comment-lint-config.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"outputs": {
|
||||
"argument-comment-lint": {
|
||||
"platforms": {
|
||||
"macos-aarch64": {
|
||||
"regex": "^argument-comment-lint-aarch64-apple-darwin\\.tar\\.gz$",
|
||||
"path": "argument-comment-lint/bin/argument-comment-lint"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"regex": "^argument-comment-lint-x86_64-unknown-linux-gnu\\.tar\\.gz$",
|
||||
"path": "argument-comment-lint/bin/argument-comment-lint"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^argument-comment-lint-aarch64-unknown-linux-gnu\\.tar\\.gz$",
|
||||
"path": "argument-comment-lint/bin/argument-comment-lint"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"regex": "^argument-comment-lint-x86_64-pc-windows-msvc\\.zip$",
|
||||
"path": "argument-comment-lint/bin/argument-comment-lint.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
.github/dotslash-zsh-config.json
vendored
Normal file
23
.github/dotslash-zsh-config.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"outputs": {
|
||||
"codex-zsh": {
|
||||
"platforms": {
|
||||
"macos-aarch64": {
|
||||
"name": "codex-zsh-aarch64-apple-darwin.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"name": "codex-zsh-x86_64-unknown-linux-musl.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"name": "codex-zsh-aarch64-unknown-linux-musl.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
.github/scripts/build-zsh-release-artifact.sh
vendored
Executable file
61
.github/scripts/build-zsh-release-artifact.sh
vendored
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$#" -ne 1 ]]; then
|
||||
echo "usage: $0 <archive-path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
archive_path="$1"
|
||||
workspace="${GITHUB_WORKSPACE:?missing GITHUB_WORKSPACE}"
|
||||
zsh_commit="${ZSH_COMMIT:?missing ZSH_COMMIT}"
|
||||
zsh_patch="${ZSH_PATCH:?missing ZSH_PATCH}"
|
||||
temp_root="${RUNNER_TEMP:-/tmp}"
|
||||
work_root="$(mktemp -d "${temp_root%/}/codex-zsh-release.XXXXXX")"
|
||||
trap 'rm -rf "$work_root"' EXIT
|
||||
|
||||
source_root="${work_root}/zsh"
|
||||
package_root="${work_root}/codex-zsh"
|
||||
wrapper_path="${work_root}/exec-wrapper"
|
||||
stdout_path="${work_root}/stdout.txt"
|
||||
wrapper_log_path="${work_root}/wrapper.log"
|
||||
|
||||
git clone https://git.code.sf.net/p/zsh/code "$source_root"
|
||||
cd "$source_root"
|
||||
git checkout "$zsh_commit"
|
||||
git apply "${workspace}/${zsh_patch}"
|
||||
./Util/preconfig
|
||||
./configure
|
||||
|
||||
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
|
||||
make -j"${cores}"
|
||||
|
||||
cat > "$wrapper_path" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
|
||||
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
|
||||
file="$1"
|
||||
shift
|
||||
if [[ "$#" -eq 0 ]]; then
|
||||
exec "$file"
|
||||
fi
|
||||
arg0="$1"
|
||||
shift
|
||||
exec -a "$arg0" "$file" "$@"
|
||||
EOF
|
||||
chmod +x "$wrapper_path"
|
||||
|
||||
CODEX_WRAPPER_LOG="$wrapper_log_path" \
|
||||
EXEC_WRAPPER="$wrapper_path" \
|
||||
"${source_root}/Src/zsh" -fc '/bin/echo smoke-zsh' > "$stdout_path"
|
||||
|
||||
grep -Fx "smoke-zsh" "$stdout_path"
|
||||
grep -Fx "/bin/echo" "$wrapper_log_path"
|
||||
|
||||
mkdir -p "$package_root/bin" "$(dirname "${workspace}/${archive_path}")"
|
||||
cp "${source_root}/Src/zsh" "$package_root/bin/zsh"
|
||||
chmod +x "$package_root/bin/zsh"
|
||||
|
||||
(cd "$work_root" && tar -czf "${workspace}/${archive_path}" codex-zsh)
|
||||
115
.github/scripts/run-argument-comment-lint-bazel.sh
vendored
Executable file
115
.github/scripts/run-argument-comment-lint-bazel.sh
vendored
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ci_config=ci-linux
|
||||
case "${RUNNER_OS:-}" in
|
||||
macOS)
|
||||
ci_config=ci-macos
|
||||
;;
|
||||
Windows)
|
||||
ci_config=ci-windows
|
||||
;;
|
||||
esac
|
||||
|
||||
bazel_lint_args=("$@")
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
has_host_platform_override=0
|
||||
for arg in "${bazel_lint_args[@]}"; do
|
||||
if [[ "$arg" == --host_platform=* ]]; then
|
||||
has_host_platform_override=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $has_host_platform_override -eq 0 ]]; then
|
||||
# The nightly Windows lint toolchain is registered with an MSVC exec
|
||||
# platform even though the lint target platform stays on `windows-gnullvm`.
|
||||
# Override the host platform here so the exec-side helper binaries actually
|
||||
# match the registered toolchain set.
|
||||
bazel_lint_args+=("--host_platform=//:local_windows_msvc")
|
||||
fi
|
||||
|
||||
# Native Windows lint runs need exec-side Rust helper binaries and proc-macros
|
||||
# to use rust-lld instead of the C++ linker path. The default `none`
|
||||
# preference resolves to `cc` when a cc_toolchain is present, which currently
|
||||
# routes these exec actions through clang++ with an argument shape it cannot
|
||||
# consume.
|
||||
bazel_lint_args+=("--@rules_rust//rust/settings:toolchain_linker_preference=rust")
|
||||
|
||||
# Some Rust top-level targets are still intentionally incompatible with the
|
||||
# local Windows MSVC exec platform. Skip those explicit targets so the native
|
||||
# lint aspect can run across the compatible crate graph instead of failing the
|
||||
# whole build after analysis.
|
||||
bazel_lint_args+=("--skip_incompatible_explicit_targets")
|
||||
fi
|
||||
|
||||
bazel_startup_args=()
|
||||
if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then
|
||||
bazel_startup_args+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
|
||||
run_bazel() {
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
MSYS2_ARG_CONV_EXCL='*' bazel "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
bazel "$@"
|
||||
}
|
||||
|
||||
run_bazel_with_startup_args() {
|
||||
if [[ ${#bazel_startup_args[@]} -gt 0 ]]; then
|
||||
run_bazel "${bazel_startup_args[@]}" "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
run_bazel "$@"
|
||||
}
|
||||
|
||||
read_query_labels() {
|
||||
local query="$1"
|
||||
local query_stdout
|
||||
local query_stderr
|
||||
query_stdout="$(mktemp)"
|
||||
query_stderr="$(mktemp)"
|
||||
|
||||
if ! run_bazel_with_startup_args \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
query \
|
||||
--keep_going \
|
||||
--output=label \
|
||||
"$query" >"$query_stdout" 2>"$query_stderr"; then
|
||||
cat "$query_stderr" >&2
|
||||
rm -f "$query_stdout" "$query_stderr"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat "$query_stdout"
|
||||
rm -f "$query_stdout" "$query_stderr"
|
||||
}
|
||||
|
||||
final_build_targets=(//codex-rs/...)
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
# Bazel's local Windows platform currently lacks a default test toolchain for
|
||||
# `rust_test`, so target the concrete Rust crate rules directly. The lint
|
||||
# aspect still walks their crate graph, which preserves incremental reuse for
|
||||
# non-test code while avoiding non-Rust wrapper targets such as platform_data.
|
||||
final_build_targets=()
|
||||
while IFS= read -r label; do
|
||||
[[ -n "$label" ]] || continue
|
||||
final_build_targets+=("$label")
|
||||
done < <(read_query_labels 'kind("rust_(library|binary|proc_macro) rule", //codex-rs/...)')
|
||||
|
||||
if [[ ${#final_build_targets[@]} -eq 0 ]]; then
|
||||
echo "Failed to discover Windows Bazel lint targets." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
-- \
|
||||
build \
|
||||
"${bazel_lint_args[@]}" \
|
||||
-- \
|
||||
"${final_build_targets[@]}"
|
||||
246
.github/scripts/run-bazel-ci.sh
vendored
Executable file
246
.github/scripts/run-bazel-ci.sh
vendored
Executable file
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
print_failed_bazel_test_logs=0
|
||||
use_node_test_env=0
|
||||
remote_download_toplevel=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--print-failed-test-logs)
|
||||
print_failed_bazel_test_logs=1
|
||||
shift
|
||||
;;
|
||||
--use-node-test-env)
|
||||
use_node_test_env=1
|
||||
shift
|
||||
;;
|
||||
--remote-download-toplevel)
|
||||
remote_download_toplevel=1
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] [--remote-download-toplevel] -- <bazel args> -- <targets>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bazel_startup_args=()
|
||||
if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then
|
||||
bazel_startup_args+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
|
||||
fi
|
||||
|
||||
run_bazel() {
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
MSYS2_ARG_CONV_EXCL='*' bazel "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
bazel "$@"
|
||||
}
|
||||
|
||||
ci_config=ci-linux
|
||||
case "${RUNNER_OS:-}" in
|
||||
macOS)
|
||||
ci_config=ci-macos
|
||||
;;
|
||||
Windows)
|
||||
ci_config=ci-windows
|
||||
;;
|
||||
esac
|
||||
|
||||
print_bazel_test_log_tails() {
|
||||
local console_log="$1"
|
||||
local testlogs_dir
|
||||
local -a bazel_info_cmd=(bazel)
|
||||
|
||||
if (( ${#bazel_startup_args[@]} > 0 )); then
|
||||
bazel_info_cmd+=("${bazel_startup_args[@]}")
|
||||
fi
|
||||
|
||||
testlogs_dir="$(run_bazel "${bazel_info_cmd[@]:1}" info bazel-testlogs 2>/dev/null || echo bazel-testlogs)"
|
||||
|
||||
local failed_targets=()
|
||||
while IFS= read -r target; do
|
||||
failed_targets+=("$target")
|
||||
done < <(
|
||||
grep -E '^FAIL: //' "$console_log" \
|
||||
| sed -E 's#^FAIL: (//[^ ]+).*#\1#' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
if [[ ${#failed_targets[@]} -eq 0 ]]; then
|
||||
echo "No failed Bazel test targets were found in console output."
|
||||
return
|
||||
fi
|
||||
|
||||
for target in "${failed_targets[@]}"; do
|
||||
local rel_path="${target#//}"
|
||||
rel_path="${rel_path/:/\/}"
|
||||
local test_log="${testlogs_dir}/${rel_path}/test.log"
|
||||
|
||||
echo "::group::Bazel test log tail for ${target}"
|
||||
if [[ -f "$test_log" ]]; then
|
||||
tail -n 200 "$test_log"
|
||||
else
|
||||
echo "Missing test log: $test_log"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
}
|
||||
|
||||
bazel_args=()
|
||||
bazel_targets=()
|
||||
found_target_separator=0
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--" && $found_target_separator -eq 0 ]]; then
|
||||
found_target_separator=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $found_target_separator -eq 0 ]]; then
|
||||
bazel_args+=("$arg")
|
||||
else
|
||||
bazel_targets+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then
|
||||
echo "Expected Bazel args and targets separated by --" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $use_node_test_env -eq 1 && "${RUNNER_OS:-}" != "Windows" ]]; then
|
||||
# Bazel test sandboxes on macOS may resolve an older Homebrew `node`
|
||||
# before the `actions/setup-node` runtime on PATH.
|
||||
node_bin="$(which node)"
|
||||
bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}")
|
||||
fi
|
||||
|
||||
post_config_bazel_args=()
|
||||
if [[ $remote_download_toplevel -eq 1 ]]; then
|
||||
# Override the CI config's remote_download_minimal setting when callers need
|
||||
# the built artifact to exist on disk after the command completes.
|
||||
post_config_bazel_args+=(--remote_download_toplevel)
|
||||
fi
|
||||
|
||||
if [[ -n "${BAZEL_REPO_CONTENTS_CACHE:-}" ]]; then
|
||||
# Windows self-hosted runners can run multiple Bazel jobs concurrently. Give
|
||||
# each job its own repo contents cache so they do not fight over the shared
|
||||
# path configured in `ci-windows`.
|
||||
post_config_bazel_args+=("--repo_contents_cache=${BAZEL_REPO_CONTENTS_CACHE}")
|
||||
fi
|
||||
|
||||
if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then
|
||||
post_config_bazel_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}")
|
||||
fi
|
||||
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
windows_action_env_vars=(
|
||||
INCLUDE
|
||||
LIB
|
||||
LIBPATH
|
||||
PATH
|
||||
UCRTVersion
|
||||
UniversalCRTSdkDir
|
||||
VCINSTALLDIR
|
||||
VCToolsInstallDir
|
||||
WindowsLibPath
|
||||
WindowsSdkBinPath
|
||||
WindowsSdkDir
|
||||
WindowsSDKLibVersion
|
||||
WindowsSDKVersion
|
||||
)
|
||||
|
||||
for env_var in "${windows_action_env_vars[@]}"; do
|
||||
if [[ -n "${!env_var:-}" ]]; then
|
||||
post_config_bazel_args+=("--action_env=${env_var}" "--host_action_env=${env_var}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
bazel_console_log="$(mktemp)"
|
||||
trap 'rm -f "$bazel_console_log"' EXIT
|
||||
|
||||
bazel_cmd=(bazel)
|
||||
if (( ${#bazel_startup_args[@]} > 0 )); then
|
||||
bazel_cmd+=("${bazel_startup_args[@]}")
|
||||
fi
|
||||
|
||||
if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then
|
||||
echo "BuildBuddy API key is available; using remote Bazel configuration."
|
||||
# Work around Bazel 9 remote repo contents cache / overlay materialization failures
|
||||
# seen in CI (for example "is not a symlink" or permission errors while
|
||||
# materializing external repos such as rules_perl). We still use BuildBuddy for
|
||||
# remote execution/cache; this only disables the startup-level repo contents cache.
|
||||
bazel_run_args=(
|
||||
"${bazel_args[@]}"
|
||||
"--config=${ci_config}"
|
||||
"--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
|
||||
)
|
||||
if (( ${#post_config_bazel_args[@]} > 0 )); then
|
||||
bazel_run_args+=("${post_config_bazel_args[@]}")
|
||||
fi
|
||||
set +e
|
||||
run_bazel "${bazel_cmd[@]:1}" \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
"${bazel_run_args[@]}" \
|
||||
-- \
|
||||
"${bazel_targets[@]}" \
|
||||
2>&1 | tee "$bazel_console_log"
|
||||
bazel_status=${PIPESTATUS[0]}
|
||||
set -e
|
||||
else
|
||||
echo "BuildBuddy API key is not available; using local Bazel configuration."
|
||||
# Keep fork/community PRs on Bazel but disable remote services that are
|
||||
# configured in .bazelrc and require auth.
|
||||
#
|
||||
# Flag docs:
|
||||
# - Command-line reference: https://bazel.build/reference/command-line-reference
|
||||
# - Remote caching overview: https://bazel.build/remote/caching
|
||||
# - Remote execution overview: https://bazel.build/remote/rbe
|
||||
# - Build Event Protocol overview: https://bazel.build/remote/bep
|
||||
#
|
||||
# --noexperimental_remote_repo_contents_cache:
|
||||
# disable remote repo contents cache enabled in .bazelrc startup options.
|
||||
# https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache
|
||||
# --remote_cache= and --remote_executor=:
|
||||
# clear remote cache/execution endpoints configured in .bazelrc.
|
||||
# https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache
|
||||
# https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor
|
||||
bazel_run_args=(
|
||||
"${bazel_args[@]}"
|
||||
--remote_cache=
|
||||
--remote_executor=
|
||||
)
|
||||
if (( ${#post_config_bazel_args[@]} > 0 )); then
|
||||
bazel_run_args+=("${post_config_bazel_args[@]}")
|
||||
fi
|
||||
set +e
|
||||
run_bazel "${bazel_cmd[@]:1}" \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
"${bazel_run_args[@]}" \
|
||||
-- \
|
||||
"${bazel_targets[@]}" \
|
||||
2>&1 | tee "$bazel_console_log"
|
||||
bazel_status=${PIPESTATUS[0]}
|
||||
set -e
|
||||
fi
|
||||
|
||||
if [[ ${bazel_status:-0} -ne 0 ]]; then
|
||||
if [[ $print_failed_bazel_test_logs -eq 1 ]]; then
|
||||
print_bazel_test_log_tails "$bazel_console_log"
|
||||
fi
|
||||
exit "$bazel_status"
|
||||
fi
|
||||
287
.github/scripts/rusty_v8_bazel.py
vendored
Normal file
287
.github/scripts/rusty_v8_bazel.py
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MUSL_RUNTIME_ARCHIVE_LABELS = [
|
||||
"@llvm//runtimes/libcxx:libcxx.static",
|
||||
"@llvm//runtimes/libcxx:libcxxabi.static",
|
||||
]
|
||||
LLVM_AR_LABEL = "@llvm//tools:llvm-ar"
|
||||
LLVM_RANLIB_LABEL = "@llvm//tools:llvm-ranlib"
|
||||
|
||||
|
||||
def bazel_execroot() -> Path:
|
||||
result = subprocess.run(
|
||||
["bazel", "info", "execution_root"],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return Path(result.stdout.strip())
|
||||
|
||||
|
||||
def bazel_output_base() -> Path:
|
||||
result = subprocess.run(
|
||||
["bazel", "info", "output_base"],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return Path(result.stdout.strip())
|
||||
|
||||
|
||||
def bazel_output_path(path: str) -> Path:
|
||||
if path.startswith("external/"):
|
||||
return bazel_output_base() / path
|
||||
return bazel_execroot() / path
|
||||
|
||||
|
||||
def bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> list[Path]:
|
||||
expression = "set(" + " ".join(labels) + ")"
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
"cquery",
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
"--output=files",
|
||||
expression,
|
||||
],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return [bazel_output_path(line.strip()) for line in result.stdout.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def bazel_build(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
"bazel",
|
||||
"build",
|
||||
"-c",
|
||||
compilation_mode,
|
||||
f"--platforms=@llvm//platforms:{platform}",
|
||||
*labels,
|
||||
],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def ensure_bazel_output_files(
|
||||
platform: str,
|
||||
labels: list[str],
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> list[Path]:
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
if all(path.exists() for path in outputs):
|
||||
return outputs
|
||||
|
||||
bazel_build(platform, labels, compilation_mode)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode)
|
||||
missing = [str(path) for path in outputs if not path.exists()]
|
||||
if missing:
|
||||
raise SystemExit(f"missing built outputs for {labels}: {missing}")
|
||||
return outputs
|
||||
|
||||
|
||||
def release_pair_label(target: str) -> str:
|
||||
target_suffix = target.replace("-", "_")
|
||||
return f"//third_party/v8:rusty_v8_release_pair_{target_suffix}"
|
||||
|
||||
|
||||
def resolved_v8_crate_version() -> str:
|
||||
cargo_lock = tomllib.loads((ROOT / "codex-rs" / "Cargo.lock").read_text())
|
||||
versions = sorted(
|
||||
{
|
||||
package["version"]
|
||||
for package in cargo_lock["package"]
|
||||
if package["name"] == "v8"
|
||||
}
|
||||
)
|
||||
if len(versions) == 1:
|
||||
return versions[0]
|
||||
if len(versions) > 1:
|
||||
raise SystemExit(f"expected exactly one resolved v8 version, found: {versions}")
|
||||
|
||||
module_bazel = (ROOT / "MODULE.bazel").read_text()
|
||||
matches = sorted(
|
||||
set(
|
||||
re.findall(
|
||||
r'https://static\.crates\.io/crates/v8/v8-([0-9]+\.[0-9]+\.[0-9]+)\.crate',
|
||||
module_bazel,
|
||||
)
|
||||
)
|
||||
)
|
||||
if len(matches) != 1:
|
||||
raise SystemExit(
|
||||
"expected exactly one pinned v8 crate version in MODULE.bazel, "
|
||||
f"found: {matches}"
|
||||
)
|
||||
return matches[0]
|
||||
|
||||
|
||||
def staged_archive_name(target: str, source_path: Path) -> str:
|
||||
if source_path.suffix == ".lib":
|
||||
return f"rusty_v8_release_{target}.lib.gz"
|
||||
return f"librusty_v8_release_{target}.a.gz"
|
||||
|
||||
|
||||
def is_musl_archive_target(target: str, source_path: Path) -> bool:
|
||||
return target.endswith("-unknown-linux-musl") and source_path.suffix == ".a"
|
||||
|
||||
|
||||
def single_bazel_output_file(
|
||||
platform: str,
|
||||
label: str,
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> Path:
|
||||
outputs = ensure_bazel_output_files(platform, [label], compilation_mode)
|
||||
if len(outputs) != 1:
|
||||
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
|
||||
return outputs[0]
|
||||
|
||||
|
||||
def merged_musl_archive(
|
||||
platform: str,
|
||||
lib_path: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> Path:
|
||||
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode)
|
||||
llvm_ranlib = single_bazel_output_file(platform, LLVM_RANLIB_LABEL, compilation_mode)
|
||||
runtime_archives = [
|
||||
single_bazel_output_file(platform, label, compilation_mode)
|
||||
for label in MUSL_RUNTIME_ARCHIVE_LABELS
|
||||
]
|
||||
|
||||
temp_dir = Path(tempfile.mkdtemp(prefix="rusty-v8-musl-stage-"))
|
||||
merged_archive = temp_dir / lib_path.name
|
||||
merge_commands = "\n".join(
|
||||
[
|
||||
f"create {merged_archive}",
|
||||
f"addlib {lib_path}",
|
||||
*[f"addlib {archive}" for archive in runtime_archives],
|
||||
"save",
|
||||
"end",
|
||||
]
|
||||
)
|
||||
subprocess.run(
|
||||
[str(llvm_ar), "-M"],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
input=merge_commands,
|
||||
text=True,
|
||||
)
|
||||
subprocess.run([str(llvm_ranlib), str(merged_archive)], cwd=ROOT, check=True)
|
||||
return merged_archive
|
||||
|
||||
|
||||
def stage_release_pair(
|
||||
platform: str,
|
||||
target: str,
|
||||
output_dir: Path,
|
||||
compilation_mode: str = "fastbuild",
|
||||
) -> None:
|
||||
outputs = ensure_bazel_output_files(
|
||||
platform,
|
||||
[release_pair_label(target)],
|
||||
compilation_mode,
|
||||
)
|
||||
|
||||
try:
|
||||
lib_path = next(path for path in outputs if path.suffix in {".a", ".lib"})
|
||||
except StopIteration as exc:
|
||||
raise SystemExit(f"missing static library output for {target}") from exc
|
||||
|
||||
try:
|
||||
binding_path = next(path for path in outputs if path.suffix == ".rs")
|
||||
except StopIteration as exc:
|
||||
raise SystemExit(f"missing Rust binding output for {target}") from exc
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
staged_library = output_dir / staged_archive_name(target, lib_path)
|
||||
staged_binding = output_dir / f"src_binding_release_{target}.rs"
|
||||
source_archive = (
|
||||
merged_musl_archive(platform, lib_path, compilation_mode)
|
||||
if is_musl_archive_target(target, lib_path)
|
||||
else lib_path
|
||||
)
|
||||
|
||||
with source_archive.open("rb") as src, staged_library.open("wb") as dst:
|
||||
with gzip.GzipFile(
|
||||
filename="",
|
||||
mode="wb",
|
||||
fileobj=dst,
|
||||
compresslevel=6,
|
||||
mtime=0,
|
||||
) as gz:
|
||||
shutil.copyfileobj(src, gz)
|
||||
|
||||
shutil.copyfile(binding_path, staged_binding)
|
||||
|
||||
print(staged_library)
|
||||
print(staged_binding)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
stage_release_pair_parser = subparsers.add_parser("stage-release-pair")
|
||||
stage_release_pair_parser.add_argument("--platform", required=True)
|
||||
stage_release_pair_parser.add_argument("--target", required=True)
|
||||
stage_release_pair_parser.add_argument("--output-dir", required=True)
|
||||
stage_release_pair_parser.add_argument(
|
||||
"--compilation-mode",
|
||||
default="fastbuild",
|
||||
choices=["fastbuild", "opt", "dbg"],
|
||||
)
|
||||
|
||||
subparsers.add_parser("resolved-v8-crate-version")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if args.command == "stage-release-pair":
|
||||
stage_release_pair(
|
||||
platform=args.platform,
|
||||
target=args.target,
|
||||
output_dir=Path(args.output_dir),
|
||||
compilation_mode=args.compilation_mode,
|
||||
)
|
||||
return 0
|
||||
if args.command == "resolved-v8-crate-version":
|
||||
print(resolved_v8_crate_version())
|
||||
return 0
|
||||
raise SystemExit(f"unsupported command: {args.command}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
33
.github/workflows/README.md
vendored
Normal file
33
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Workflow Strategy
|
||||
|
||||
The workflows in this directory are split so that pull requests get fast, review-friendly signal while `main` still gets the full cross-platform verification pass.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
- `bazel.yml` is the main pre-merge verification path for Rust code.
|
||||
It runs Bazel `test` and Bazel `clippy` on the supported Bazel targets.
|
||||
- `rust-ci.yml` keeps the Cargo-native PR checks intentionally small:
|
||||
- `cargo fmt --check`
|
||||
- `cargo shear`
|
||||
- `argument-comment-lint` on Linux, macOS, and Windows
|
||||
- `tools/argument-comment-lint` package tests when the lint or its workflow wiring changes
|
||||
|
||||
The PR workflow still keeps the Linux lint lane on the default-targets-only invocation for now, but the released linter runs on Linux, macOS, and Windows before merge.
|
||||
|
||||
## Post-Merge On `main`
|
||||
|
||||
- `bazel.yml` also runs on pushes to `main`.
|
||||
This re-verifies the merged Bazel path and helps keep the BuildBuddy caches warm.
|
||||
- `rust-ci-full.yml` is the full Cargo-native verification workflow.
|
||||
It keeps the heavier checks off the PR path while still validating them after merge:
|
||||
- the full Cargo `clippy` matrix
|
||||
- the full Cargo `nextest` matrix
|
||||
- release-profile Cargo builds
|
||||
- cross-platform `argument-comment-lint`
|
||||
- Linux remote-env tests
|
||||
|
||||
## Rule Of Thumb
|
||||
|
||||
- If a build/test/clippy check can be expressed in Bazel, prefer putting the PR-time version in `bazel.yml`.
|
||||
- Keep `rust-ci.yml` fast enough that it usually does not dominate PR latency.
|
||||
- Reserve `rust-ci-full.yml` for heavyweight Cargo-native coverage that Bazel does not replace yet.
|
||||
248
.github/workflows/bazel.yml
vendored
248
.github/workflows/bazel.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Bazel (experimental)
|
||||
name: Bazel
|
||||
|
||||
# Note this workflow was originally derived from:
|
||||
# https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml
|
||||
@@ -17,6 +17,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref_name != 'main' }}
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -39,183 +40,116 @@ jobs:
|
||||
# - os: ubuntu-24.04-arm
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
|
||||
# TODO: Enable Windows once we fix the toolchain issues there.
|
||||
#- os: windows-latest
|
||||
# target: x86_64-pc-windows-gnullvm
|
||||
# Windows
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Configure a human readable name for each job
|
||||
name: Local Bazel build on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Node.js for js_repl tests
|
||||
uses: actions/setup-node@v6
|
||||
- name: Set up Bazel CI
|
||||
id: setup_bazel
|
||||
uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
node-version-file: codex-rs/node-version.txt
|
||||
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
# See https://github.com/openai/codex/pull/7617.
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@v2
|
||||
|
||||
- name: Make DotSlash available in PATH (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: cp "$(which dotslash)" /usr/local/bin
|
||||
|
||||
- name: Make DotSlash available in PATH (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe"
|
||||
|
||||
# Install Bazel via Bazelisk
|
||||
- name: Set up Bazel
|
||||
uses: bazelbuild/setup-bazelisk@v3
|
||||
target: ${{ matrix.target }}
|
||||
install-test-prereqs: "true"
|
||||
|
||||
- name: Check MODULE.bazel.lock is up to date
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
shell: bash
|
||||
run: ./scripts/check-module-bazel-lock.sh
|
||||
|
||||
# TODO(mbolin): Bring this back once we have caching working. Currently,
|
||||
# we never seem to get a cache hit but we still end up paying the cost of
|
||||
# uploading at the end of the build, which takes over a minute!
|
||||
#
|
||||
# Cache build and external artifacts so that the next ci build is incremental.
|
||||
# Because github action caches cannot be updated after a build, we need to
|
||||
# store the contents of each build in a unique cache key, then fall back to loading
|
||||
# it on the next ci run. We use hashFiles(...) in the key and restore-keys- with
|
||||
# the prefix to load the most recent cache for the branch on a cache miss. You
|
||||
# should customize the contents of hashFiles to capture any bazel input sources,
|
||||
# although this doesn't need to be perfect. If none of the input sources change
|
||||
# then a cache hit will load an existing cache and bazel won't have to do any work.
|
||||
# In the case of a cache miss, you want the fallback cache to contain most of the
|
||||
# previously built artifacts to minimize build time. The more precise you are with
|
||||
# hashFiles sources the less work bazel will have to do.
|
||||
# - name: Mount bazel caches
|
||||
# uses: actions/cache@v5
|
||||
# with:
|
||||
# path: |
|
||||
# ~/.cache/bazel-repo-cache
|
||||
# ~/.cache/bazel-repo-contents-cache
|
||||
# key: bazel-cache-${{ matrix.os }}-${{ hashFiles('**/BUILD.bazel', '**/*.bzl', 'MODULE.bazel') }}
|
||||
# restore-keys: |
|
||||
# bazel-cache-${{ matrix.os }}
|
||||
|
||||
- name: Configure Bazel startup args (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Use a very short path to reduce argv/path length issues.
|
||||
"BAZEL_STARTUP_ARGS=--output_user_root=C:\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: bazel test //...
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
|
||||
bazel_console_log="$(mktemp)"
|
||||
|
||||
print_failed_bazel_test_logs() {
|
||||
local console_log="$1"
|
||||
local testlogs_dir
|
||||
|
||||
testlogs_dir="$(bazel $BAZEL_STARTUP_ARGS info bazel-testlogs 2>/dev/null || echo bazel-testlogs)"
|
||||
|
||||
local failed_targets=()
|
||||
while IFS= read -r target; do
|
||||
failed_targets+=("$target")
|
||||
done < <(
|
||||
grep -E '^FAIL: //' "$console_log" \
|
||||
| sed -E 's#^FAIL: (//[^ ]+).*#\1#' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
if [[ ${#failed_targets[@]} -eq 0 ]]; then
|
||||
echo "No failed Bazel test targets were found in console output."
|
||||
return
|
||||
fi
|
||||
|
||||
for target in "${failed_targets[@]}"; do
|
||||
local rel_path="${target#//}"
|
||||
rel_path="${rel_path/:/\/}"
|
||||
local test_log="${testlogs_dir}/${rel_path}/test.log"
|
||||
|
||||
echo "::group::Bazel test log tail for ${target}"
|
||||
if [[ -f "$test_log" ]]; then
|
||||
tail -n 200 "$test_log"
|
||||
else
|
||||
echo "Missing test log: $test_log"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
}
|
||||
|
||||
bazel_args=(
|
||||
test
|
||||
bazel_targets=(
|
||||
//...
|
||||
--test_verbose_timeout_warnings
|
||||
--build_metadata=REPO_URL=https://github.com/openai/codex.git
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
--build_metadata=ROLE=CI
|
||||
--build_metadata=VISIBILITY=PUBLIC
|
||||
# Keep standalone V8 library targets out of the ordinary Bazel CI
|
||||
# path. V8 consumers under `//codex-rs/...` still participate
|
||||
# transitively through `//...`.
|
||||
-//third_party/v8:all
|
||||
)
|
||||
|
||||
if [[ "${RUNNER_OS:-}" != "Windows" ]]; then
|
||||
# Bazel test sandboxes on macOS may resolve an older Homebrew `node`
|
||||
# before the `actions/setup-node` runtime on PATH.
|
||||
node_bin="$(which node)"
|
||||
bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}")
|
||||
fi
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
--print-failed-test-logs \
|
||||
--use-node-test-env \
|
||||
-- \
|
||||
test \
|
||||
--test_tag_filters=-argument-comment-lint \
|
||||
--test_verbose_timeout_warnings \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
-- \
|
||||
"${bazel_targets[@]}"
|
||||
|
||||
if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then
|
||||
echo "BuildBuddy API key is available; using remote Bazel configuration."
|
||||
# Work around Bazel 9 remote repo contents cache / overlay materialization failures
|
||||
# seen in CI (for example "is not a symlink" or permission errors while
|
||||
# materializing external repos such as rules_perl). We still use BuildBuddy for
|
||||
# remote execution/cache; this only disables the startup-level repo contents cache.
|
||||
set +e
|
||||
bazel $BAZEL_STARTUP_ARGS \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
--bazelrc=.github/workflows/ci.bazelrc \
|
||||
"${bazel_args[@]}" \
|
||||
"--remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY" \
|
||||
2>&1 | tee "$bazel_console_log"
|
||||
bazel_status=${PIPESTATUS[0]}
|
||||
set -e
|
||||
else
|
||||
echo "BuildBuddy API key is not available; using local Bazel configuration."
|
||||
# Keep fork/community PRs on Bazel but disable remote services that are
|
||||
# configured in .bazelrc and require auth.
|
||||
#
|
||||
# Flag docs:
|
||||
# - Command-line reference: https://bazel.build/reference/command-line-reference
|
||||
# - Remote caching overview: https://bazel.build/remote/caching
|
||||
# - Remote execution overview: https://bazel.build/remote/rbe
|
||||
# - Build Event Protocol overview: https://bazel.build/remote/bep
|
||||
#
|
||||
# --noexperimental_remote_repo_contents_cache:
|
||||
# disable remote repo contents cache enabled in .bazelrc startup options.
|
||||
# https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache
|
||||
# --remote_cache= and --remote_executor=:
|
||||
# clear remote cache/execution endpoints configured in .bazelrc.
|
||||
# https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache
|
||||
# https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor
|
||||
set +e
|
||||
bazel $BAZEL_STARTUP_ARGS \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
"${bazel_args[@]}" \
|
||||
--remote_cache= \
|
||||
--remote_executor= \
|
||||
2>&1 | tee "$bazel_console_log"
|
||||
bazel_status=${PIPESTATUS[0]}
|
||||
set -e
|
||||
fi
|
||||
# Save bazel repository cache explicitly; make non-fatal so cache uploading
|
||||
# never fails the overall job. Only save when key wasn't hit.
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
|
||||
if [[ ${bazel_status:-0} -ne 0 ]]; then
|
||||
print_failed_bazel_test_logs "$bazel_console_log"
|
||||
exit "$bazel_status"
|
||||
fi
|
||||
clippy:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Keep Linux lint coverage on x64 and add the arm64 macOS path that
|
||||
# the Bazel test job already exercises. Add Windows gnullvm as well
|
||||
# so PRs get Bazel-native lint signal on the same Windows toolchain
|
||||
# that the Bazel test job uses.
|
||||
- os: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Bazel clippy on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Bazel CI
|
||||
id: setup_bazel
|
||||
uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: bazel build --config=clippy //codex-rs/...
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Keep the initial Bazel clippy scope on codex-rs and out of the
|
||||
# V8 proof-of-concept target for now.
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
-- \
|
||||
build \
|
||||
--config=clippy \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
--build_metadata=TAG_job=clippy \
|
||||
-- \
|
||||
//codex-rs/... \
|
||||
-//codex-rs/v8-poc:all
|
||||
|
||||
# Save bazel repository cache explicitly; make non-fatal so cache uploading
|
||||
# never fails the overall job. Only save when key wasn't hit.
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
|
||||
2
.github/workflows/blob-size-policy.yml
vendored
2
.github/workflows/blob-size-policy.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Blob size policy
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
6
.github/workflows/cargo-deny.yml
vendored
6
.github/workflows/cargo-deny.yml
vendored
@@ -14,13 +14,13 @@ jobs:
|
||||
working-directory: ./codex-rs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
|
||||
- name: Run cargo-deny
|
||||
uses: EmbarkStudios/cargo-deny-action@v2
|
||||
uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2
|
||||
with:
|
||||
rust-version: stable
|
||||
manifest-path: ./codex-rs/Cargo.toml
|
||||
|
||||
27
.github/workflows/ci.bazelrc
vendored
27
.github/workflows/ci.bazelrc
vendored
@@ -1,27 +0,0 @@
|
||||
common --remote_download_minimal
|
||||
common --keep_going
|
||||
common --verbose_failures
|
||||
|
||||
# Disable disk cache since we have remote one and aren't using persistent workers.
|
||||
common --disk_cache=
|
||||
|
||||
# Rearrange caches on Windows so they're on the same volume as the checkout.
|
||||
common:windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache
|
||||
common:windows --repository_cache=D:/a/.cache/bazel-repo-cache
|
||||
|
||||
# We prefer to run the build actions entirely remotely so we can dial up the concurrency.
|
||||
# We have platform-specific tests, so we want to execute the tests on all platforms using the strongest sandboxing available on each platform.
|
||||
|
||||
# On linux, we can do a full remote build/test, by targeting the right (x86/arm) runners, so we have coverage of both.
|
||||
# Linux crossbuilds don't work until we untangle the libc constraint mess.
|
||||
common:linux --config=remote
|
||||
common:linux --strategy=remote
|
||||
common:linux --platforms=//:rbe
|
||||
|
||||
# On mac, we can run all the build actions remotely but test actions locally.
|
||||
common:macos --config=remote
|
||||
common:macos --strategy=remote
|
||||
common:macos --strategy=TestRunner=darwin-sandbox,local
|
||||
|
||||
# On windows we cannot cross-build the tests but run them locally due to what appears to be a Bazel bug
|
||||
# (windows vs unix path confusion)
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -12,15 +12,15 @@ jobs:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# stage_npm_packages.py requires DotSlash when staging releases.
|
||||
- uses: facebook/install-dotslash@v2
|
||||
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- name: Stage npm package
|
||||
id: stage_npm_package
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Use a rust-release version that includes all native binaries.
|
||||
CODEX_VERSION=0.74.0
|
||||
CODEX_VERSION=0.115.0
|
||||
OUTPUT_DIR="${RUNNER_TEMP}"
|
||||
python3 ./scripts/stage_npm_packages.py \
|
||||
--release-version "$CODEX_VERSION" \
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload staged npm package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-npm-staging
|
||||
path: ${{ steps.stage_npm_package.outputs.pack_output }}
|
||||
|
||||
2
.github/workflows/cla.yml
vendored
2
.github/workflows/cla.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
if: ${{ github.repository_owner == 'openai' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: contributor-assistant/github-action@v2.6.1
|
||||
- uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
|
||||
# Run on close only if the PR was merged. This will lock the PR to preserve
|
||||
# the CLA agreement. We don't want to lock PRs that have been closed without
|
||||
# merging because the contributor may want to respond with additional comments.
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close inactive PRs from contributors
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/codespell.yml
vendored
2
.github/workflows/codespell.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1
|
||||
- name: Codespell
|
||||
|
||||
10
.github/workflows/issue-deduplicator.yml
vendored
10
.github/workflows/issue-deduplicator.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
reason: ${{ steps.normalize-all.outputs.reason }}
|
||||
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Codex inputs
|
||||
env:
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
# .github/prompts/issue-deduplicator.txt file is obsolete and removed.
|
||||
- id: codex-all
|
||||
name: Find duplicates (pass 1, all issues)
|
||||
uses: openai/codex-action@main
|
||||
uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1
|
||||
with:
|
||||
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
|
||||
allow-users: "*"
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
reason: ${{ steps.normalize-open.outputs.reason }}
|
||||
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Prepare Codex inputs
|
||||
env:
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
|
||||
- id: codex-open
|
||||
name: Find duplicates (pass 2, open issues)
|
||||
uses: openai/codex-action@main
|
||||
uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1
|
||||
with:
|
||||
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
|
||||
allow-users: "*"
|
||||
@@ -342,7 +342,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Comment on issue
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
CODEX_OUTPUT: ${{ needs.select-final.outputs.codex_output }}
|
||||
with:
|
||||
|
||||
4
.github/workflows/issue-labeler.yml
vendored
4
.github/workflows/issue-labeler.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
outputs:
|
||||
codex_output: ${{ steps.codex.outputs.final-message }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- id: codex
|
||||
uses: openai/codex-action@main
|
||||
uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1
|
||||
with:
|
||||
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
|
||||
allow-users: "*"
|
||||
|
||||
775
.github/workflows/rust-ci-full.yml
vendored
Normal file
775
.github/workflows/rust-ci-full.yml
vendored
Normal file
@@ -0,0 +1,775 @@
|
||||
name: rust-ci-full
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
# CI builds in debug (dev) for faster signal.
|
||||
|
||||
jobs:
|
||||
# --- CI that doesn't need specific targets ---------------------------------
|
||||
general:
|
||||
name: Format / etc
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: cargo fmt
|
||||
run: cargo fmt -- --config imports_granularity=Item --check
|
||||
|
||||
cargo_shear:
|
||||
name: cargo shear
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-shear
|
||||
version: 1.5.1
|
||||
- name: cargo shear
|
||||
run: cargo shear
|
||||
|
||||
argument_comment_lint_package:
|
||||
name: Argument comment lint package
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
components: llvm-tools-preview, rustc-dev, rust-src
|
||||
- name: Cache cargo-dylint tooling
|
||||
id: cargo_dylint_cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-dylint
|
||||
~/.cargo/bin/dylint-link
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml', '.github/workflows/rust-ci-full.yml') }}
|
||||
- name: Install cargo-dylint tooling
|
||||
if: ${{ steps.cargo_dylint_cache.outputs.cache-hit != 'true' }}
|
||||
run: cargo install --locked cargo-dylint dylint-link
|
||||
- name: Check Python wrapper syntax
|
||||
run: python3 -m py_compile tools/argument-comment-lint/wrapper_common.py tools/argument-comment-lint/run.py tools/argument-comment-lint/run-prebuilt-linter.py tools/argument-comment-lint/test_wrapper_common.py
|
||||
- name: Test Python wrapper helpers
|
||||
run: python3 -m unittest discover -s tools/argument-comment-lint -p 'test_*.py'
|
||||
- name: Test argument comment lint package
|
||||
working-directory: tools/argument-comment-lint
|
||||
run: cargo test
|
||||
|
||||
argument_comment_lint_prebuilt:
|
||||
name: Argument comment lint - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux
|
||||
runner: ubuntu-24.04
|
||||
- name: macOS
|
||||
runner: macos-15-xlarge
|
||||
- name: Windows
|
||||
runner: windows-x64
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
target: ${{ runner.os }}
|
||||
install-test-prereqs: true
|
||||
- name: Install Linux sandbox build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
- name: Run argument comment lint on codex-rs via Bazel
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)"
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
-- \
|
||||
build \
|
||||
--config=argument-comment-lint \
|
||||
--keep_going \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
-- \
|
||||
${bazel_targets}
|
||||
- name: Run argument comment lint on codex-rs via Bazel
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
./.github/scripts/run-argument-comment-lint-bazel.sh \
|
||||
--config=argument-comment-lint \
|
||||
--platforms=//:local_windows \
|
||||
--keep_going \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
|
||||
|
||||
# --- CI to validate on different os/targets --------------------------------
|
||||
lint_build:
|
||||
name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Speed up repeated builds across CI runs by caching compiled objects, except on
|
||||
# arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce
|
||||
# mixed-architecture archives under sccache.
|
||||
USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
# In rust-ci, representative release-profile checks use thin LTO for faster feedback.
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ matrix.profile == 'release' && 'thin' || 'fat' }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: dev
|
||||
- runner: macos-15-xlarge
|
||||
target: x86_64-apple-darwin
|
||||
profile: dev
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
# Also run representative release builds on Mac and Linux because
|
||||
# there could be release-only build errors we want to catch.
|
||||
# Hopefully this also pre-populates the build cache to speed up
|
||||
# releases.
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: release
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
packages=(pkg-config libcap-dev)
|
||||
if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then
|
||||
packages+=(libubsan1)
|
||||
fi
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
|
||||
fi
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
components: clippy
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Use hermetic Cargo home (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_home="${GITHUB_WORKSPACE}/.cargo-home"
|
||||
mkdir -p "${cargo_home}/bin"
|
||||
echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV"
|
||||
echo "${cargo_home}/bin" >> "$GITHUB_PATH"
|
||||
: > "${cargo_home}/config.toml"
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
working-directory: codex-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Explicit cache restore: split cargo home vs target, so we can
|
||||
# avoid caching the large target dir on the gnu-dev job.
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/.cargo-home/bin/
|
||||
${{ github.workspace }}/.cargo-home/registry/index/
|
||||
${{ github.workspace }}/.cargo-home/registry/cache/
|
||||
${{ github.workspace }}/.cargo-home/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
# Install and restore sccache cache
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Disable sccache wrapper (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Prepare APT cache directories (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo mkdir -p /var/cache/apt/archives /var/lib/apt/lists
|
||||
sudo chown -R "$USER:$USER" /var/cache/apt /var/lib/apt/lists
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Restore APT cache (musl)
|
||||
id: cache_apt_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install Zig
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
with:
|
||||
version: 0.14.0
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install musl build tools
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
TARGET: ${{ matrix.target }}
|
||||
APT_UPDATE_ARGS: -o Acquire::Retries=3
|
||||
APT_INSTALL_ARGS: --no-install-recommends
|
||||
shell: bash
|
||||
run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Configure rustc UBSan wrapper (musl host)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ubsan=""
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')"
|
||||
fi
|
||||
wrapper_root="${RUNNER_TEMP:-/tmp}"
|
||||
wrapper="${wrapper_root}/rustc-ubsan-wrapper"
|
||||
cat > "${wrapper}" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "${ubsan}" ]]; then
|
||||
export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}"
|
||||
fi
|
||||
exec "\$1" "\${@:2}"
|
||||
EOF
|
||||
chmod +x "${wrapper}"
|
||||
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Clear sanitizer flags (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Clear global Rust flags so host/proc-macro builds don't pull in UBSan.
|
||||
echo "RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV"
|
||||
# Override any runner-level Cargo config rustflags as well.
|
||||
echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
|
||||
sanitize_flags() {
|
||||
local input="$1"
|
||||
input="${input//-fsanitize=undefined/}"
|
||||
input="${input//-fno-sanitize-recover=undefined/}"
|
||||
input="${input//-fno-sanitize-trap=undefined/}"
|
||||
echo "$input"
|
||||
}
|
||||
|
||||
cflags="$(sanitize_flags "${CFLAGS-}")"
|
||||
cxxflags="$(sanitize_flags "${CXXFLAGS-}")"
|
||||
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
||||
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
name: Configure musl rusty_v8 artifact overrides
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)"
|
||||
release_tag="rusty-v8-v${version}"
|
||||
base_url="https://github.com/openai/codex/releases/download/${release_tag}"
|
||||
archive="https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz"
|
||||
binding_dir="${RUNNER_TEMP}/rusty_v8"
|
||||
binding_path="${binding_dir}/src_binding_release_${TARGET}.rs"
|
||||
mkdir -p "${binding_dir}"
|
||||
curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}"
|
||||
echo "RUSTY_V8_ARCHIVE=${archive}" >> "$GITHUB_ENV"
|
||||
echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install cargo-chef
|
||||
if: ${{ matrix.profile == 'release' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-chef
|
||||
version: 0.1.71
|
||||
|
||||
- name: Pre-warm dependency cache (cargo-chef)
|
||||
if: ${{ matrix.profile == 'release' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RECIPE="${RUNNER_TEMP}/chef-recipe.json"
|
||||
cargo chef prepare --recipe-path "$RECIPE"
|
||||
cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings
|
||||
|
||||
- name: Upload Cargo timings (clippy)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
# Save caches explicitly; make non-fatal so cache packaging
|
||||
# never fails the overall job. Only save when key wasn't hit.
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/.cargo-home/bin/
|
||||
${{ github.workspace }}/.cargo-home/registry/index/
|
||||
${{ github.workspace }}/.cargo-home/registry/cache/
|
||||
${{ github.workspace }}/.cargo-home/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ matrix.target }} (${{ matrix.profile }})";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Save APT cache (musl)
|
||||
if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
|
||||
|
||||
tests:
|
||||
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
# Perhaps we can bring this back down to 30m once we finish the cutover
|
||||
# from tui_app_server/ to tui/. Incidentally, windows-arm64 was the main
|
||||
# offender for exceeding the timeout.
|
||||
timeout-minutes: 45
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Speed up repeated builds across CI runs by caching compiled objects, except on
|
||||
# arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce
|
||||
# mixed-architecture archives under sccache.
|
||||
USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: dev
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: dev
|
||||
remote_env: "true"
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Set up Node.js for js_repl tests
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version-file: codex-rs/node-version.txt
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
fi
|
||||
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
# See https://github.com/openai/codex/pull/7617.
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
working-directory: codex-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
|
||||
- name: Enable unprivileged user namespaces (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
# Required for bubblewrap to work on Linux CI runners.
|
||||
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||
# Ubuntu 24.04+ can additionally gate unprivileged user namespaces
|
||||
# behind AppArmor.
|
||||
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
|
||||
- name: Set up remote test env (Docker)
|
||||
if: ${{ runner.os == 'Linux' && matrix.remote_env == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME=codex-remote-test-env
|
||||
source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh"
|
||||
echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: tests
|
||||
id: test
|
||||
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
NEXTEST_STATUS_LEVEL: leak
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ matrix.target }} (tests)";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Tear down remote test env
|
||||
if: ${{ always() && runner.os == 'Linux' && matrix.remote_env == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
if [[ "${{ steps.test.outcome }}" != "success" ]]; then
|
||||
docker logs codex-remote-test-env || true
|
||||
fi
|
||||
docker rm -f codex-remote-test-env >/dev/null 2>&1 || true
|
||||
|
||||
- name: verify tests passed
|
||||
if: steps.test.outcome == 'failure'
|
||||
run: |
|
||||
echo "Tests failed. See logs for details."
|
||||
exit 1
|
||||
|
||||
# --- Gatherer job for the full post-merge workflow --------------------------
|
||||
results:
|
||||
name: Full CI results
|
||||
needs:
|
||||
[
|
||||
general,
|
||||
cargo_shear,
|
||||
argument_comment_lint_package,
|
||||
argument_comment_lint_prebuilt,
|
||||
lint_build,
|
||||
tests,
|
||||
]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Summarize
|
||||
shell: bash
|
||||
run: |
|
||||
echo "argpkg : ${{ needs.argument_comment_lint_package.result }}"
|
||||
echo "arglint: ${{ needs.argument_comment_lint_prebuilt.result }}"
|
||||
echo "general: ${{ needs.general.result }}"
|
||||
echo "shear : ${{ needs.cargo_shear.result }}"
|
||||
echo "lint : ${{ needs.lint_build.result }}"
|
||||
echo "tests : ${{ needs.tests.result }}"
|
||||
[[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; }
|
||||
[[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; }
|
||||
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
|
||||
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
|
||||
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
|
||||
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
|
||||
|
||||
- name: sccache summary note
|
||||
if: always()
|
||||
run: |
|
||||
echo "Per-job sccache stats are attached to each matrix job's Step Summary."
|
||||
692
.github/workflows/rust-ci.yml
vendored
692
.github/workflows/rust-ci.yml
vendored
@@ -1,15 +1,10 @@
|
||||
name: rust-ci
|
||||
on:
|
||||
pull_request: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
# CI builds in debug (dev) for faster signal.
|
||||
|
||||
jobs:
|
||||
# --- Detect what changed to detect which tests to run (always runs) -------------------------------------
|
||||
# --- Detect what changed so the fast PR workflow only runs relevant jobs ----
|
||||
changed:
|
||||
name: Detect changed areas
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -19,7 +14,7 @@ jobs:
|
||||
codex: ${{ steps.detect.outputs.codex }}
|
||||
workflows: ${{ steps.detect.outputs.workflows }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Detect changed paths (no external action)
|
||||
@@ -33,11 +28,10 @@ jobs:
|
||||
HEAD_SHA='${{ github.event.pull_request.head.sha }}'
|
||||
echo "Base SHA: $BASE_SHA"
|
||||
echo "Head SHA: $HEAD_SHA"
|
||||
# List files changed between base and PR head
|
||||
mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA" "$HEAD_SHA")
|
||||
else
|
||||
# On push / manual runs, default to running everything
|
||||
files=("codex-rs/force" ".github/force")
|
||||
# On manual runs, default to the full fast-PR bundle.
|
||||
files=("codex-rs/force" "tools/argument-comment-lint/force" ".github/force")
|
||||
fi
|
||||
|
||||
codex=false
|
||||
@@ -47,7 +41,7 @@ jobs:
|
||||
for f in "${files[@]}"; do
|
||||
[[ $f == codex-rs/* ]] && codex=true
|
||||
[[ $f == codex-rs/* || $f == tools/argument-comment-lint/* || $f == justfile ]] && argument_comment_lint=true
|
||||
[[ $f == tools/argument-comment-lint/* || $f == .github/workflows/rust-ci.yml ]] && argument_comment_lint_package=true
|
||||
[[ $f == tools/argument-comment-lint/* || $f == .github/workflows/rust-ci.yml || $f == .github/workflows/rust-ci-full.yml ]] && argument_comment_lint_package=true
|
||||
[[ $f == .github/* ]] && workflows=true
|
||||
done
|
||||
|
||||
@@ -56,18 +50,18 @@ jobs:
|
||||
echo "codex=$codex" >> "$GITHUB_OUTPUT"
|
||||
echo "workflows=$workflows" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# --- CI that doesn't need specific targets ---------------------------------
|
||||
# --- Fast Cargo-native PR checks -------------------------------------------
|
||||
general:
|
||||
name: Format / etc
|
||||
runs-on: ubuntu-24.04
|
||||
needs: changed
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: cargo fmt
|
||||
@@ -77,13 +71,13 @@ jobs:
|
||||
name: cargo shear
|
||||
runs-on: ubuntu-24.04
|
||||
needs: changed
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-shear
|
||||
@@ -91,24 +85,27 @@ jobs:
|
||||
- name: cargo shear
|
||||
run: cargo shear
|
||||
|
||||
argument_comment_lint:
|
||||
name: Argument comment lint
|
||||
argument_comment_lint_package:
|
||||
name: Argument comment lint package
|
||||
runs-on: ubuntu-24.04
|
||||
needs: changed
|
||||
if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
|
||||
if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Linux sandbox build dependencies
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Install nightly argument-comment-lint toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
components: llvm-tools-preview, rustc-dev, rust-src
|
||||
rustup toolchain install nightly-2025-09-18 \
|
||||
--profile minimal \
|
||||
--component llvm-tools-preview \
|
||||
--component rustc-dev \
|
||||
--component rust-src \
|
||||
--no-self-update
|
||||
rustup default nightly-2025-09-18
|
||||
- name: Cache cargo-dylint tooling
|
||||
id: cargo_dylint_cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-dylint
|
||||
@@ -116,626 +113,117 @@ jobs:
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml') }}
|
||||
key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml', '.github/workflows/rust-ci-full.yml') }}
|
||||
- name: Install cargo-dylint tooling
|
||||
if: ${{ steps.cargo_dylint_cache.outputs.cache-hit != 'true' }}
|
||||
run: cargo install --locked cargo-dylint dylint-link
|
||||
- name: Check Python wrapper syntax
|
||||
run: python3 -m py_compile tools/argument-comment-lint/wrapper_common.py tools/argument-comment-lint/run.py tools/argument-comment-lint/run-prebuilt-linter.py tools/argument-comment-lint/test_wrapper_common.py
|
||||
- name: Test Python wrapper helpers
|
||||
run: python3 -m unittest discover -s tools/argument-comment-lint -p 'test_*.py'
|
||||
- name: Test argument comment lint package
|
||||
if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' || github.event_name == 'push' }}
|
||||
working-directory: tools/argument-comment-lint
|
||||
run: cargo test
|
||||
- name: Run argument comment lint on codex-rs
|
||||
run: |
|
||||
bash -n tools/argument-comment-lint/run.sh
|
||||
./tools/argument-comment-lint/run.sh
|
||||
|
||||
# --- CI to validate on different os/targets --------------------------------
|
||||
lint_build:
|
||||
name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }}
|
||||
argument_comment_lint_prebuilt:
|
||||
name: Argument comment lint - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
needs: changed
|
||||
# Keep job-level if to avoid spinning up runners when not needed
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
|
||||
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
# In rust-ci, representative release-profile checks use thin LTO for faster feedback.
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ matrix.profile == 'release' && 'thin' || 'fat' }}
|
||||
|
||||
if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: dev
|
||||
- runner: macos-15-xlarge
|
||||
target: x86_64-apple-darwin
|
||||
profile: dev
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: dev
|
||||
- name: Linux
|
||||
runner: ubuntu-24.04
|
||||
timeout_minutes: 30
|
||||
- name: macOS
|
||||
runner: macos-15-xlarge
|
||||
timeout_minutes: 30
|
||||
- name: Windows
|
||||
runner: windows-x64
|
||||
timeout_minutes: 30
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
# Also run representative release builds on Mac and Linux because
|
||||
# there could be release-only build errors we want to catch.
|
||||
# Hopefully this also pre-populates the build cache to speed up
|
||||
# releases.
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: release
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: release
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Linux build dependencies
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
target: ${{ runner.os }}
|
||||
install-test-prereqs: true
|
||||
- name: Install Linux sandbox build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
packages=(pkg-config libcap-dev)
|
||||
if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then
|
||||
packages+=(libubsan1)
|
||||
fi
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
|
||||
fi
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
components: clippy
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Use hermetic Cargo home (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_home="${GITHUB_WORKSPACE}/.cargo-home"
|
||||
mkdir -p "${cargo_home}/bin"
|
||||
echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV"
|
||||
echo "${cargo_home}/bin" >> "$GITHUB_PATH"
|
||||
: > "${cargo_home}/config.toml"
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
working-directory: codex-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Explicit cache restore: split cargo home vs target, so we can
|
||||
# avoid caching the large target dir on the gnu-dev job.
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/.cargo-home/bin/
|
||||
${{ github.workspace }}/.cargo-home/registry/index/
|
||||
${{ github.workspace }}/.cargo-home/registry/cache/
|
||||
${{ github.workspace }}/.cargo-home/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
# Install and restore sccache cache
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Disable sccache wrapper (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Prepare APT cache directories (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo mkdir -p /var/cache/apt/archives /var/lib/apt/lists
|
||||
sudo chown -R "$USER:$USER" /var/cache/apt /var/lib/apt/lists
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Restore APT cache (musl)
|
||||
id: cache_apt_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install Zig
|
||||
uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.14.0
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install musl build tools
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
- name: Run argument comment lint on codex-rs via Bazel
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
TARGET: ${{ matrix.target }}
|
||||
APT_UPDATE_ARGS: -o Acquire::Retries=3
|
||||
APT_INSTALL_ARGS: --no-install-recommends
|
||||
shell: bash
|
||||
run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Configure rustc UBSan wrapper (musl host)
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ubsan=""
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')"
|
||||
fi
|
||||
wrapper_root="${RUNNER_TEMP:-/tmp}"
|
||||
wrapper="${wrapper_root}/rustc-ubsan-wrapper"
|
||||
cat > "${wrapper}" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "${ubsan}" ]]; then
|
||||
export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}"
|
||||
fi
|
||||
exec "\$1" "\${@:2}"
|
||||
EOF
|
||||
chmod +x "${wrapper}"
|
||||
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Clear sanitizer flags (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Clear global Rust flags so host/proc-macro builds don't pull in UBSan.
|
||||
echo "RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV"
|
||||
# Override any runner-level Cargo config rustflags as well.
|
||||
echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
|
||||
sanitize_flags() {
|
||||
local input="$1"
|
||||
input="${input//-fsanitize=undefined/}"
|
||||
input="${input//-fno-sanitize-recover=undefined/}"
|
||||
input="${input//-fno-sanitize-trap=undefined/}"
|
||||
echo "$input"
|
||||
}
|
||||
|
||||
cflags="$(sanitize_flags "${CFLAGS-}")"
|
||||
cxxflags="$(sanitize_flags "${CXXFLAGS-}")"
|
||||
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
||||
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install cargo-chef
|
||||
if: ${{ matrix.profile == 'release' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: cargo-chef
|
||||
version: 0.1.71
|
||||
|
||||
- name: Pre-warm dependency cache (cargo-chef)
|
||||
if: ${{ matrix.profile == 'release' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RECIPE="${RUNNER_TEMP}/chef-recipe.json"
|
||||
cargo chef prepare --recipe-path "$RECIPE"
|
||||
cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings
|
||||
|
||||
- name: Upload Cargo timings (clippy)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
# Save caches explicitly; make non-fatal so cache packaging
|
||||
# never fails the overall job. Only save when key wasn't hit.
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/.cargo-home/bin/
|
||||
${{ github.workspace }}/.cargo-home/registry/index/
|
||||
${{ github.workspace }}/.cargo-home/registry/cache/
|
||||
${{ github.workspace }}/.cargo-home/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ matrix.target }} (${{ matrix.profile }})";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Save APT cache (musl)
|
||||
if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt
|
||||
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
|
||||
|
||||
tests:
|
||||
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
needs: changed
|
||||
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Speed up repeated builds across CI runs by caching compiled objects (non-Windows).
|
||||
USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: dev
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Node.js for js_repl tests
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: codex-rs/node-version.txt
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
fi
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
# See https://github.com/openai/codex/pull/7617.
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
working-directory: codex-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
|
||||
- name: Enable unprivileged user namespaces (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
# Required for bubblewrap to work on Linux CI runners.
|
||||
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||
# Ubuntu 24.04+ can additionally gate unprivileged user namespaces
|
||||
# behind AppArmor.
|
||||
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
|
||||
- name: tests
|
||||
id: test
|
||||
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings
|
||||
bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)"
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
-- \
|
||||
build \
|
||||
--config=argument-comment-lint \
|
||||
--keep_going \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
-- \
|
||||
${bazel_targets}
|
||||
- name: Run argument comment lint on codex-rs via Bazel
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
NEXTEST_STATUS_LEVEL: leak
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ matrix.target }} (tests)";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: verify tests passed
|
||||
if: steps.test.outcome == 'failure'
|
||||
run: |
|
||||
echo "Tests failed. See logs for details."
|
||||
exit 1
|
||||
./.github/scripts/run-argument-comment-lint-bazel.sh \
|
||||
--config=argument-comment-lint \
|
||||
--platforms=//:local_windows \
|
||||
--keep_going \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
|
||||
|
||||
# --- Gatherer job that you mark as the ONLY required status -----------------
|
||||
results:
|
||||
name: CI results (required)
|
||||
needs:
|
||||
[changed, general, cargo_shear, argument_comment_lint, lint_build, tests]
|
||||
[
|
||||
changed,
|
||||
general,
|
||||
cargo_shear,
|
||||
argument_comment_lint_package,
|
||||
argument_comment_lint_prebuilt,
|
||||
]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Summarize
|
||||
shell: bash
|
||||
run: |
|
||||
echo "arglint: ${{ needs.argument_comment_lint.result }}"
|
||||
echo "argpkg : ${{ needs.argument_comment_lint_package.result }}"
|
||||
echo "arglint: ${{ needs.argument_comment_lint_prebuilt.result }}"
|
||||
echo "general: ${{ needs.general.result }}"
|
||||
echo "shear : ${{ needs.cargo_shear.result }}"
|
||||
echo "lint : ${{ needs.lint_build.result }}"
|
||||
echo "tests : ${{ needs.tests.result }}"
|
||||
|
||||
# If nothing relevant changed (PR touching only root README, etc.),
|
||||
# declare success regardless of other jobs.
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' ]]; then
|
||||
echo 'No relevant changes -> CI not required.'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then
|
||||
[[ '${{ needs.argument_comment_lint.result }}' == 'success' ]] || { echo 'argument_comment_lint failed'; exit 1; }
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint_package }}' == 'true' ]]; then
|
||||
[[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; }
|
||||
fi
|
||||
|
||||
if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then
|
||||
if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then
|
||||
[[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; }
|
||||
fi
|
||||
|
||||
if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then
|
||||
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
|
||||
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
|
||||
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
|
||||
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
|
||||
fi
|
||||
|
||||
- name: sccache summary note
|
||||
if: always()
|
||||
run: |
|
||||
echo "Per-job sccache stats are attached to each matrix job's Step Summary."
|
||||
|
||||
103
.github/workflows/rust-release-argument-comment-lint.yml
vendored
Normal file
103
.github/workflows/rust-release-argument-comment-lint.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: rust-release-argument-comment-lint
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
publish:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
skip:
|
||||
if: ${{ !inputs.publish }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Skipping argument-comment-lint release assets for prerelease tag"
|
||||
|
||||
build:
|
||||
if: ${{ inputs.publish }}
|
||||
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
archive_name: argument-comment-lint-aarch64-apple-darwin.tar.gz
|
||||
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-apple-darwin.dylib
|
||||
runner_binary: argument-comment-lint
|
||||
cargo_dylint_binary: cargo-dylint
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
archive_name: argument-comment-lint-x86_64-unknown-linux-gnu.tar.gz
|
||||
lib_name: libargument_comment_lint@nightly-2025-09-18-x86_64-unknown-linux-gnu.so
|
||||
runner_binary: argument-comment-lint
|
||||
cargo_dylint_binary: cargo-dylint
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
archive_name: argument-comment-lint-aarch64-unknown-linux-gnu.tar.gz
|
||||
lib_name: libargument_comment_lint@nightly-2025-09-18-aarch64-unknown-linux-gnu.so
|
||||
runner_binary: argument-comment-lint
|
||||
cargo_dylint_binary: cargo-dylint
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
archive_name: argument-comment-lint-x86_64-pc-windows-msvc.zip
|
||||
lib_name: argument_comment_lint@nightly-2025-09-18-x86_64-pc-windows-msvc.dll
|
||||
runner_binary: argument-comment-lint.exe
|
||||
cargo_dylint_binary: cargo-dylint.exe
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
targets: ${{ matrix.target }}
|
||||
components: llvm-tools-preview, rustc-dev, rust-src
|
||||
|
||||
- name: Install tooling
|
||||
shell: bash
|
||||
run: |
|
||||
install_root="${RUNNER_TEMP}/argument-comment-lint-tools"
|
||||
cargo install --locked cargo-dylint --root "$install_root"
|
||||
cargo install --locked dylint-link
|
||||
echo "INSTALL_ROOT=$install_root" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cargo build
|
||||
working-directory: tools/argument-comment-lint
|
||||
shell: bash
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Stage artifact
|
||||
shell: bash
|
||||
run: |
|
||||
dest="dist/argument-comment-lint/${{ matrix.target }}"
|
||||
mkdir -p "$dest"
|
||||
package_root="${RUNNER_TEMP}/argument-comment-lint"
|
||||
rm -rf "$package_root"
|
||||
mkdir -p "$package_root/bin" "$package_root/lib"
|
||||
|
||||
cp "tools/argument-comment-lint/target/${{ matrix.target }}/release/${{ matrix.runner_binary }}" \
|
||||
"$package_root/bin/${{ matrix.runner_binary }}"
|
||||
cp "${INSTALL_ROOT}/bin/${{ matrix.cargo_dylint_binary }}" \
|
||||
"$package_root/bin/${{ matrix.cargo_dylint_binary }}"
|
||||
cp "tools/argument-comment-lint/target/${{ matrix.target }}/release/${{ matrix.lib_name }}" \
|
||||
"$package_root/lib/${{ matrix.lib_name }}"
|
||||
|
||||
archive_path="$dest/${{ matrix.archive_name }}"
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then
|
||||
(cd "${RUNNER_TEMP}" && 7z a "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint >/dev/null)
|
||||
else
|
||||
(cd "${RUNNER_TEMP}" && tar -czf "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint)
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: argument-comment-lint-${{ matrix.target }}
|
||||
path: dist/argument-comment-lint/${{ matrix.target }}/*
|
||||
4
.github/workflows/rust-release-prepare.yml
vendored
4
.github/workflows/rust-release-prepare.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
if: github.repository == 'openai/codex'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
curl --http1.1 --fail --show-error --location "${headers[@]}" "${url}" | jq '.' > codex-rs/core/models.json
|
||||
|
||||
- name: Open pull request (if changed)
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
|
||||
with:
|
||||
commit-message: "Update models.json"
|
||||
title: "Update models.json"
|
||||
|
||||
18
.github/workflows/rust-release-windows.yml
vendored
18
.github/workflows/rust-release-windows.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Print runner specs (Windows)
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
Write-Host "Total RAM: $ramGiB GiB"
|
||||
Write-Host "Disk usage:"
|
||||
Get-PSDrive -PSProvider FileSystem | Format-Table -AutoSize Name, @{Name='Size(GB)';Expression={[math]::Round(($_.Used + $_.Free) / 1GB, 1)}}, @{Name='Free(GB)';Expression={[math]::Round($_.Free / 1GB, 1)}}
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
cargo build --target ${{ matrix.target }} --release --timings ${{ matrix.build_args }}
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: |
|
||||
@@ -147,16 +147,16 @@ jobs:
|
||||
labels: codex-windows-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Download prebuilt Windows primary binaries
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-primary
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
|
||||
- name: Download prebuilt Windows helper binaries
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-helpers
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
@@ -193,7 +193,7 @@ jobs:
|
||||
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
|
||||
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@v2
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- name: Compress artifacts
|
||||
shell: bash
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
||||
95
.github/workflows/rust-release-zsh.yml
vendored
Normal file
95
.github/workflows/rust-release-zsh.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: rust-release-zsh
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
ZSH_COMMIT: 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||
ZSH_PATCH: codex-rs/shell-escalation/patches/zsh-exec-wrapper.patch
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build zsh (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: ubuntu:24.04
|
||||
archive_name: codex-zsh-x86_64-unknown-linux-musl.tar.gz
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: arm64v8/ubuntu:24.04
|
||||
archive_name: codex-zsh-aarch64-unknown-linux-musl.tar.gz
|
||||
|
||||
steps:
|
||||
- name: Install build prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
autoconf \
|
||||
bison \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
gettext \
|
||||
git \
|
||||
libncursesw5-dev
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Build, smoke-test, and stage zsh artifact
|
||||
shell: bash
|
||||
run: |
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \
|
||||
"dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}"
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-zsh-${{ matrix.target }}
|
||||
path: dist/zsh/${{ matrix.target }}/*
|
||||
|
||||
darwin:
|
||||
name: Build zsh (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-15
|
||||
archive_name: codex-zsh-aarch64-apple-darwin.tar.gz
|
||||
|
||||
steps:
|
||||
- name: Install build prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v autoconf >/dev/null 2>&1; then
|
||||
brew install autoconf
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Build, smoke-test, and stage zsh artifact
|
||||
shell: bash
|
||||
run: |
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \
|
||||
"dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}"
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: codex-zsh-${{ matrix.target }}
|
||||
path: dist/zsh/${{ matrix.target }}/*
|
||||
83
.github/workflows/rust-release.yml
vendored
83
.github/workflows/rust-release.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
tag-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@1.92
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dtolnay/rust-toolchain@c2b55edffaf41a251c410bb32bed22afefa800f1 # 1.92
|
||||
- name: Validate tag matches Cargo.toml version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
target: aarch64-unknown-linux-gnu
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Print runner specs (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
|
||||
fi
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Install Zig
|
||||
uses: mlugg/setup-zig@v2
|
||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
with:
|
||||
version: 0.14.0
|
||||
|
||||
@@ -210,6 +210,24 @@ jobs:
|
||||
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
||||
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
name: Configure musl rusty_v8 artifact overrides
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)"
|
||||
release_tag="rusty-v8-v${version}"
|
||||
base_url="https://github.com/openai/codex/releases/download/${release_tag}"
|
||||
archive="https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz"
|
||||
binding_dir="${RUNNER_TEMP}/rusty_v8"
|
||||
binding_path="${binding_dir}/src_binding_release_${TARGET}.rs"
|
||||
mkdir -p "${binding_dir}"
|
||||
curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}"
|
||||
echo "RUSTY_V8_ARCHIVE=${archive}" >> "$GITHUB_ENV"
|
||||
echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cargo build
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -217,7 +235,7 @@ jobs:
|
||||
cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: cargo-timings-rust-release-${{ matrix.target }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -356,7 +374,7 @@ jobs:
|
||||
zstd -T0 -19 --rm "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
# Upload the per-binary .zst files as well as the new .tar.gz
|
||||
@@ -371,20 +389,24 @@ jobs:
|
||||
release-lto: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
|
||||
secrets: inherit
|
||||
|
||||
shell-tool-mcp:
|
||||
name: shell-tool-mcp
|
||||
argument-comment-lint-release-assets:
|
||||
name: argument-comment-lint release assets
|
||||
needs: tag-check
|
||||
uses: ./.github/workflows/shell-tool-mcp.yml
|
||||
uses: ./.github/workflows/rust-release-argument-comment-lint.yml
|
||||
with:
|
||||
release-tag: ${{ github.ref_name }}
|
||||
publish: true
|
||||
secrets: inherit
|
||||
|
||||
zsh-release-assets:
|
||||
name: zsh release assets
|
||||
needs: tag-check
|
||||
uses: ./.github/workflows/rust-release-zsh.yml
|
||||
|
||||
release:
|
||||
needs:
|
||||
- build
|
||||
- build-windows
|
||||
- shell-tool-mcp
|
||||
- argument-comment-lint-release-assets
|
||||
- zsh-release-assets
|
||||
name: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -398,7 +420,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Generate release notes from tag commit message
|
||||
id: release_notes
|
||||
@@ -420,18 +442,15 @@ jobs:
|
||||
|
||||
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: List
|
||||
run: ls -R dist/
|
||||
|
||||
# This is a temporary fix: we should modify shell-tool-mcp.yml so these
|
||||
# files do not end up in dist/ in the first place.
|
||||
- name: Delete entries from dist/ that should not go in the release
|
||||
run: |
|
||||
rm -rf dist/shell-tool-mcp*
|
||||
rm -rf dist/windows-binaries*
|
||||
# cargo-timing.html appears under multiple target-specific directories.
|
||||
# If included in files: dist/**, release upload races on duplicate
|
||||
@@ -473,12 +492,12 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js for npm packaging
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
@@ -486,7 +505,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# stage_npm_packages.py requires DotSlash when staging releases.
|
||||
- uses: facebook/install-dotslash@v2
|
||||
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
- name: Stage npm packages
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -504,7 +523,7 @@ jobs:
|
||||
cp scripts/install/install.ps1 dist/install.ps1
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
name: ${{ steps.release_name.outputs.name }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
@@ -514,13 +533,27 @@ jobs:
|
||||
# (e.g. -alpha, -beta). Otherwise publish a normal release.
|
||||
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
|
||||
|
||||
- uses: facebook/dotslash-publish-release@v2
|
||||
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
config: .github/dotslash-config.json
|
||||
|
||||
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
config: .github/dotslash-zsh-config.json
|
||||
|
||||
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
config: .github/dotslash-argument-comment-lint-config.json
|
||||
|
||||
- name: Trigger developers.openai.com deploy
|
||||
# Only trigger the deploy if the release is not a pre-release.
|
||||
# The deploy is used to update the developers.openai.com website with the new config schema json file.
|
||||
@@ -549,7 +582,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
@@ -660,7 +693,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Publish to WinGet
|
||||
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
||||
uses: vedantmgoyal9/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
|
||||
with:
|
||||
identifier: OpenAI.Codex
|
||||
version: ${{ needs.release.outputs.version }}
|
||||
|
||||
188
.github/workflows/rusty-v8-release.yml
vendored
Normal file
188
.github/workflows/rusty-v8-release.yml
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
name: rusty-v8-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Optional release tag. Defaults to rusty-v8-v<resolved_v8_version>.
|
||||
required: false
|
||||
type: string
|
||||
publish:
|
||||
description: Publish the staged musl artifacts to a GitHub release.
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ inputs.release_tag || github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
metadata:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_tag: ${{ steps.release_tag.outputs.release_tag }}
|
||||
v8_version: ${{ steps.v8_version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Resolve exact v8 crate version
|
||||
id: v8_version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(python3 .github/scripts/rusty_v8_bazel.py resolved-v8-crate-version)"
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve release tag
|
||||
id: release_tag
|
||||
env:
|
||||
RELEASE_TAG_INPUT: ${{ inputs.release_tag }}
|
||||
V8_VERSION: ${{ steps.v8_version.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
release_tag="${RELEASE_TAG_INPUT}"
|
||||
if [[ -z "${release_tag}" ]]; then
|
||||
release_tag="rusty-v8-v${V8_VERSION}"
|
||||
fi
|
||||
|
||||
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux_amd64_musl
|
||||
target: x86_64-unknown-linux-musl
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux_arm64_musl
|
||||
target: aarch64-unknown-linux-musl
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Bazel
|
||||
uses: bazelbuild/setup-bazelisk@6ecf4fd8b7d1f9721785f1dd656a689acf9add47 # v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Build Bazel V8 release pair
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
target_suffix="${TARGET//-/_}"
|
||||
pair_target="//third_party/v8:rusty_v8_release_pair_${target_suffix}"
|
||||
extra_targets=()
|
||||
if [[ "${TARGET}" == *-unknown-linux-musl ]]; then
|
||||
extra_targets=(
|
||||
"@llvm//runtimes/libcxx:libcxx.static"
|
||||
"@llvm//runtimes/libcxx:libcxxabi.static"
|
||||
)
|
||||
fi
|
||||
|
||||
bazel_args=(
|
||||
build
|
||||
-c
|
||||
opt
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
)
|
||||
|
||||
bazel \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
"${bazel_args[@]}" \
|
||||
--config=ci-v8 \
|
||||
"--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
|
||||
|
||||
- name: Stage release pair
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--compilation-mode opt \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
publish-release:
|
||||
if: ${{ inputs.publish }}
|
||||
needs:
|
||||
- metadata
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Ensure publishing from default branch
|
||||
if: ${{ github.ref_name != github.event.repository.default_branch }}
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "Publishing is only allowed from ${DEFAULT_BRANCH}; current ref is ${GITHUB_REF_NAME}." >&2
|
||||
exit 1
|
||||
|
||||
- name: Ensure release tag is new
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" > /dev/null 2>&1; then
|
||||
echo "Release tag ${RELEASE_TAG} already exists; musl artifact tags are immutable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
tag_name: ${{ needs.metadata.outputs.release_tag }}
|
||||
name: ${{ needs.metadata.outputs.release_tag }}
|
||||
files: dist/**
|
||||
# Keep V8 artifact releases out of Codex's normal "latest release" channel.
|
||||
prerelease: true
|
||||
84
.github/workflows/sdk.yml
vendored
84
.github/workflows/sdk.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Install Linux bwrap build dependencies
|
||||
shell: bash
|
||||
@@ -23,21 +23,82 @@ jobs:
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- uses: dtolnay/rust-toolchain@1.93.0
|
||||
- name: Set up Bazel CI
|
||||
id: setup_bazel
|
||||
uses: ./.github/actions/setup-bazel-ci
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: build codex
|
||||
run: cargo build --bin codex
|
||||
working-directory: codex-rs
|
||||
- name: Build codex with Bazel
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Use the shared CI wrapper so fork PRs fall back cleanly when
|
||||
# BuildBuddy credentials are unavailable. This workflow needs the
|
||||
# built `codex` binary on disk afterwards, so ask the wrapper to
|
||||
# override CI's default remote_download_minimal behavior.
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
--remote-download-toplevel \
|
||||
-- \
|
||||
build \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
--build_metadata=TAG_job=sdk \
|
||||
-- \
|
||||
//codex-rs/cli:codex
|
||||
|
||||
# Resolve the exact output file using the same wrapper/config path as
|
||||
# the build instead of guessing which Bazel convenience symlink is
|
||||
# available on the runner.
|
||||
cquery_output="$(
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
-- \
|
||||
cquery \
|
||||
--output=files \
|
||||
-- \
|
||||
//codex-rs/cli:codex \
|
||||
| grep -E '^(/|bazel-out/)' \
|
||||
| tail -n 1
|
||||
)"
|
||||
if [[ "${cquery_output}" = /* ]]; then
|
||||
codex_bazel_output_path="${cquery_output}"
|
||||
else
|
||||
codex_bazel_output_path="${GITHUB_WORKSPACE}/${cquery_output}"
|
||||
fi
|
||||
if [[ -z "${codex_bazel_output_path}" ]]; then
|
||||
echo "Bazel did not report an output path for //codex-rs/cli:codex." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -e "${codex_bazel_output_path}" ]]; then
|
||||
echo "Unable to locate the Bazel-built codex binary at ${codex_bazel_output_path}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stage the binary into the workspace and point the SDK tests at that
|
||||
# stable path. The tests spawn `codex` directly many times, so using a
|
||||
# normal executable path is more reliable than invoking Bazel for each
|
||||
# test process.
|
||||
install_dir="${GITHUB_WORKSPACE}/.tmp/sdk-ci"
|
||||
mkdir -p "${install_dir}"
|
||||
install -m 755 "${codex_bazel_output_path}" "${install_dir}/codex"
|
||||
echo "CODEX_EXEC_PATH=${install_dir}/codex" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Warm up Bazel-built codex
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
"${CODEX_EXEC_PATH}" --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -50,3 +111,12 @@ jobs:
|
||||
|
||||
- name: Test SDK packages
|
||||
run: pnpm -r --filter ./sdk/typescript run test
|
||||
|
||||
- name: Save bazel repository cache
|
||||
if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-x86_64-unknown-linux-gnu-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
|
||||
48
.github/workflows/shell-tool-mcp-ci.yml
vendored
48
.github/workflows/shell-tool-mcp-ci.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: shell-tool-mcp CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "shell-tool-mcp/**"
|
||||
- ".github/workflows/shell-tool-mcp-ci.yml"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "shell-tool-mcp/**"
|
||||
- ".github/workflows/shell-tool-mcp-ci.yml"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Format check
|
||||
run: pnpm --filter @openai/codex-shell-tool-mcp run format
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm --filter @openai/codex-shell-tool-mcp test
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @openai/codex-shell-tool-mcp run build
|
||||
553
.github/workflows/shell-tool-mcp.yml
vendored
553
.github/workflows/shell-tool-mcp.yml
vendored
@@ -1,553 +0,0 @@
|
||||
name: shell-tool-mcp
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release-version:
|
||||
description: Version to publish (x.y.z or x.y.z-alpha.N). Defaults to GITHUB_REF_NAME when it starts with rust-v.
|
||||
required: false
|
||||
type: string
|
||||
release-tag:
|
||||
description: Tag name to use when downloading release artifacts (defaults to rust-v<version>).
|
||||
required: false
|
||||
type: string
|
||||
publish:
|
||||
description: Whether to publish to npm when the version is releasable.
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22
|
||||
|
||||
jobs:
|
||||
metadata:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.compute.outputs.version }}
|
||||
release_tag: ${{ steps.compute.outputs.release_tag }}
|
||||
should_publish: ${{ steps.compute.outputs.should_publish }}
|
||||
npm_tag: ${{ steps.compute.outputs.npm_tag }}
|
||||
steps:
|
||||
- name: Compute version and tags
|
||||
id: compute
|
||||
env:
|
||||
RELEASE_TAG_INPUT: ${{ inputs.release-tag }}
|
||||
RELEASE_VERSION_INPUT: ${{ inputs.release-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version="$RELEASE_VERSION_INPUT"
|
||||
release_tag="$RELEASE_TAG_INPUT"
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
if [[ -n "$release_tag" && "$release_tag" =~ ^rust-v.+ ]]; then
|
||||
version="${release_tag#rust-v}"
|
||||
elif [[ "${GITHUB_REF_NAME:-}" =~ ^rust-v.+ ]]; then
|
||||
version="${GITHUB_REF_NAME#rust-v}"
|
||||
release_tag="${GITHUB_REF_NAME}"
|
||||
else
|
||||
echo "release-version is required when GITHUB_REF_NAME is not a rust-v tag."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$release_tag" ]]; then
|
||||
release_tag="rust-v${version}"
|
||||
fi
|
||||
|
||||
npm_tag=""
|
||||
should_publish="false"
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
should_publish="true"
|
||||
elif [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
|
||||
should_publish="true"
|
||||
npm_tag="alpha"
|
||||
fi
|
||||
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "npm_tag=${npm_tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "should_publish=${should_publish}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
bash-linux:
|
||||
name: Build Bash (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: ubuntu:24.04
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: ubuntu-22.04
|
||||
image: ubuntu:22.04
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: debian-12
|
||||
image: debian:12
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: debian-11
|
||||
image: debian:11
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: centos-9
|
||||
image: quay.io/centos/centos:stream9
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: arm64v8/ubuntu:24.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-22.04
|
||||
image: arm64v8/ubuntu:22.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-20.04
|
||||
image: arm64v8/ubuntu:20.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: debian-12
|
||||
image: arm64v8/debian:12
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: debian-11
|
||||
image: arm64v8/debian:11
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: centos-9
|
||||
image: quay.io/centos/centos:stream9
|
||||
steps:
|
||||
- name: Install build prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential bison autoconf gettext libncursesw5-dev
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y git gcc gcc-c++ make bison autoconf gettext ncurses-devel
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
yum install -y git gcc gcc-c++ make bison autoconf gettext ncurses-devel
|
||||
else
|
||||
echo "Unsupported package manager in container"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build patched Bash
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone https://git.savannah.gnu.org/git/bash /tmp/bash
|
||||
cd /tmp/bash
|
||||
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
|
||||
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
|
||||
./configure --without-bash-malloc
|
||||
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
|
||||
make -j"${cores}"
|
||||
|
||||
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/bash/${{ matrix.variant }}"
|
||||
mkdir -p "$dest"
|
||||
cp bash "$dest/bash"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
if-no-files-found: error
|
||||
|
||||
bash-darwin:
|
||||
name: Build Bash (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-15
|
||||
- runner: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-14
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build patched Bash
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone https://git.savannah.gnu.org/git/bash /tmp/bash
|
||||
cd /tmp/bash
|
||||
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
|
||||
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
|
||||
./configure --without-bash-malloc
|
||||
cores="$(getconf _NPROCESSORS_ONLN)"
|
||||
make -j"${cores}"
|
||||
|
||||
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/bash/${{ matrix.variant }}"
|
||||
mkdir -p "$dest"
|
||||
cp bash "$dest/bash"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
if-no-files-found: error
|
||||
|
||||
zsh-linux:
|
||||
name: Build zsh (Linux) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: ubuntu:24.04
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: ubuntu-22.04
|
||||
image: ubuntu:22.04
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: debian-12
|
||||
image: debian:12
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: debian-11
|
||||
image: debian:11
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
variant: centos-9
|
||||
image: quay.io/centos/centos:stream9
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-24.04
|
||||
image: arm64v8/ubuntu:24.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-22.04
|
||||
image: arm64v8/ubuntu:22.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: ubuntu-20.04
|
||||
image: arm64v8/ubuntu:20.04
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: debian-12
|
||||
image: arm64v8/debian:12
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: debian-11
|
||||
image: arm64v8/debian:11
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
variant: centos-9
|
||||
image: quay.io/centos/centos:stream9
|
||||
steps:
|
||||
- name: Install build prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y git build-essential bison autoconf gettext libncursesw5-dev
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y git gcc gcc-c++ make bison autoconf gettext ncurses-devel
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
yum install -y git gcc gcc-c++ make bison autoconf gettext ncurses-devel
|
||||
else
|
||||
echo "Unsupported package manager in container"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build patched zsh
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone https://git.code.sf.net/p/zsh/code /tmp/zsh
|
||||
cd /tmp/zsh
|
||||
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
|
||||
./Util/preconfig
|
||||
./configure
|
||||
cores="$(command -v nproc >/dev/null 2>&1 && nproc || getconf _NPROCESSORS_ONLN)"
|
||||
make -j"${cores}"
|
||||
|
||||
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
|
||||
mkdir -p "$dest"
|
||||
cp Src/zsh "$dest/zsh"
|
||||
|
||||
- name: Smoke test zsh exec wrapper
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tmpdir="$(mktemp -d)"
|
||||
cat > "$tmpdir/exec-wrapper" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
|
||||
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
|
||||
file="$1"
|
||||
shift
|
||||
if [[ "$#" -eq 0 ]]; then
|
||||
exec "$file"
|
||||
fi
|
||||
arg0="$1"
|
||||
shift
|
||||
exec -a "$arg0" "$file" "$@"
|
||||
EOF
|
||||
chmod +x "$tmpdir/exec-wrapper"
|
||||
|
||||
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
|
||||
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
|
||||
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
|
||||
|
||||
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
if-no-files-found: error
|
||||
|
||||
zsh-darwin:
|
||||
name: Build zsh (macOS) - ${{ matrix.variant }} - ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-15
|
||||
- runner: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-14
|
||||
steps:
|
||||
- name: Install build prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v autoconf >/dev/null 2>&1; then
|
||||
brew install autoconf
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build patched zsh
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone https://git.code.sf.net/p/zsh/code /tmp/zsh
|
||||
cd /tmp/zsh
|
||||
git checkout 77045ef899e53b9598bebc5a41db93a548a40ca6
|
||||
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/zsh-exec-wrapper.patch"
|
||||
./Util/preconfig
|
||||
./configure
|
||||
cores="$(getconf _NPROCESSORS_ONLN)"
|
||||
make -j"${cores}"
|
||||
|
||||
dest="${GITHUB_WORKSPACE}/artifacts/vendor/${{ matrix.target }}/zsh/${{ matrix.variant }}"
|
||||
mkdir -p "$dest"
|
||||
cp Src/zsh "$dest/zsh"
|
||||
|
||||
- name: Smoke test zsh exec wrapper
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tmpdir="$(mktemp -d)"
|
||||
cat > "$tmpdir/exec-wrapper" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
: "${CODEX_WRAPPER_LOG:?missing CODEX_WRAPPER_LOG}"
|
||||
printf '%s\n' "$@" > "$CODEX_WRAPPER_LOG"
|
||||
file="$1"
|
||||
shift
|
||||
if [[ "$#" -eq 0 ]]; then
|
||||
exec "$file"
|
||||
fi
|
||||
arg0="$1"
|
||||
shift
|
||||
exec -a "$arg0" "$file" "$@"
|
||||
EOF
|
||||
chmod +x "$tmpdir/exec-wrapper"
|
||||
|
||||
CODEX_WRAPPER_LOG="$tmpdir/wrapper.log" \
|
||||
EXEC_WRAPPER="$tmpdir/exec-wrapper" \
|
||||
/tmp/zsh/Src/zsh -fc '/bin/echo smoke-zsh' > "$tmpdir/stdout.txt"
|
||||
|
||||
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
if-no-files-found: error
|
||||
|
||||
package:
|
||||
name: Package npm module
|
||||
needs:
|
||||
- metadata
|
||||
- bash-linux
|
||||
- bash-darwin
|
||||
- zsh-linux
|
||||
- zsh-darwin
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install JavaScript dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build (shell-tool-mcp)
|
||||
run: pnpm --filter @openai/codex-shell-tool-mcp run build
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Assemble staging directory
|
||||
id: staging
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
staging="${STAGING_DIR}"
|
||||
mkdir -p "$staging" "$staging/vendor"
|
||||
cp shell-tool-mcp/README.md "$staging/"
|
||||
cp shell-tool-mcp/package.json "$staging/"
|
||||
|
||||
found_vendor="false"
|
||||
shopt -s nullglob
|
||||
for vendor_dir in artifacts/*/vendor; do
|
||||
rsync -av "$vendor_dir/" "$staging/vendor/"
|
||||
found_vendor="true"
|
||||
done
|
||||
if [[ "$found_vendor" == "false" ]]; then
|
||||
echo "No vendor payloads were downloaded."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node - <<'NODE'
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const stagingDir = process.env.STAGING_DIR;
|
||||
const version = process.env.PACKAGE_VERSION;
|
||||
const pkgPath = path.join(stagingDir, "package.json");
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
||||
pkg.version = version;
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
||||
NODE
|
||||
|
||||
echo "dir=$staging" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
STAGING_DIR: ${{ runner.temp }}/shell-tool-mcp
|
||||
|
||||
- name: Ensure binaries are executable
|
||||
env:
|
||||
STAGING_DIR: ${{ steps.staging.outputs.dir }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chmod +x \
|
||||
"$STAGING_DIR"/vendor/*/bash/*/bash \
|
||||
"$STAGING_DIR"/vendor/*/zsh/*/zsh
|
||||
|
||||
- name: Create npm tarball
|
||||
shell: bash
|
||||
env:
|
||||
STAGING_DIR: ${{ steps.staging.outputs.dir }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p dist/npm
|
||||
pack_info=$(cd "$STAGING_DIR" && npm pack --ignore-scripts --json --pack-destination "${GITHUB_WORKSPACE}/dist/npm")
|
||||
filename=$(PACK_INFO="$pack_info" node -e 'const data = JSON.parse(process.env.PACK_INFO); console.log(data[0].filename);')
|
||||
mv "dist/npm/${filename}" "dist/npm/codex-shell-tool-mcp-npm-${PACKAGE_VERSION}.tgz"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: codex-shell-tool-mcp-npm
|
||||
path: dist/npm/codex-shell-tool-mcp-npm-${{ env.PACKAGE_VERSION }}.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish npm package
|
||||
needs:
|
||||
- metadata
|
||||
- package
|
||||
if: ${{ inputs.publish && needs.metadata.outputs.should_publish == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: https://registry.npmjs.org
|
||||
scope: "@openai"
|
||||
|
||||
# Trusted publishing requires npm CLI version 11.5.1 or later.
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Download npm tarball
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: codex-shell-tool-mcp-npm
|
||||
path: dist/npm
|
||||
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NPM_TAG: ${{ needs.metadata.outputs.npm_tag }}
|
||||
VERSION: ${{ needs.metadata.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag_args=()
|
||||
if [[ -n "${NPM_TAG}" ]]; then
|
||||
tag_args+=(--tag "${NPM_TAG}")
|
||||
fi
|
||||
npm publish "dist/npm/codex-shell-tool-mcp-npm-${VERSION}.tgz" "${tag_args[@]}"
|
||||
132
.github/workflows/v8-canary.yml
vendored
Normal file
132
.github/workflows/v8-canary.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: v8-canary
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/scripts/rusty_v8_bazel.py"
|
||||
- ".github/workflows/rusty-v8-release.yml"
|
||||
- ".github/workflows/v8-canary.yml"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- "codex-rs/Cargo.toml"
|
||||
- "patches/BUILD.bazel"
|
||||
- "patches/v8_*.patch"
|
||||
- "third_party/v8/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/scripts/rusty_v8_bazel.py"
|
||||
- ".github/workflows/rusty-v8-release.yml"
|
||||
- ".github/workflows/v8-canary.yml"
|
||||
- "MODULE.bazel"
|
||||
- "MODULE.bazel.lock"
|
||||
- "codex-rs/Cargo.toml"
|
||||
- "patches/BUILD.bazel"
|
||||
- "patches/v8_*.patch"
|
||||
- "third_party/v8/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
|
||||
cancel-in-progress: ${{ github.ref_name != 'main' }}
|
||||
|
||||
jobs:
|
||||
metadata:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
v8_version: ${{ steps.v8_version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Resolve exact v8 crate version
|
||||
id: v8_version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$(python3 .github/scripts/rusty_v8_bazel.py resolved-v8-crate-version)"
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
platform: linux_amd64_musl
|
||||
target: x86_64-unknown-linux-musl
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux_arm64_musl
|
||||
target: aarch64-unknown-linux-musl
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Bazel
|
||||
uses: bazelbuild/setup-bazelisk@6ecf4fd8b7d1f9721785f1dd656a689acf9add47 # v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Build Bazel V8 release pair
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
target_suffix="${TARGET//-/_}"
|
||||
pair_target="//third_party/v8:rusty_v8_release_pair_${target_suffix}"
|
||||
extra_targets=(
|
||||
"@llvm//runtimes/libcxx:libcxx.static"
|
||||
"@llvm//runtimes/libcxx:libcxxabi.static"
|
||||
)
|
||||
|
||||
bazel_args=(
|
||||
build
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
"${pair_target}"
|
||||
"${extra_targets[@]}"
|
||||
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
|
||||
)
|
||||
|
||||
bazel \
|
||||
--noexperimental_remote_repo_contents_cache \
|
||||
"${bazel_args[@]}" \
|
||||
--config=ci-v8 \
|
||||
"--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
|
||||
|
||||
- name: Stage release pair
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--output-dir "dist/${TARGET}"
|
||||
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ node_modules
|
||||
# build
|
||||
dist/
|
||||
bazel-*
|
||||
user.bazelrc
|
||||
build/
|
||||
out/
|
||||
storybook-static/
|
||||
|
||||
309
20260414_wasm_support.md
Normal file
309
20260414_wasm_support.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# WASM Support In Codex
|
||||
|
||||
## Purpose
|
||||
|
||||
This document summarizes the design and implementation changes needed to support running the Codex agent harness in a `wasm32` browser environment.
|
||||
|
||||
The goals are:
|
||||
|
||||
1. Identify the points in the Codex harness that need to be refactored to support WASM so we can discuss the right upstream abstractions with Codex maintainers.
|
||||
2. Record the major categories of changes required by the current prototype so that future rebases can distinguish essential seams from incidental implementation details.
|
||||
|
||||
This document is not intended to be a complete walkthrough of every file changed in the prototype branch. It is intended to capture the architectural pressure points.
|
||||
|
||||
## Non-Goal
|
||||
|
||||
This document does not argue that the browser should support the full native Codex feature set on day one. The prototype relies on degraded mode in several places. The main question is whether the Codex orchestration loop can run in the browser with a reduced environment surface. The prototype shows that it can.
|
||||
|
||||
## Key Result From The Prototype
|
||||
|
||||
The main Codex turn loop did not require a fundamental redesign to run in the browser.
|
||||
|
||||
The browser demo now runs a real turn through the Codex harness by creating a real thread and calling:
|
||||
|
||||
```rust
|
||||
session.thread.submit(Op::UserTurn { ... }).await
|
||||
```
|
||||
|
||||
The necessary refactoring was mostly at the environment boundary:
|
||||
|
||||
- networking and streaming
|
||||
- async runtime semantics
|
||||
- code execution runtime
|
||||
- filesystem/config/state assumptions
|
||||
- tool inventory assembly
|
||||
- native-only utility crates
|
||||
|
||||
The orchestrator was largely reusable. The host-heavy edges were the real blockers.
|
||||
|
||||
## High-Level Framing
|
||||
|
||||
The useful architectural question is:
|
||||
|
||||
> What assumptions does the Codex harness make about the runtime environment, and which of those assumptions must become explicit abstractions or degraded-mode implementations for `wasm32`?
|
||||
|
||||
In practice, the current prototype required two kinds of changes:
|
||||
|
||||
1. Compile-time separation of native and wasm implementations.
|
||||
2. Runtime degradation for features that are not required to complete a basic turn in the browser.
|
||||
|
||||
## Refactor Points Required For WASM
|
||||
|
||||
### 1. Responses API Transport And Streaming
|
||||
|
||||
This is one of the core seams.
|
||||
|
||||
The Codex harness assumes streamed model events. The browser prototype preserved that shape rather than introducing a separate unary or mock model path.
|
||||
|
||||
What needed refactoring:
|
||||
|
||||
- the HTTP transport needed a wasm-compatible implementation
|
||||
- SSE parsing and stream handling needed to work in wasm
|
||||
- native-only transport details needed to be compile-gated
|
||||
|
||||
Relevant files from the prototype:
|
||||
|
||||
- `codex-rs/codex-client/src/transport.rs`
|
||||
- `codex-rs/codex-client/src/sse.rs`
|
||||
- `codex-rs/codex-api/src/sse/responses.rs`
|
||||
- `codex-rs/codex-client/src/custom_ca_wasm.rs`
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- There should be a stable transport boundary for Responses HTTP plus SSE.
|
||||
- Websocket support can remain native-only at first if it is cleanly compiled out on wasm.
|
||||
|
||||
### 2. Async Runtime Semantics
|
||||
|
||||
WASM support did not require removing async Rust. It did require making native assumptions about `Send` and task spawning explicit.
|
||||
|
||||
The key issue is that native Tokio code often assumes futures can safely satisfy `Send`, while browser-backed values may not.
|
||||
|
||||
What needed refactoring:
|
||||
|
||||
- wasm-specific async surfaces where `?Send` is required
|
||||
- compatibility wrappers for spawn/timing behavior
|
||||
- preserving native `Send` guarantees instead of weakening them globally
|
||||
|
||||
Relevant files:
|
||||
|
||||
- `codex-rs/core/src/async_runtime.rs`
|
||||
- `codex-rs/async-utils/src/lib.rs`
|
||||
|
||||
Prototype lesson:
|
||||
|
||||
- This is a real source of regressions when doing a wasm split.
|
||||
- The correct pattern is usually platform-specific impls or conditional traits, not silently weakening native guarantees.
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- A small async compatibility layer is a better seam than scattering `cfg(target_arch = "wasm32")` checks throughout orchestration code.
|
||||
|
||||
### 3. Code Execution Runtime
|
||||
|
||||
This is the most important architectural seam for browser execution.
|
||||
|
||||
The browser does not need native shell execution to run a useful Codex turn. It does need a model-visible execution surface for code mode.
|
||||
|
||||
The prototype solved this by injecting a browser-specific code-mode runtime backed by an iframe sandbox.
|
||||
|
||||
What needed refactoring:
|
||||
|
||||
- thread/session startup needed a way to accept a non-native code-mode runtime
|
||||
- code mode needed to remain enabled on wasm
|
||||
- nested native tools needed to be absent or reduced in wasm mode
|
||||
|
||||
Relevant files:
|
||||
|
||||
- `codex-rs/wasm-harness/src/browser.rs`
|
||||
- `codex-rs/core/src/thread_manager.rs`
|
||||
- `codex-rs/core/src/codex.rs`
|
||||
- `codex-rs/core/src/tools/code_mode/mod.rs`
|
||||
- `codex-rs/code-mode/src/description.rs`
|
||||
|
||||
Prototype lesson:
|
||||
|
||||
- The orchestrator can stay the same if code execution is treated as an injected environment capability.
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- The code-mode runtime boundary should be treated as a first-class extension point.
|
||||
- Browser execution can then become one implementation, not a fork of the harness loop.
|
||||
|
||||
### 4. Filesystem, Config, Memory, And Session State
|
||||
|
||||
Codex currently assumes a host filesystem in many places:
|
||||
|
||||
- config loading
|
||||
- project docs and instruction files
|
||||
- memory artifacts
|
||||
- rollout persistence
|
||||
- message history
|
||||
- parts of the state bridge
|
||||
|
||||
For the browser prototype, most of these did not need full browser implementations. They needed degraded-mode behavior so a basic turn could complete.
|
||||
|
||||
What needed refactoring:
|
||||
|
||||
- local file reads needed wasm-safe or stubbed alternatives
|
||||
- config loading needed a wasm-aware path
|
||||
- memory and history handling needed ephemeral or no-op behavior
|
||||
- state persistence needed to tolerate the absence of the native DB/runtime
|
||||
|
||||
Relevant files:
|
||||
|
||||
- `codex-rs/core/src/async_fs.rs`
|
||||
- `codex-rs/core/src/config_loader_wasm.rs`
|
||||
- `codex-rs/core/src/memories_wasm.rs`
|
||||
- `codex-rs/core/src/message_history_wasm.rs`
|
||||
- `codex-rs/core/src/state_db_bridge.rs`
|
||||
- `codex-rs/core/src/project_doc_wasm.rs`
|
||||
|
||||
Prototype lesson:
|
||||
|
||||
- For a browser v0, the important distinction is between in-turn required state and optional persisted state.
|
||||
- Many of these systems can run in degraded mode initially.
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- The harness should make instruction/config/state sources injectable where practical.
|
||||
- Optional persistence should be cleanly separable from turn execution.
|
||||
|
||||
### 5. Tool Inventory Assembly
|
||||
|
||||
The browser does not need every built-in Codex tool to complete a basic turn.
|
||||
|
||||
The important requirement is that the harness can assemble a reduced, coherent tool surface from available capabilities.
|
||||
|
||||
What needed refactoring:
|
||||
|
||||
- built-in tool inventory logic had to tolerate absent native capabilities
|
||||
- code-mode-only browser execution had to work with fewer nested tools
|
||||
- native-only tools had to be omitted rather than assumed
|
||||
|
||||
Relevant files:
|
||||
|
||||
- `codex-rs/core/src/tools/spec.rs`
|
||||
- `codex-rs/core/src/tools/handlers/mod.rs`
|
||||
- `codex-rs/core/src/tools/code_mode/mod.rs`
|
||||
|
||||
Prototype lesson:
|
||||
|
||||
- Browser support does not require porting every native tool.
|
||||
- It requires that tools be derived from capabilities rather than hard-wired native assumptions.
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- Tool assembly should be capability-driven.
|
||||
- That would align well with the broader orchestrator/environment split.
|
||||
|
||||
### 6. Native-Only Utility Crates
|
||||
|
||||
Several crates assumed native behavior directly at crate root, which prevented wasm compilation even when the browser did not need their full behavior.
|
||||
|
||||
The main fix was to split these crates into native/wasm implementations and re-export through a common surface.
|
||||
|
||||
Examples:
|
||||
|
||||
- `codex-rs/apply-patch/src/lib.rs`
|
||||
- `codex-rs/apply-patch/src/native.rs`
|
||||
- `codex-rs/apply-patch/src/wasm.rs`
|
||||
- `codex-rs/secrets/src/lib.rs`
|
||||
- `codex-rs/secrets/src/native.rs`
|
||||
- `codex-rs/secrets/src/wasm.rs`
|
||||
- `codex-rs/login/src/wasm.rs`
|
||||
- `codex-rs/utils/pty/src/process_wasm.rs`
|
||||
|
||||
Prototype lesson:
|
||||
|
||||
- Some of these crates are not conceptually part of the wasm browser runtime.
|
||||
- They still need compile-time separation so the orchestrator can build.
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- Native/wasm crate structure should minimize leakage of host assumptions into shared orchestration code.
|
||||
|
||||
### 7. Native Execution, Sandboxing, And Process APIs
|
||||
|
||||
Native Codex includes shell execution, PTY handling, sandbox policies, JS REPL internals, and related runtime services that do not map directly to browser execution.
|
||||
|
||||
For the browser prototype, the correct move was not to emulate the full native stack. The correct move was to provide wasm-specific modules that let the core crate compile and let the browser run with a reduced execution surface.
|
||||
|
||||
Relevant files:
|
||||
|
||||
- `codex-rs/core/src/exec_wasm.rs`
|
||||
- `codex-rs/core/src/exec_policy_wasm.rs`
|
||||
- `codex-rs/core/src/landlock_wasm.rs`
|
||||
- `codex-rs/core/src/tools/js_repl_wasm.rs`
|
||||
- `codex-rs/core/src/mcp_connection_manager_wasm.rs`
|
||||
|
||||
Upstream discussion point:
|
||||
|
||||
- Executor and sandboxing concerns should remain environment-owned.
|
||||
- The orchestrator should depend on explicit capability surfaces, not directly on native process behavior.
|
||||
|
||||
## What Did Not Need Fundamental Refactoring
|
||||
|
||||
The following was the most important finding from the prototype:
|
||||
|
||||
- the main turn loop did not need to be replaced
|
||||
- the browser did not need a custom harness loop
|
||||
- the browser did not need a fake event model
|
||||
|
||||
Once the host-heavy assumptions were refactored or degraded, the prototype could run a real turn through the existing Codex orchestration path.
|
||||
|
||||
This is important for upstream discussion because it suggests the right investment is not a second browser-specific harness. The right investment is making the existing harness more environment-aware.
|
||||
|
||||
## Rebase Guidance
|
||||
|
||||
Codex upstream is evolving quickly, including active work around orchestrator/environment boundaries. Future rebases are likely to encounter churn in exactly the areas touched by this prototype.
|
||||
|
||||
To make rebases tractable, it is useful to classify changes into two buckets.
|
||||
|
||||
### Essential Changes To Preserve
|
||||
|
||||
These reflect real architectural seams needed for wasm:
|
||||
|
||||
- Responses transport/SSE support
|
||||
- async runtime compatibility
|
||||
- injectable code execution runtime
|
||||
- degraded-mode config/state/memory handling
|
||||
- capability-driven tool assembly
|
||||
- native/wasm crate separation for host-bound utilities
|
||||
|
||||
### Changes That May Need Rework During Rebase
|
||||
|
||||
These are more implementation-specific and may not survive upstream changes unchanged:
|
||||
|
||||
- exact wasm stub file layout
|
||||
- exact browser sandbox message shape
|
||||
- exact prompt tuning in code mode
|
||||
- exact conditional compilation points
|
||||
- exact degraded-mode behavior for optional systems
|
||||
|
||||
The important rule for rebasing is:
|
||||
|
||||
> preserve the seam, not necessarily the exact patch.
|
||||
|
||||
## Questions To Discuss With Upstream Maintainers
|
||||
|
||||
1. What is the intended stable boundary for environment-specific execution capabilities?
|
||||
2. Should Responses networking be abstracted more explicitly, or is compile-gated transport injection sufficient?
|
||||
3. What is the intended abstraction boundary for code mode runtime injection?
|
||||
4. Which persistence systems are truly required for a turn, and which can be optional?
|
||||
5. Should tool inventory assembly become explicitly capability-driven?
|
||||
6. Which native-only crates should expose formal wasm stubs versus being moved behind higher-level interfaces?
|
||||
|
||||
## Practical Takeaway
|
||||
|
||||
The prototype suggests that supporting wasm is feasible without replacing the Codex agent harness.
|
||||
|
||||
The main work is to make runtime assumptions explicit:
|
||||
|
||||
- what needs network transport
|
||||
- what needs async tasking semantics
|
||||
- what needs code execution
|
||||
- what needs filesystem or persistence
|
||||
- what tools exist because of capabilities versus because of current native defaults
|
||||
|
||||
That is the set of seams worth discussing upstream.
|
||||
149
20260416_system_instructions.md
Normal file
149
20260416_system_instructions.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Browser System Instructions Injection
|
||||
|
||||
## Problem
|
||||
|
||||
The browser harness can run a real Codex turn, but the `wasm32` build does not
|
||||
load project docs from `AGENTS.md` today. In the browser, the application often
|
||||
knows important environment-specific policy that the harness does not know:
|
||||
|
||||
- code runs inside AppKernel;
|
||||
- browser APIs such as OPFS and network access are available;
|
||||
- only a specific OPFS path layout is allowed;
|
||||
- only specific GitHub URL prefixes are allowed; and
|
||||
- the agent is expected to fetch, cache, and search source code itself.
|
||||
|
||||
Those details need to reach the model as prompt-layer instructions even when the
|
||||
harness cannot discover them from the filesystem.
|
||||
|
||||
## Goals
|
||||
|
||||
- Let browser applications inject session-scoped prompt instructions.
|
||||
- Keep prompt roles distinct:
|
||||
- base instructions remain the base/system layer;
|
||||
- developer instructions carry application or runtime policy;
|
||||
- user instructions carry AGENTS/project-doc style content.
|
||||
- Keep responsibility boundaries clean:
|
||||
- the application decides where instructions come from;
|
||||
- the harness only accepts and applies resolved instruction text.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Teach the wasm harness to discover `AGENTS.md` on its own.
|
||||
- Move application-specific policy such as AppKernel, OPFS layout, or GitHub
|
||||
allowlists into `codex-core`.
|
||||
- Replace Codex's built-in base prompt.
|
||||
|
||||
## Existing State
|
||||
|
||||
`codex-core` already has the right internal prompt channels:
|
||||
|
||||
- `Config.base_instructions`
|
||||
- `Config.developer_instructions`
|
||||
- `Config.user_instructions`
|
||||
|
||||
The browser adapter does not expose them. `BrowserCodex` currently accepts an
|
||||
API key and a code executor, then builds a session config internally with a
|
||||
fixed cwd. The wasm project-doc loader is also stubbed out, so no `AGENTS.md`
|
||||
content is discovered automatically.
|
||||
|
||||
## Proposed API
|
||||
|
||||
Add a browser-session configuration object that the application can set before
|
||||
starting a turn:
|
||||
|
||||
```ts
|
||||
type BrowserSessionOptions = {
|
||||
cwd?: string;
|
||||
instructions?: {
|
||||
base?: string;
|
||||
developer?: string;
|
||||
user?: string;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Expose it through the browser harness as a session-scoped setter:
|
||||
|
||||
```ts
|
||||
const codex = new BrowserCodex(apiKey);
|
||||
codex.setSessionOptions({
|
||||
cwd: "/workspace",
|
||||
instructions: {
|
||||
developer: "...AppKernel/OPFS/network/GitHub policy...",
|
||||
user: "...resolved AGENTS.md text...",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Prompt Layer Mapping
|
||||
|
||||
### `instructions.developer`
|
||||
|
||||
Use for environment and policy details supplied by the browser application, for
|
||||
example:
|
||||
|
||||
- code runs inside AppKernel;
|
||||
- OPFS and network APIs are available;
|
||||
- approved OPFS path layout;
|
||||
- approved GitHub URL prefixes; and
|
||||
- fetch/cache/search expectations.
|
||||
|
||||
This maps to `Config.developer_instructions`.
|
||||
|
||||
### `instructions.user`
|
||||
|
||||
Use for application-resolved project docs such as `AGENTS.md`.
|
||||
|
||||
This maps to `Config.user_instructions`.
|
||||
|
||||
### `instructions.base`
|
||||
|
||||
Use only as an escape hatch when the embedding application intentionally wants
|
||||
to replace the base instruction bundle for the session.
|
||||
|
||||
This maps to `Config.base_instructions`.
|
||||
|
||||
Most callers should leave this unset so Codex keeps its built-in base prompt.
|
||||
|
||||
## Session Lifetime
|
||||
|
||||
These values are session-scoped, not turn-scoped.
|
||||
|
||||
If `cwd` or any injected instruction changes, the harness should discard the
|
||||
current browser session and create a new one on the next turn. This avoids
|
||||
mixing old prompt state with new environment policy.
|
||||
|
||||
## API Boundary
|
||||
|
||||
### Application responsibilities
|
||||
|
||||
- discover `AGENTS.md` or any equivalent project-doc source;
|
||||
- decide the approved OPFS layout;
|
||||
- decide the approved GitHub URL prefixes;
|
||||
- decide whether AppKernel/network guidance should be present; and
|
||||
- assemble those values into prompt text.
|
||||
|
||||
### Harness responsibilities
|
||||
|
||||
- accept already-resolved session options;
|
||||
- validate and store them;
|
||||
- apply them to the Codex session config;
|
||||
- ensure the configured cwd is used consistently for the session and for turn
|
||||
submission; and
|
||||
- recreate the session when session options change.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Keep the API additive. Existing callers that only set the API key and code
|
||||
executor should continue to work.
|
||||
- Reject unknown fields in the session-options payload so applications do not
|
||||
silently think a field is applied when it is ignored.
|
||||
- Apply the configured cwd both when building the browser config and when
|
||||
calling `Op::UserTurn`.
|
||||
|
||||
## Initial Scope
|
||||
|
||||
Implement the new `BrowserCodex` session-options setter in `wasm-harness`,
|
||||
apply it to config creation, and update the browser demo to act like an
|
||||
embedding application by supplying explicit developer instructions for the
|
||||
browser/AppKernel environment.
|
||||
427
20260416_wasm_appserver.md
Normal file
427
20260416_wasm_appserver.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# WASM Embedded App-Server Design
|
||||
|
||||
## TL;DR
|
||||
|
||||
The current browser harness wraps a single user submission by creating a thread, calling `thread.submit(Op::UserTurn { ... })`, and draining `thread.next_event()` until the turn completes. That path proved that the core harness can run in the browser, but it leaves the browser with a custom control surface that diverges from native app-server behavior and currently resets session state after every turn.
|
||||
|
||||
This document proposes switching the browser integration to an embedded in-process app-server runtime. The browser would keep a long-lived app-server instance in wasm, communicate with it through the existing app-server request and event model, and reuse the native app-server boundary instead of maintaining a browser-specific harness API.
|
||||
|
||||
## Objective
|
||||
|
||||
Use the existing app-server boundary as the primary control plane for the browser runtime so that:
|
||||
|
||||
- browser and native clients speak the same conceptual protocol
|
||||
- multi-turn behavior matches native behavior more closely
|
||||
- debugging and event inspection reuse existing app-server semantics
|
||||
- future features such as thread lifecycle, steering, approvals, and richer tooling do not require a second browser-specific orchestration layer
|
||||
|
||||
## Background
|
||||
|
||||
### Current Browser Design
|
||||
|
||||
The current browser runtime is centered on `BrowserCodex` in `codex-rs/wasm-harness/src/browser.rs`.
|
||||
|
||||
Its main flow is:
|
||||
|
||||
1. `BrowserCodex.submit_turn(prompt, on_event)` creates a fresh `LocalSet`.
|
||||
2. It either reuses or creates a `BrowserSession`.
|
||||
3. It submits a turn directly to core with:
|
||||
|
||||
```rust
|
||||
session.thread.submit(Op::UserTurn { ... }).await
|
||||
```
|
||||
|
||||
4. It loops on `session.thread.next_event().await` and forwards events to JS.
|
||||
5. When the turn finishes, it clears `self.session`.
|
||||
|
||||
This is a thin wrapper around the core thread API. It is effectively a `UserSubmit`-style interface: the browser gives the harness a prompt, the harness starts one turn, then pushes turn events back to JS.
|
||||
|
||||
The relevant behavior today is:
|
||||
|
||||
- `submit_turn()` scopes execution to a per-turn `LocalSet`
|
||||
- the browser wrapper owns a `BrowserSession { config, thread, session_configured }`
|
||||
- the wrapper emits raw core events directly to JS
|
||||
- the wrapper explicitly drops the session after each turn
|
||||
|
||||
### Why The Current Design Is Not Enough
|
||||
|
||||
The current design was useful as a proof of feasibility, but it creates several product and maintenance problems.
|
||||
|
||||
First, it is not actually aligned with the native client boundary. Native clients talk to app-server through requests, notifications, and streamed server events. The browser currently bypasses that layer and talks directly to `CodexThread`.
|
||||
|
||||
Second, it currently loses continuity across turns. Because `submit_turn()` uses a fresh `LocalSet` and clears `self.session` after each submission, the browser prototype starts a new harness session for every turn. That is why follow-up prompts appear to lack memory of earlier prompts.
|
||||
|
||||
Third, it forces the browser to maintain a separate orchestration contract. Any feature added at the app-server boundary has to be re-exposed or re-invented in the browser wrapper.
|
||||
|
||||
Fourth, it weakens debugging. Native app-server already has a request model, notification model, server-request model, and thread/turn lifecycle semantics. The browser wrapper currently exposes only a narrower direct-thread view.
|
||||
|
||||
## Why Switch To An Embedded App-Server
|
||||
|
||||
The app-server boundary already solves the problems the browser needs:
|
||||
|
||||
- request/response for typed client operations
|
||||
- notifications for fire-and-forget client messages
|
||||
- streamed server notifications for turn progress
|
||||
- server requests for approvals and similar interactive flows
|
||||
- explicit thread lifecycle APIs
|
||||
- a stable place to add future browser features without widening the direct core API
|
||||
|
||||
The repository already includes an in-process embedding path in `codex-rs/app-server/src/in_process.rs`. That runtime preserves app-server semantics while replacing stdio/websocket transport with in-memory channels.
|
||||
|
||||
That makes it a much better browser boundary than `BrowserCodex.submit_turn(...)`.
|
||||
|
||||
## Goals
|
||||
|
||||
- Preserve the existing app-server request and event model in the browser.
|
||||
- Keep a long-lived runtime alive across multiple browser turns.
|
||||
- Support multi-turn conversations without rebuilding browser-specific thread state each turn.
|
||||
- Reuse existing app-server features such as `thread/start`, `turn/start`, `turn/interrupt`, and server-driven approval requests.
|
||||
- Keep the wasm integration transport-local and in-process. The browser should not need to run a real socket server to use app-server semantics.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- This document does not require full native parity for browser tools, filesystem access, or sandboxing.
|
||||
- This document does not solve browser persistence by itself. State DB and rollout persistence still need separate wasm-capable implementations.
|
||||
- This document does not require reusing `run_main_with_transport(...)` directly. The proposal reuses the app-server boundary, not that exact native entrypoint.
|
||||
|
||||
## Proposal
|
||||
|
||||
### High-Level Design
|
||||
|
||||
Replace the current direct-thread browser wrapper with a wasm-facing wrapper around an embedded in-process app-server runtime.
|
||||
|
||||
This refactor should be a replacement, not an addition. We should remove the existing `BrowserCodex` implementation as part of the cleanup and move the current prototype onto the new app-server-based path.
|
||||
|
||||
The new stack would look like:
|
||||
|
||||
```text
|
||||
JS UI
|
||||
-> wasm wrapper
|
||||
-> in-process app-server runtime
|
||||
-> MessageProcessor
|
||||
-> ThreadManager / Codex core
|
||||
```
|
||||
|
||||
Instead of calling `thread.submit(Op::UserTurn { ... })` directly, the browser would:
|
||||
|
||||
- start one in-process app-server runtime for the browser session
|
||||
- send app-server client requests into that runtime
|
||||
- consume app-server server notifications and server requests from that runtime
|
||||
|
||||
### Browser Boundary
|
||||
|
||||
The wasm wrapper should expose an API shaped like the app-server protocol rather than the current `submit_turn(prompt, on_event)` helper.
|
||||
|
||||
A minimal JS-facing API is:
|
||||
|
||||
- `start(options) -> handle`
|
||||
- `request(request) -> Promise<response>`
|
||||
- `notify(notification) -> Promise<void>` or `void`
|
||||
- `nextEvent() -> Promise<event | null>`
|
||||
- `respondToServerRequest(requestId, result) -> Promise<void>`
|
||||
- `failServerRequest(requestId, error) -> Promise<void>`
|
||||
- `shutdown() -> Promise<void>`
|
||||
|
||||
This mirrors the capabilities already present on `InProcessClientHandle`:
|
||||
|
||||
- `request(...)`
|
||||
- `notify(...)`
|
||||
- `next_event()`
|
||||
- `respond_to_server_request(...)`
|
||||
- `fail_server_request(...)`
|
||||
- `shutdown()`
|
||||
|
||||
### Runtime Lifecycle
|
||||
|
||||
The browser should create one long-lived in-process app-server runtime and keep it alive until the browser session is reset or closed.
|
||||
|
||||
Expected lifecycle:
|
||||
|
||||
1. Browser constructs wasm wrapper.
|
||||
2. Wrapper builds `InProcessStartArgs`.
|
||||
3. Wrapper calls `codex_app_server::in_process::start(...)`.
|
||||
4. Wrapper keeps the returned handle alive across turns.
|
||||
5. JS sends requests such as `thread/start` and `turn/start`.
|
||||
6. JS drains streamed server events with `nextEvent()`.
|
||||
7. On shutdown or reset, wrapper calls `shutdown()`.
|
||||
|
||||
This is the key change from the current design. The runtime is session-scoped, not turn-scoped.
|
||||
|
||||
### Message Flow
|
||||
|
||||
For a typical first turn:
|
||||
|
||||
1. JS calls `request(thread/start { ... })`.
|
||||
2. The app-server returns a thread id.
|
||||
3. JS calls `request(turn/start { threadId, input, ... })`.
|
||||
4. The app-server returns an in-progress turn response.
|
||||
5. JS repeatedly calls `nextEvent()` or receives pushed events.
|
||||
6. The runtime emits `turn/started`, item deltas, tool events, and `turn/completed`.
|
||||
|
||||
For later turns:
|
||||
|
||||
1. JS reuses the existing thread id.
|
||||
2. JS calls `request(turn/start { threadId, input, ... })` again.
|
||||
3. The same app-server runtime and the same underlying thread/session continue processing.
|
||||
|
||||
This aligns browser behavior with native behavior and removes the current per-turn session reset.
|
||||
|
||||
### Event Model
|
||||
|
||||
The browser should consume app-server events, not raw core `EventMsg` values.
|
||||
|
||||
That gives the browser:
|
||||
|
||||
- a stable protocol-shaped event stream
|
||||
- server notifications for turn lifecycle and content updates
|
||||
- server requests for approvals and other interactive flows
|
||||
- lag/backpressure signals already defined by the in-process embedding
|
||||
|
||||
It also gives us a cleaner debugging surface because the browser can log:
|
||||
|
||||
- outgoing client requests
|
||||
- client notifications
|
||||
- incoming server notifications
|
||||
- incoming server requests
|
||||
- responses to server requests
|
||||
|
||||
### Why Use `in_process` Instead Of `run_main_with_transport`
|
||||
|
||||
`run_main_with_transport(...)` is not the right wasm entrypoint.
|
||||
|
||||
It hardcodes:
|
||||
|
||||
- stdio or websocket transport startup
|
||||
- native signal handling
|
||||
- native logging and DB startup assumptions
|
||||
|
||||
By contrast, `in_process.rs` already does the important part we want:
|
||||
|
||||
- run `MessageProcessor`
|
||||
- keep the app-server request/notification/event contract
|
||||
- replace transport with in-memory channels
|
||||
|
||||
So the design should reuse app-server semantics through `in_process`, not try to reuse the native transport bootstrap unchanged.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### 1. Replace `BrowserCodex` With A WASM App-Server Wrapper
|
||||
|
||||
Add a new browser-facing wrapper that owns:
|
||||
|
||||
- the long-lived in-process app-server handle
|
||||
- browser-specific runtime services such as the code executor bridge
|
||||
- browser session options needed to build config and initialize params
|
||||
|
||||
This wrapper replaces `BrowserCodex` as the main orchestration entrypoint.
|
||||
|
||||
As part of this refactor we should:
|
||||
|
||||
- remove the existing direct-thread `BrowserCodex` implementation
|
||||
- remove `BrowserSession` as a browser-specific wrapper around `CodexThread`
|
||||
- update the current browser prototype to construct and use the new app-server-based wrapper instead
|
||||
|
||||
We do not want to maintain two browser orchestration paths. The prototype should become the first consumer of the new design.
|
||||
|
||||
### 2. Request Serialization
|
||||
|
||||
The wrapper should accept JSON or `JsValue` payloads that correspond to app-server requests and notifications.
|
||||
|
||||
At the wasm boundary:
|
||||
|
||||
- JS passes a request object
|
||||
- wasm deserializes into `ClientRequest` or `ClientNotification`
|
||||
- `in_process` handles the request
|
||||
- wasm serializes responses and events back to JS
|
||||
|
||||
This keeps the browser API close to the existing protocol and avoids introducing a second custom Rust-to-JS command language.
|
||||
|
||||
### 3. Thread Ownership
|
||||
|
||||
The browser should treat thread ids as app-server resources, not as direct `CodexThread` handles.
|
||||
|
||||
That means:
|
||||
|
||||
- creating threads through `thread/start`
|
||||
- reading state through `thread/read` and related APIs
|
||||
- starting turns through `turn/start`
|
||||
- steering or interrupting turns through existing turn APIs
|
||||
|
||||
This is an important layering choice. The browser should stop owning direct thread runtime objects.
|
||||
|
||||
### 4. Approval And Server Request Handling
|
||||
|
||||
The browser must support app-server initiated requests back to the client.
|
||||
|
||||
Examples include:
|
||||
|
||||
- tool approval requests
|
||||
- user input requests
|
||||
- future browser-specific interactive flows
|
||||
|
||||
When `nextEvent()` yields a `ServerRequest`, JS must either:
|
||||
|
||||
- answer with `respondToServerRequest(...)`
|
||||
- or reject with `failServerRequest(...)`
|
||||
|
||||
This is a capability the current direct-thread wrapper does not model cleanly.
|
||||
|
||||
### 5. Debugging
|
||||
|
||||
The browser wrapper should log the app-server boundary directly.
|
||||
|
||||
Recommended browser-side logging:
|
||||
|
||||
- every outgoing request with method and id
|
||||
- every request result
|
||||
- every incoming server notification
|
||||
- every incoming server request
|
||||
- every reply to a server request
|
||||
- backpressure or lag markers
|
||||
|
||||
This is a better debugging surface than ad hoc logging around direct `submit()` and `next_event()` calls because it reflects the real control plane used by native clients.
|
||||
|
||||
## Storage And Persistence
|
||||
|
||||
This proposal improves the control plane, but it does not by itself provide browser persistence.
|
||||
|
||||
There are two separate persistence problems:
|
||||
|
||||
1. Core state DB
|
||||
Core currently initializes session storage internally. On `wasm32`, the current state DB bridge is stubbed and returns `None`.
|
||||
|
||||
2. Rollout persistence
|
||||
The current wasm rollout recorder is also stubbed. Recording is effectively a no-op and history reload is unavailable.
|
||||
|
||||
As a result, the embedded app-server design gives us:
|
||||
|
||||
- live multi-turn continuity within a running browser session
|
||||
|
||||
But it does not yet give us:
|
||||
|
||||
- browser reload/resume
|
||||
- durable thread metadata
|
||||
- durable rollout history
|
||||
|
||||
Those require follow-on work below the `in_process` layer.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Milestone 1: Replace The Prototype Runtime With `in_process`
|
||||
|
||||
Build the wasm wrapper on top of `codex_app_server::in_process` and migrate the existing prototype to use it.
|
||||
|
||||
Scope:
|
||||
|
||||
- long-lived runtime
|
||||
- request/notification/event API exposed to JS
|
||||
- thread and turn flow through app-server methods
|
||||
- browser code executor still supplied through existing wasm/runtime hooks
|
||||
- remove the direct `BrowserCodex` / `BrowserSession` path from `wasm-harness`
|
||||
|
||||
Success criteria:
|
||||
|
||||
- multi-turn browser session works without resetting runtime state between turns
|
||||
- browser logs show app-server request and event traffic
|
||||
- browser no longer calls `thread.submit(Op::UserTurn { ... })` directly
|
||||
- the existing browser prototype runs on the app-server-based implementation
|
||||
- there is only one supported browser runtime path in the codebase
|
||||
|
||||
### Milestone 2: Align With `codex-app-server-client`
|
||||
|
||||
Decide whether the wasm wrapper should sit directly on `in_process` or on a thinner variant of `codex-app-server-client`.
|
||||
|
||||
The client facade is appealing because it already wraps:
|
||||
|
||||
- in-process runtime startup
|
||||
- event forwarding
|
||||
- server-request resolution helpers
|
||||
- convergence with the remote app-server client shape
|
||||
|
||||
This may reduce custom browser-side orchestration code.
|
||||
|
||||
### Milestone 3: Browser Persistence
|
||||
|
||||
Add real wasm-backed persistence for:
|
||||
|
||||
- rollout history
|
||||
- thread metadata
|
||||
- any state DB-backed browser features we want to preserve across reloads
|
||||
|
||||
This likely requires explicit storage seams below the app-server wrapper.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Keep The Current `BrowserCodex` Model And Preserve Session State
|
||||
|
||||
We could keep the current wrapper and only stop clearing `self.session`.
|
||||
|
||||
That would likely fix the immediate memory bug, but it would not fix the larger architectural problem:
|
||||
|
||||
- the browser would still use a custom direct-thread boundary
|
||||
- app-server APIs would still need browser-specific re-exposure
|
||||
- debugging would still happen at a less stable abstraction level
|
||||
|
||||
This is a tactical fix, not the design we want to converge on.
|
||||
|
||||
### Reuse `run_main_with_transport(...)` Directly
|
||||
|
||||
We could try to add a custom wasm transport and plug it into `run_main_with_transport(...)`.
|
||||
|
||||
This is not attractive because the function currently bundles:
|
||||
|
||||
- transport startup
|
||||
- native shutdown handling
|
||||
- logging and telemetry bootstrap
|
||||
- native transport assumptions
|
||||
|
||||
`in_process` is already a better split for embedding.
|
||||
|
||||
## Risks
|
||||
|
||||
- The browser protocol surface becomes closer to app-server, which may require slightly more client-side plumbing than the current `submit_turn(prompt)` helper.
|
||||
- Some app-server internals still assume native runtime pieces and may need additional injection points for wasm.
|
||||
- Storage is still unresolved. This design fixes control-plane drift first, not persistence.
|
||||
- If we expose raw JSON-RPC too literally to JS, the browser API may become awkward. We should keep the protocol shape while still providing small ergonomic helpers.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. Should the wasm wrapper expose raw JSON-RPC payloads, typed helper methods, or both?
|
||||
2. Should the browser wrap `in_process` directly, or should it reuse a slimmer `codex-app-server-client` facade?
|
||||
3. Which app-server methods do we want to support in browser v1 beyond `thread/start` and `turn/start`?
|
||||
4. Do we want server events delivered by pull (`nextEvent`) only, or also by JS callback subscription?
|
||||
5. What is the right storage abstraction for browser-backed rollout and state DB persistence?
|
||||
|
||||
## Recommended Decisions For Implementation Start
|
||||
|
||||
To begin implementation, we should make the following decisions explicit.
|
||||
|
||||
- Browser API surface
|
||||
Recommendation:
|
||||
Expose a thin typed wrapper over the app-server protocol rather than raw JSON-RPC only. The JS API should provide ergonomic helpers such as `startThread`, `startTurn`, `interruptTurn`, `nextEvent`, and `respondToServerRequest`, while staying close to app-server request and event types under the hood.
|
||||
|
||||
- `in_process` vs `codex-app-server-client`
|
||||
Recommendation:
|
||||
Start directly on `codex_app_server::in_process`. It is the simpler runtime dependency and gives us direct control in wasm. We should borrow the client facade's worker and event-forwarding patterns where useful, but avoid adding a second abstraction layer unless the wasm wrapper clearly grows into it.
|
||||
|
||||
- Session lifecycle
|
||||
Recommendation:
|
||||
Create one long-lived embedded app-server runtime per browser session and keep it alive until explicit reset or shutdown. Runtime lifetime should be browser-session scoped, not turn scoped. Reset should happen only on explicit session teardown, configuration changes that require rebuild, or API key changes.
|
||||
|
||||
- Event delivery model
|
||||
Recommendation:
|
||||
Use callback subscription as the primary browser-facing event model, while optionally retaining a polling escape hatch for tests or simple consumers. Internally we can still drain `next_event()`, but most browser UI code is simpler if events are pushed into JS callbacks.
|
||||
|
||||
- Server request handling
|
||||
Recommendation:
|
||||
Treat server requests as first-class and require explicit client handling. The wrapper should surface every `ServerRequest` to JS and require the browser to answer or reject it. For unsupported request types in browser v1, reject them explicitly with a clear error rather than letting turns hang.
|
||||
|
||||
- v1 app-server method set
|
||||
Recommendation:
|
||||
Support a narrow but complete thread and turn slice in browser v1: `thread/start`, `thread/read`, `turn/start`, `turn/interrupt`, and `turn/steer`. That is enough for a real multi-turn conversational product and debugging workflow without taking on the full app-server surface immediately.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Move the browser runtime to an embedded in-process app-server boundary.
|
||||
|
||||
That keeps the browser on the same architectural path as native clients, fixes the current per-turn session model, improves debugging, and creates a better long-term seam for browser-specific runtime and storage work.
|
||||
18
AGENTS.md
18
AGENTS.md
@@ -40,6 +40,7 @@ In the codex-rs folder where the rust code lives:
|
||||
`codex-rs/tui/src/bottom_pane/mod.rs`, and similarly central orchestration modules.
|
||||
- When extracting code from a large module, move the related tests and module/type docs toward
|
||||
the new implementation so the invariants stay close to the code that owns them.
|
||||
- When running Rust commands (e.g. `just fix` or `cargo test`) be patient with the command and never try to kill them using the PID. Rust lock can make the execution slow, this is expected.
|
||||
|
||||
Run `just fmt` (in `codex-rs` directory) automatically after you have finished making Rust code changes; do not ask for approval to run it. Additionally, run the tests:
|
||||
|
||||
@@ -48,14 +49,27 @@ Run `just fmt` (in `codex-rs` directory) automatically after you have finished m
|
||||
|
||||
Before finalizing a large change to `codex-rs`, run `just fix -p <project>` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspace‑wide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Do not re-run tests after running `fix` or `fmt`.
|
||||
|
||||
Also run `just argument-comment-lint` to ensure the codebase is clean of comment lint errors.
|
||||
|
||||
## The `codex-core` crate
|
||||
|
||||
Over time, the `codex-core` crate (defined in `codex-rs/core/`) has become bloated because it is the largest crate, so it is often easier to add something new to `codex-core` rather than refactor out the library code you need so your new code neither takes a dependency on, nor contributes to the size of, `codex-core`.
|
||||
|
||||
To that end: **resist adding code to codex-core**!
|
||||
|
||||
Particularly when introducing a new concept/feature/API, before adding to `codex-core`, consider whether:
|
||||
|
||||
- There is an existing crate other than `codex-core` that is an appropriate place for your new code to live.
|
||||
- It is time to introduce a new crate to the Cargo workspace for your new functionality. Refactor existing code as necessary to make this happen.
|
||||
|
||||
Likewise, when reviewing code, do not hesitate to push back on PRs that would unnecessarily add code to `codex-core`.
|
||||
|
||||
## TUI style conventions
|
||||
|
||||
See `codex-rs/tui/styles.md`.
|
||||
|
||||
## TUI code conventions
|
||||
|
||||
- When a change lands in `codex-rs/tui` and `codex-rs/tui_app_server` has a parallel implementation of the same behavior, reflect the change in `codex-rs/tui_app_server` too unless there is a documented reason not to.
|
||||
|
||||
- Use concise styling helpers from ratatui’s Stylize trait.
|
||||
- Basic spans: use "text".into()
|
||||
- Styled spans: use "text".red(), "text".green(), "text".magenta(), "text".dim(), etc.
|
||||
|
||||
@@ -17,12 +17,19 @@ platform(
|
||||
platform(
|
||||
name = "local_windows",
|
||||
constraint_values = [
|
||||
# We just need to pick one of the ABIs. Do the same one we target.
|
||||
"@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm",
|
||||
],
|
||||
parents = ["@platforms//host"],
|
||||
)
|
||||
|
||||
platform(
|
||||
name = "local_windows_msvc",
|
||||
constraint_values = [
|
||||
"@rules_rs//rs/experimental/platforms/constraints:windows_msvc",
|
||||
],
|
||||
parents = ["@platforms//host"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "rbe",
|
||||
actual = "@rbe_platform",
|
||||
|
||||
304
MODULE.bazel
304
MODULE.bazel
@@ -1,31 +1,61 @@
|
||||
module(name = "codex")
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.8.2")
|
||||
bazel_dep(name = "platforms", version = "1.0.0")
|
||||
bazel_dep(name = "llvm", version = "0.6.7")
|
||||
bazel_dep(name = "llvm", version = "0.6.8")
|
||||
# The upstream LLVM archive contains a few unix-only symlink entries and is
|
||||
# missing a couple of MinGW compatibility archives that windows-gnullvm needs
|
||||
# during extraction and linking, so patch it until upstream grows native support.
|
||||
single_version_override(
|
||||
module_name = "llvm",
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
"//patches:llvm_windows_symlink_extract.patch",
|
||||
],
|
||||
)
|
||||
# Abseil picks a MinGW pthread TLS path that does not match our hermetic
|
||||
# windows-gnullvm toolchain; force it onto the portable C++11 thread-local path.
|
||||
single_version_override(
|
||||
module_name = "abseil-cpp",
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
"//patches:abseil_windows_gnullvm_thread_identity.patch",
|
||||
],
|
||||
)
|
||||
|
||||
register_toolchains("@llvm//toolchain:all")
|
||||
|
||||
osx = use_extension("@llvm//extensions:osx.bzl", "osx")
|
||||
osx.framework(name = "ApplicationServices")
|
||||
osx.framework(name = "AppKit")
|
||||
osx.framework(name = "ColorSync")
|
||||
osx.framework(name = "CoreFoundation")
|
||||
osx.framework(name = "CoreGraphics")
|
||||
osx.framework(name = "CoreServices")
|
||||
osx.framework(name = "CoreText")
|
||||
osx.framework(name = "AudioToolbox")
|
||||
osx.framework(name = "CFNetwork")
|
||||
osx.framework(name = "FontServices")
|
||||
osx.framework(name = "AudioUnit")
|
||||
osx.framework(name = "CoreAudio")
|
||||
osx.framework(name = "CoreAudioTypes")
|
||||
osx.framework(name = "Foundation")
|
||||
osx.framework(name = "ImageIO")
|
||||
osx.framework(name = "IOKit")
|
||||
osx.framework(name = "Kernel")
|
||||
osx.framework(name = "OSLog")
|
||||
osx.framework(name = "Security")
|
||||
osx.framework(name = "SystemConfiguration")
|
||||
osx.from_archive(
|
||||
sha256 = "1bde70c0b1c2ab89ff454acbebf6741390d7b7eb149ca2a3ca24cc9203a408b7",
|
||||
strip_prefix = "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX26.4.sdk",
|
||||
type = "pkg",
|
||||
urls = [
|
||||
"https://swcdn.apple.com/content/downloads/32/53/047-96692-A_OAHIHT53YB/ybtshxmrcju8m2qvw3w5elr4rajtg1x3y3/CLTools_macOSNMOS_SDK.pkg",
|
||||
],
|
||||
)
|
||||
osx.frameworks(names = [
|
||||
"ApplicationServices",
|
||||
"AppKit",
|
||||
"ColorSync",
|
||||
"CoreFoundation",
|
||||
"CoreGraphics",
|
||||
"CoreServices",
|
||||
"CoreText",
|
||||
"AudioToolbox",
|
||||
"CFNetwork",
|
||||
"FontServices",
|
||||
"AudioUnit",
|
||||
"CoreAudio",
|
||||
"CoreAudioTypes",
|
||||
"Foundation",
|
||||
"ImageIO",
|
||||
"IOKit",
|
||||
"Kernel",
|
||||
"OSLog",
|
||||
"Security",
|
||||
"SystemConfiguration",
|
||||
])
|
||||
use_repo(osx, "macos_sdk")
|
||||
|
||||
# Needed to disable xcode...
|
||||
@@ -33,10 +63,77 @@ bazel_dep(name = "apple_support", version = "2.1.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.16")
|
||||
bazel_dep(name = "rules_platform", version = "0.1.0")
|
||||
bazel_dep(name = "rules_rs", version = "0.0.43")
|
||||
# `rules_rs` 0.0.43 does not model `windows-gnullvm` as a distinct Windows exec
|
||||
# platform, so patch it until upstream grows that support for both x86_64 and
|
||||
# aarch64.
|
||||
single_version_override(
|
||||
module_name = "rules_rs",
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
"//patches:rules_rs_windows_gnullvm_exec.patch",
|
||||
],
|
||||
version = "0.0.43",
|
||||
)
|
||||
|
||||
rules_rust = use_extension("@rules_rs//rs/experimental:rules_rust.bzl", "rules_rust")
|
||||
# Build-script probe binaries inherit CFLAGS/CXXFLAGS from Bazel's C++
|
||||
# toolchain. On `windows-gnullvm`, llvm-mingw does not ship
|
||||
# `libssp_nonshared`, so strip the forwarded stack-protector flags there.
|
||||
rules_rust.patch(
|
||||
patches = [
|
||||
"//patches:rules_rust_windows_gnullvm_build_script.patch",
|
||||
"//patches:rules_rust_windows_exec_msvc_build_script_env.patch",
|
||||
"//patches:rules_rust_windows_bootstrap_process_wrapper_linker.patch",
|
||||
"//patches:rules_rust_windows_msvc_direct_link_args.patch",
|
||||
"//patches:rules_rust_windows_exec_bin_target.patch",
|
||||
"//patches:rules_rust_windows_exec_std.patch",
|
||||
"//patches:rules_rust_windows_exec_rustc_dev_rlib.patch",
|
||||
"//patches:rules_rust_repository_set_exec_constraints.patch",
|
||||
],
|
||||
strip = 1,
|
||||
)
|
||||
use_repo(rules_rust, "rules_rust")
|
||||
|
||||
nightly_rust = use_extension(
|
||||
"@rules_rs//rs/experimental:rules_rust_reexported_extensions.bzl",
|
||||
"rust",
|
||||
)
|
||||
nightly_rust.toolchain(
|
||||
versions = ["nightly/2025-09-18"],
|
||||
dev_components = True,
|
||||
edition = "2024",
|
||||
)
|
||||
# Keep Windows exec tools on MSVC so Bazel helper binaries link correctly, but
|
||||
# lint crate targets as `windows-gnullvm` to preserve the repo's actual cfgs.
|
||||
nightly_rust.repository_set(
|
||||
name = "rust_windows_x86_64",
|
||||
dev_components = True,
|
||||
edition = "2024",
|
||||
exec_triple = "x86_64-pc-windows-msvc",
|
||||
exec_compatible_with = [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:windows",
|
||||
"@rules_rs//rs/experimental/platforms/constraints:windows_msvc",
|
||||
],
|
||||
target_compatible_with = [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:windows",
|
||||
"@rules_rs//rs/experimental/platforms/constraints:windows_msvc",
|
||||
],
|
||||
target_triple = "x86_64-pc-windows-msvc",
|
||||
versions = ["nightly/2025-09-18"],
|
||||
)
|
||||
nightly_rust.repository_set(
|
||||
name = "rust_windows_x86_64",
|
||||
target_compatible_with = [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:windows",
|
||||
"@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm",
|
||||
],
|
||||
target_triple = "x86_64-pc-windows-gnullvm",
|
||||
)
|
||||
use_repo(nightly_rust, "rust_toolchains")
|
||||
|
||||
toolchains = use_extension("@rules_rs//rs/experimental/toolchains:module_extension.bzl", "toolchains")
|
||||
toolchains.toolchain(
|
||||
edition = "2024",
|
||||
@@ -45,6 +142,7 @@ toolchains.toolchain(
|
||||
use_repo(toolchains, "default_rust_toolchains")
|
||||
|
||||
register_toolchains("@default_rust_toolchains//:all")
|
||||
register_toolchains("@rust_toolchains//:all")
|
||||
|
||||
crate = use_extension("@rules_rs//rs:extensions.bzl", "crate")
|
||||
crate.from_cargo(
|
||||
@@ -54,10 +152,33 @@ crate.from_cargo(
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"aarch64-apple-darwin",
|
||||
# Keep both Windows ABIs in the generated Cargo metadata: the V8
|
||||
# experiment still consumes release assets that only exist under the
|
||||
# MSVC names while targeting the GNU toolchain.
|
||||
"aarch64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-gnullvm",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-pc-windows-gnullvm",
|
||||
],
|
||||
use_experimental_platforms = True,
|
||||
)
|
||||
crate.from_cargo(
|
||||
name = "argument_comment_lint_crates",
|
||||
cargo_lock = "//tools/argument-comment-lint:Cargo.lock",
|
||||
cargo_toml = "//tools/argument-comment-lint:Cargo.toml",
|
||||
platform_triples = [
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-gnullvm",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-pc-windows-gnullvm",
|
||||
],
|
||||
use_experimental_platforms = True,
|
||||
@@ -78,10 +199,19 @@ crate.annotation(
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
"//patches:aws-lc-sys_memcmp_check.patch",
|
||||
"//patches:aws-lc-sys_windows_msvc_prebuilt_nasm.patch",
|
||||
"//patches:aws-lc-sys_windows_msvc_memcmp_probe.patch",
|
||||
],
|
||||
)
|
||||
|
||||
crate.annotation(
|
||||
# The build script only validates embedded source/version metadata.
|
||||
crate = "rustc_apfloat",
|
||||
gen_build_script = "off",
|
||||
)
|
||||
|
||||
inject_repo(crate, "zstd")
|
||||
use_repo(crate, "argument_comment_lint_crates")
|
||||
|
||||
bazel_dep(name = "bzip2", version = "1.0.8.bcr.3")
|
||||
|
||||
@@ -132,6 +262,35 @@ crate.annotation(
|
||||
workspace_cargo_toml = "rust/runfiles/Cargo.toml",
|
||||
)
|
||||
|
||||
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
|
||||
new_local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "new_local_repository")
|
||||
|
||||
new_local_repository(
|
||||
name = "v8_targets",
|
||||
build_file = "//third_party/v8:BUILD.bazel",
|
||||
path = "third_party/v8",
|
||||
)
|
||||
|
||||
crate.annotation(
|
||||
build_script_data = [
|
||||
"@v8_targets//:rusty_v8_archive_for_target",
|
||||
"@v8_targets//:rusty_v8_binding_for_target",
|
||||
],
|
||||
build_script_env = {
|
||||
"RUSTY_V8_ARCHIVE": "$(execpath @v8_targets//:rusty_v8_archive_for_target)",
|
||||
"RUSTY_V8_SRC_BINDING_PATH": "$(execpath @v8_targets//:rusty_v8_binding_for_target)",
|
||||
},
|
||||
crate = "v8",
|
||||
gen_build_script = "on",
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
"//patches:rusty_v8_prebuilt_out_dir.patch",
|
||||
],
|
||||
)
|
||||
|
||||
inject_repo(crate, "v8_targets")
|
||||
|
||||
llvm = use_extension("@llvm//extensions:llvm.bzl", "llvm")
|
||||
use_repo(llvm, "llvm-project")
|
||||
|
||||
@@ -174,6 +333,109 @@ crate.annotation(
|
||||
|
||||
inject_repo(crate, "alsa_lib")
|
||||
|
||||
bazel_dep(name = "v8", version = "14.6.202.9")
|
||||
archive_override(
|
||||
module_name = "v8",
|
||||
integrity = "sha256-JphDwLAzsd9KvgRZ7eQvNtPU6qGd3XjFt/a/1QITAJU=",
|
||||
patch_strip = 3,
|
||||
patches = [
|
||||
"//patches:v8_module_deps.patch",
|
||||
"//patches:v8_bazel_rules.patch",
|
||||
"//patches:v8_source_portability.patch",
|
||||
],
|
||||
strip_prefix = "v8-14.6.202.9",
|
||||
urls = ["https://github.com/v8/v8/archive/refs/tags/14.6.202.9.tar.gz"],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "v8_crate_146_4_0",
|
||||
build_file = "//third_party/v8:v8_crate.BUILD.bazel",
|
||||
sha256 = "d97bcac5cdc5a195a4813f1855a6bc658f240452aac36caa12fd6c6f16026ab1",
|
||||
strip_prefix = "v8-146.4.0",
|
||||
type = "tar.gz",
|
||||
urls = ["https://static.crates.io/crates/v8/v8-146.4.0.crate"],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_aarch64_apple_darwin_archive",
|
||||
downloaded_file_path = "librusty_v8_release_aarch64-apple-darwin.a.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_aarch64-apple-darwin.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_aarch64_unknown_linux_gnu_archive",
|
||||
downloaded_file_path = "librusty_v8_release_aarch64-unknown-linux-gnu.a.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_aarch64-unknown-linux-gnu.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_aarch64_pc_windows_msvc_archive",
|
||||
downloaded_file_path = "rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_x86_64_apple_darwin_archive",
|
||||
downloaded_file_path = "librusty_v8_release_x86_64-apple-darwin.a.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_x86_64-apple-darwin.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_x86_64_unknown_linux_gnu_archive",
|
||||
downloaded_file_path = "librusty_v8_release_x86_64-unknown-linux-gnu.a.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_x86_64-unknown-linux-gnu.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_x86_64_pc_windows_msvc_archive",
|
||||
downloaded_file_path = "rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_aarch64_unknown_linux_musl_archive",
|
||||
downloaded_file_path = "librusty_v8_release_aarch64-unknown-linux-musl.a.gz",
|
||||
urls = [
|
||||
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/librusty_v8_release_aarch64-unknown-linux-musl.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_aarch64_unknown_linux_musl_binding",
|
||||
downloaded_file_path = "src_binding_release_aarch64-unknown-linux-musl.rs",
|
||||
urls = [
|
||||
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/src_binding_release_aarch64-unknown-linux-musl.rs",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_x86_64_unknown_linux_musl_archive",
|
||||
downloaded_file_path = "librusty_v8_release_x86_64-unknown-linux-musl.a.gz",
|
||||
urls = [
|
||||
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/librusty_v8_release_x86_64-unknown-linux-musl.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
name = "rusty_v8_146_4_0_x86_64_unknown_linux_musl_binding",
|
||||
downloaded_file_path = "src_binding_release_x86_64-unknown-linux-musl.rs",
|
||||
urls = [
|
||||
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/src_binding_release_x86_64-unknown-linux-musl.rs",
|
||||
],
|
||||
)
|
||||
|
||||
use_repo(crate, "crates")
|
||||
|
||||
bazel_dep(name = "libcap", version = "2.27.bcr.1")
|
||||
|
||||
118
MODULE.bazel.lock
generated
118
MODULE.bazel.lock
generated
File diff suppressed because one or more lines are too long
@@ -1,3 +1,18 @@
|
||||
exports_files([
|
||||
"clippy.toml",
|
||||
"node-version.txt",
|
||||
])
|
||||
|
||||
filegroup(
|
||||
name = "workspace-files",
|
||||
srcs = glob(
|
||||
[
|
||||
"*",
|
||||
".cargo/**",
|
||||
],
|
||||
exclude = [
|
||||
"BUILD.bazel",
|
||||
],
|
||||
),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
928
codex-rs/Cargo.lock
generated
928
codex-rs/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"analytics",
|
||||
"backend-client",
|
||||
"ansi-escape",
|
||||
"async-utils",
|
||||
@@ -11,7 +12,9 @@ members = [
|
||||
"apply-patch",
|
||||
"arg0",
|
||||
"feedback",
|
||||
"features",
|
||||
"codex-backend-openapi-models",
|
||||
"code-mode",
|
||||
"cloud-requirements",
|
||||
"cloud-tasks",
|
||||
"cloud-tasks-client",
|
||||
@@ -22,10 +25,12 @@ members = [
|
||||
"shell-escalation",
|
||||
"skills",
|
||||
"core",
|
||||
"environment",
|
||||
"core-skills",
|
||||
"hooks",
|
||||
"instructions",
|
||||
"secrets",
|
||||
"exec",
|
||||
"exec-server",
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"keyring-store",
|
||||
@@ -38,15 +43,19 @@ members = [
|
||||
"ollama",
|
||||
"process-hardening",
|
||||
"protocol",
|
||||
"rollout",
|
||||
"rmcp-client",
|
||||
"responses-api-proxy",
|
||||
"sandboxing",
|
||||
"stdio-to-uds",
|
||||
"otel",
|
||||
"tui",
|
||||
"tui_app_server",
|
||||
"tools",
|
||||
"v8-poc",
|
||||
"wasm-harness",
|
||||
"utils/absolute-path",
|
||||
"utils/cargo-bin",
|
||||
"utils/git",
|
||||
"git-utils",
|
||||
"utils/cache",
|
||||
"utils/image",
|
||||
"utils/json-to-toml",
|
||||
@@ -61,15 +70,18 @@ members = [
|
||||
"utils/sleep-inhibitor",
|
||||
"utils/approval-presets",
|
||||
"utils/oss",
|
||||
"utils/output-truncation",
|
||||
"utils/path-utils",
|
||||
"utils/plugins",
|
||||
"utils/fuzzy-match",
|
||||
"utils/stream-parser",
|
||||
"utils/template",
|
||||
"codex-client",
|
||||
"codex-api",
|
||||
"state",
|
||||
"terminal-detection",
|
||||
"codex-experimental-api-macros",
|
||||
"test-macros",
|
||||
"package-manager",
|
||||
"artifacts",
|
||||
"plugin",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -86,9 +98,9 @@ license = "Apache-2.0"
|
||||
# Internal
|
||||
app_test_support = { path = "app-server/tests/common" }
|
||||
codex-ansi-escape = { path = "ansi-escape" }
|
||||
codex-analytics = { path = "analytics" }
|
||||
codex-api = { path = "codex-api" }
|
||||
codex-artifacts = { path = "artifacts" }
|
||||
codex-package-manager = { path = "package-manager" }
|
||||
codex-code-mode = { path = "code-mode" }
|
||||
codex-app-server = { path = "app-server" }
|
||||
codex-app-server-client = { path = "app-server-client" }
|
||||
codex-app-server-protocol = { path = "app-server-protocol" }
|
||||
@@ -104,14 +116,17 @@ codex-cloud-requirements = { path = "cloud-requirements" }
|
||||
codex-connectors = { path = "connectors" }
|
||||
codex-config = { path = "config" }
|
||||
codex-core = { path = "core" }
|
||||
codex-environment = { path = "environment" }
|
||||
codex-core-skills = { path = "core-skills" }
|
||||
codex-exec = { path = "exec" }
|
||||
codex-exec-server = { path = "exec-server" }
|
||||
codex-execpolicy = { path = "execpolicy" }
|
||||
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
|
||||
codex-feedback = { path = "feedback" }
|
||||
codex-features = { path = "features" }
|
||||
codex-file-search = { path = "file-search" }
|
||||
codex-git = { path = "utils/git" }
|
||||
codex-git-utils = { path = "git-utils" }
|
||||
codex-hooks = { path = "hooks" }
|
||||
codex-instructions = { path = "instructions" }
|
||||
codex-keyring-store = { path = "keyring-store" }
|
||||
codex-linux-sandbox = { path = "linux-sandbox" }
|
||||
codex-lmstudio = { path = "lmstudio" }
|
||||
@@ -120,19 +135,23 @@ codex-mcp-server = { path = "mcp-server" }
|
||||
codex-network-proxy = { path = "network-proxy" }
|
||||
codex-ollama = { path = "ollama" }
|
||||
codex-otel = { path = "otel" }
|
||||
codex-plugin = { path = "plugin" }
|
||||
codex-process-hardening = { path = "process-hardening" }
|
||||
codex-protocol = { path = "protocol" }
|
||||
codex-rollout = { path = "rollout" }
|
||||
codex-responses-api-proxy = { path = "responses-api-proxy" }
|
||||
codex-rmcp-client = { path = "rmcp-client" }
|
||||
codex-sandboxing = { path = "sandboxing" }
|
||||
codex-secrets = { path = "secrets" }
|
||||
codex-shell-command = { path = "shell-command" }
|
||||
codex-shell-escalation = { path = "shell-escalation" }
|
||||
codex-skills = { path = "skills" }
|
||||
codex-state = { path = "state" }
|
||||
codex-stdio-to-uds = { path = "stdio-to-uds" }
|
||||
codex-test-macros = { path = "test-macros" }
|
||||
codex-terminal-detection = { path = "terminal-detection" }
|
||||
codex-tools = { path = "tools" }
|
||||
codex-tui = { path = "tui" }
|
||||
codex-tui-app-server = { path = "tui_app_server" }
|
||||
codex-v8-poc = { path = "v8-poc" }
|
||||
codex-utils-absolute-path = { path = "utils/absolute-path" }
|
||||
codex-utils-approval-presets = { path = "utils/approval-presets" }
|
||||
codex-utils-cache = { path = "utils/cache" }
|
||||
@@ -144,12 +163,16 @@ codex-utils-home-dir = { path = "utils/home-dir" }
|
||||
codex-utils-image = { path = "utils/image" }
|
||||
codex-utils-json-to-toml = { path = "utils/json-to-toml" }
|
||||
codex-utils-oss = { path = "utils/oss" }
|
||||
codex-utils-output-truncation = { path = "utils/output-truncation" }
|
||||
codex-utils-path = { path = "utils/path-utils" }
|
||||
codex-utils-plugins = { path = "utils/plugins" }
|
||||
codex-utils-pty = { path = "utils/pty" }
|
||||
codex-utils-readiness = { path = "utils/readiness" }
|
||||
codex-utils-rustls-provider = { path = "utils/rustls-provider" }
|
||||
codex-utils-sandbox-summary = { path = "utils/sandbox-summary" }
|
||||
codex-utils-sleep-inhibitor = { path = "utils/sleep-inhibitor" }
|
||||
codex-utils-stream-parser = { path = "utils/stream-parser" }
|
||||
codex-utils-template = { path = "utils/template" }
|
||||
codex-utils-string = { path = "utils/string" }
|
||||
codex-windows-sandbox = { path = "windows-sandbox-rs" }
|
||||
core_test_support = { path = "core/tests/common" }
|
||||
@@ -161,7 +184,7 @@ allocative = "0.3.3"
|
||||
ansi-to-tui = "7.0.0"
|
||||
anyhow = "1"
|
||||
arboard = { version = "3", features = ["wayland-data-control"] }
|
||||
askama = "0.15.4"
|
||||
arc-swap = "1.9.0"
|
||||
assert_cmd = "2"
|
||||
assert_matches = "1.5.0"
|
||||
async-channel = "2.3.1"
|
||||
@@ -176,6 +199,7 @@ chrono = "0.4.43"
|
||||
clap = "4"
|
||||
clap_complete = "4"
|
||||
color-eyre = "0.6.3"
|
||||
constant_time_eq = "0.3.1"
|
||||
crossbeam-channel = "0.5.15"
|
||||
crossterm = "0.28.1"
|
||||
csv = "1.3.1"
|
||||
@@ -186,14 +210,13 @@ dirs = "6"
|
||||
dotenvy = "0.15.7"
|
||||
dunce = "1.0.4"
|
||||
encoding_rs = "0.8.35"
|
||||
fd-lock = "4.0.4"
|
||||
env-flags = "0.1.1"
|
||||
env_logger = "0.11.9"
|
||||
eventsource-stream = "0.2.3"
|
||||
flate2 = "1.1.4"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
gethostname = "1.1.0"
|
||||
globset = "0.4"
|
||||
hmac = "0.12.1"
|
||||
http = "1.3.1"
|
||||
icu_decimal = "2.1"
|
||||
icu_locale_core = "2.1"
|
||||
@@ -206,6 +229,8 @@ indexmap = "2.12.0"
|
||||
insta = "1.46.3"
|
||||
inventory = "0.3.19"
|
||||
itertools = "0.14.0"
|
||||
jsonwebtoken = "9.3.1"
|
||||
js-sys = "0.3.85"
|
||||
keyring = { version = "3.6", default-features = false }
|
||||
landlock = "0.4.4"
|
||||
lazy_static = "1"
|
||||
@@ -232,6 +257,7 @@ portable-pty = "0.9.0"
|
||||
predicates = "3"
|
||||
pretty_assertions = "1.4.1"
|
||||
pulldown-cmark = "0.10"
|
||||
quick-xml = "0.38.4"
|
||||
rand = "0.9"
|
||||
ratatui = "0.29.0"
|
||||
ratatui-macros = "0.6.0"
|
||||
@@ -240,6 +266,7 @@ regex-lite = "0.1.8"
|
||||
reqwest = "0.12"
|
||||
rmcp = { version = "0.15.0", default-features = false }
|
||||
runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" }
|
||||
v8 = "=146.4.0"
|
||||
rustls = { version = "0.23", default-features = false, features = [
|
||||
"ring",
|
||||
"std",
|
||||
@@ -278,7 +305,6 @@ supports-color = "3.0.2"
|
||||
syntect = "5"
|
||||
sys-locale = "0.3.2"
|
||||
tempfile = "3.23.0"
|
||||
tar = "0.4.44"
|
||||
test-log = "0.2.19"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = "2.0.17"
|
||||
@@ -311,10 +337,13 @@ urlencoding = "2.1"
|
||||
uuid = "1"
|
||||
vt100 = "0.16.2"
|
||||
walkdir = "2.5.0"
|
||||
wasm-bindgen = "0.2.108"
|
||||
wasm-bindgen-futures = "0.4.58"
|
||||
web-sys = "0.3.85"
|
||||
webbrowser = "1.0"
|
||||
which = "8"
|
||||
wildmatch = "2.6.1"
|
||||
zip = "2.4.2"
|
||||
zip = { version = "2.4.2", default-features = false, features = ["deflate"] }
|
||||
zstd = "0.13"
|
||||
|
||||
wiremock = "0.6"
|
||||
@@ -365,7 +394,8 @@ ignored = [
|
||||
"icu_provider",
|
||||
"openssl-sys",
|
||||
"codex-utils-readiness",
|
||||
"codex-secrets"
|
||||
"codex-utils-template",
|
||||
"codex-v8-poc",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -50,7 +50,7 @@ You can enable notifications by configuring a script that is run whenever the ag
|
||||
|
||||
### `codex exec` to run Codex programmatically/non-interactively
|
||||
|
||||
To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on.
|
||||
To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. If you provide both a prompt argument and piped stdin, Codex appends stdin as a `<stdin>` block after the prompt so patterns like `echo "my output" | codex exec "Summarize this concisely"` work naturally. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on.
|
||||
Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk.
|
||||
|
||||
### Experimenting with the Codex Sandbox
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "artifacts",
|
||||
crate_name = "codex_artifacts",
|
||||
name = "analytics",
|
||||
crate_name = "codex_analytics",
|
||||
)
|
||||
33
codex-rs/analytics/Cargo.toml
Normal file
33
codex-rs/analytics/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-analytics"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_analytics"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha1 = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
codex-login = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
671
codex-rs/analytics/src/analytics_client.rs
Normal file
671
codex-rs/analytics/src/analytics_client.rs
Normal file
@@ -0,0 +1,671 @@
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::ClientResponse;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_git_utils::collect_git_info;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::default_client::create_client;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use serde::Serialize;
|
||||
use sha1::Digest;
|
||||
use sha1::Sha1;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackEventsContext {
|
||||
pub model_slug: String,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
pub fn build_track_events_context(
|
||||
model_slug: String,
|
||||
thread_id: String,
|
||||
turn_id: String,
|
||||
) -> TrackEventsContext {
|
||||
TrackEventsContext {
|
||||
model_slug,
|
||||
thread_id,
|
||||
turn_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SkillInvocation {
|
||||
pub skill_name: String,
|
||||
pub skill_scope: SkillScope,
|
||||
pub skill_path: PathBuf,
|
||||
pub invocation_type: InvocationType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum InvocationType {
|
||||
Explicit,
|
||||
Implicit,
|
||||
}
|
||||
|
||||
pub struct AppInvocation {
|
||||
pub connector_id: Option<String>,
|
||||
pub app_name: Option<String>,
|
||||
pub invocation_type: Option<InvocationType>,
|
||||
}
|
||||
|
||||
pub enum AnalyticsFact {
|
||||
Initialize {
|
||||
connection_id: u64,
|
||||
params: InitializeParams,
|
||||
},
|
||||
Request {
|
||||
connection_id: u64,
|
||||
request_id: RequestId,
|
||||
request: Box<ClientRequest>,
|
||||
},
|
||||
Response {
|
||||
connection_id: u64,
|
||||
response: Box<ClientResponse>,
|
||||
},
|
||||
Notification(Box<ServerNotification>),
|
||||
// Facts that do not naturally exist on the app-server protocol surface, or
|
||||
// would require non-trivial protocol reshaping on this branch.
|
||||
Custom(CustomAnalyticsFact),
|
||||
}
|
||||
|
||||
pub enum CustomAnalyticsFact {
|
||||
SkillInvoked(SkillInvokedInput),
|
||||
AppMentioned(AppMentionedInput),
|
||||
AppUsed(AppUsedInput),
|
||||
PluginUsed(PluginUsedInput),
|
||||
PluginStateChanged(PluginStateChangedInput),
|
||||
}
|
||||
|
||||
pub struct SkillInvokedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub invocations: Vec<SkillInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppMentionedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub mentions: Vec<AppInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub app: AppInvocation,
|
||||
}
|
||||
|
||||
pub struct PluginUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
}
|
||||
|
||||
pub struct PluginStateChangedInput {
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
pub state: PluginState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PluginState {
|
||||
Installed,
|
||||
Uninstalled,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnalyticsReducer;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AnalyticsEventsQueue {
|
||||
sender: mpsc::Sender<AnalyticsFact>,
|
||||
app_used_emitted_keys: Arc<Mutex<HashSet<(String, String)>>>,
|
||||
plugin_used_emitted_keys: Arc<Mutex<HashSet<(String, String)>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnalyticsEventsClient {
|
||||
queue: AnalyticsEventsQueue,
|
||||
analytics_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl AnalyticsEventsQueue {
|
||||
pub(crate) fn new(auth_manager: Arc<AuthManager>, base_url: String) -> Self {
|
||||
let (sender, mut receiver) = mpsc::channel(ANALYTICS_EVENTS_QUEUE_SIZE);
|
||||
tokio::spawn(async move {
|
||||
let mut reducer = AnalyticsReducer;
|
||||
while let Some(input) = receiver.recv().await {
|
||||
let mut events = Vec::new();
|
||||
reducer.ingest(input, &mut events).await;
|
||||
send_track_events(&auth_manager, &base_url, events).await;
|
||||
}
|
||||
});
|
||||
Self {
|
||||
sender,
|
||||
app_used_emitted_keys: Arc::new(Mutex::new(HashSet::new())),
|
||||
plugin_used_emitted_keys: Arc::new(Mutex::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_send(&self, input: AnalyticsFact) {
|
||||
if self.sender.try_send(input).is_err() {
|
||||
//TODO: add a metric for this
|
||||
tracing::warn!("dropping analytics events: queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
fn should_enqueue_app_used(&self, tracking: &TrackEventsContext, app: &AppInvocation) -> bool {
|
||||
let Some(connector_id) = app.connector_id.as_ref() else {
|
||||
return true;
|
||||
};
|
||||
let mut emitted = self
|
||||
.app_used_emitted_keys
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if emitted.len() >= ANALYTICS_EVENT_DEDUPE_MAX_KEYS {
|
||||
emitted.clear();
|
||||
}
|
||||
emitted.insert((tracking.turn_id.clone(), connector_id.clone()))
|
||||
}
|
||||
|
||||
fn should_enqueue_plugin_used(
|
||||
&self,
|
||||
tracking: &TrackEventsContext,
|
||||
plugin: &PluginTelemetryMetadata,
|
||||
) -> bool {
|
||||
let mut emitted = self
|
||||
.plugin_used_emitted_keys
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if emitted.len() >= ANALYTICS_EVENT_DEDUPE_MAX_KEYS {
|
||||
emitted.clear();
|
||||
}
|
||||
emitted.insert((tracking.turn_id.clone(), plugin.plugin_id.as_key()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AnalyticsEventsClient {
|
||||
pub fn new(
|
||||
auth_manager: Arc<AuthManager>,
|
||||
base_url: String,
|
||||
analytics_enabled: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url),
|
||||
analytics_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_skill_invocations(
|
||||
&self,
|
||||
tracking: TrackEventsContext,
|
||||
invocations: Vec<SkillInvocation>,
|
||||
) {
|
||||
if invocations.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked(
|
||||
SkillInvokedInput {
|
||||
tracking,
|
||||
invocations,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_app_mentioned(&self, tracking: TrackEventsContext, mentions: Vec<AppInvocation>) {
|
||||
if mentions.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppMentioned(
|
||||
AppMentionedInput { tracking, mentions },
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) {
|
||||
if !self.queue.should_enqueue_app_used(&tracking, &app) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(
|
||||
AppUsedInput { tracking, app },
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) {
|
||||
if !self.queue.should_enqueue_plugin_used(&tracking, &plugin) {
|
||||
return;
|
||||
}
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed(
|
||||
PluginUsedInput { tracking, plugin },
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_plugin_installed(&self, plugin: PluginTelemetryMetadata) {
|
||||
self.record_fact(AnalyticsFact::Custom(
|
||||
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
|
||||
plugin,
|
||||
state: PluginState::Installed,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn track_plugin_uninstalled(&self, plugin: PluginTelemetryMetadata) {
|
||||
self.record_fact(AnalyticsFact::Custom(
|
||||
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
|
||||
plugin,
|
||||
state: PluginState::Uninstalled,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn track_plugin_enabled(&self, plugin: PluginTelemetryMetadata) {
|
||||
self.record_fact(AnalyticsFact::Custom(
|
||||
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
|
||||
plugin,
|
||||
state: PluginState::Enabled,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn track_plugin_disabled(&self, plugin: PluginTelemetryMetadata) {
|
||||
self.record_fact(AnalyticsFact::Custom(
|
||||
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
|
||||
plugin,
|
||||
state: PluginState::Disabled,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
fn record_fact(&self, input: AnalyticsFact) {
|
||||
if self.analytics_enabled == Some(false) {
|
||||
return;
|
||||
}
|
||||
self.queue.try_send(input);
|
||||
}
|
||||
}
|
||||
|
||||
const ANALYTICS_EVENTS_QUEUE_SIZE: usize = 256;
|
||||
const ANALYTICS_EVENTS_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const ANALYTICS_EVENT_DEDUPE_MAX_KEYS: usize = 4096;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TrackEventsRequest {
|
||||
events: Vec<TrackEventRequest>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum TrackEventRequest {
|
||||
SkillInvocation(SkillInvocationEventRequest),
|
||||
AppMentioned(CodexAppMentionedEventRequest),
|
||||
AppUsed(CodexAppUsedEventRequest),
|
||||
PluginUsed(CodexPluginUsedEventRequest),
|
||||
PluginInstalled(CodexPluginEventRequest),
|
||||
PluginUninstalled(CodexPluginEventRequest),
|
||||
PluginEnabled(CodexPluginEventRequest),
|
||||
PluginDisabled(CodexPluginEventRequest),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SkillInvocationEventRequest {
|
||||
event_type: &'static str,
|
||||
skill_id: String,
|
||||
skill_name: String,
|
||||
event_params: SkillInvocationEventParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SkillInvocationEventParams {
|
||||
product_client_id: Option<String>,
|
||||
skill_scope: Option<String>,
|
||||
repo_url: Option<String>,
|
||||
thread_id: Option<String>,
|
||||
invoke_type: Option<InvocationType>,
|
||||
model_slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexAppMetadata {
|
||||
connector_id: Option<String>,
|
||||
thread_id: Option<String>,
|
||||
turn_id: Option<String>,
|
||||
app_name: Option<String>,
|
||||
product_client_id: Option<String>,
|
||||
invoke_type: Option<InvocationType>,
|
||||
model_slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexAppMentionedEventRequest {
|
||||
event_type: &'static str,
|
||||
event_params: CodexAppMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexAppUsedEventRequest {
|
||||
event_type: &'static str,
|
||||
event_params: CodexAppMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexPluginMetadata {
|
||||
plugin_id: Option<String>,
|
||||
plugin_name: Option<String>,
|
||||
marketplace_name: Option<String>,
|
||||
has_skills: Option<bool>,
|
||||
mcp_server_count: Option<usize>,
|
||||
connector_ids: Option<Vec<String>>,
|
||||
product_client_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexPluginUsedMetadata {
|
||||
#[serde(flatten)]
|
||||
plugin: CodexPluginMetadata,
|
||||
thread_id: Option<String>,
|
||||
turn_id: Option<String>,
|
||||
model_slug: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexPluginEventRequest {
|
||||
event_type: &'static str,
|
||||
event_params: CodexPluginMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CodexPluginUsedEventRequest {
|
||||
event_type: &'static str,
|
||||
event_params: CodexPluginUsedMetadata,
|
||||
}
|
||||
|
||||
impl AnalyticsReducer {
|
||||
async fn ingest(&mut self, input: AnalyticsFact, out: &mut Vec<TrackEventRequest>) {
|
||||
match input {
|
||||
AnalyticsFact::Initialize {
|
||||
connection_id: _connection_id,
|
||||
params: _params,
|
||||
} => {}
|
||||
AnalyticsFact::Request {
|
||||
connection_id: _connection_id,
|
||||
request_id: _request_id,
|
||||
request: _request,
|
||||
} => {}
|
||||
AnalyticsFact::Response {
|
||||
connection_id: _connection_id,
|
||||
response: _response,
|
||||
} => {}
|
||||
AnalyticsFact::Notification(_notification) => {}
|
||||
AnalyticsFact::Custom(input) => match input {
|
||||
CustomAnalyticsFact::SkillInvoked(input) => {
|
||||
self.ingest_skill_invoked(input, out).await;
|
||||
}
|
||||
CustomAnalyticsFact::AppMentioned(input) => {
|
||||
self.ingest_app_mentioned(input, out);
|
||||
}
|
||||
CustomAnalyticsFact::AppUsed(input) => {
|
||||
self.ingest_app_used(input, out);
|
||||
}
|
||||
CustomAnalyticsFact::PluginUsed(input) => {
|
||||
self.ingest_plugin_used(input, out);
|
||||
}
|
||||
CustomAnalyticsFact::PluginStateChanged(input) => {
|
||||
self.ingest_plugin_state_changed(input, out);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn ingest_skill_invoked(
|
||||
&mut self,
|
||||
input: SkillInvokedInput,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let SkillInvokedInput {
|
||||
tracking,
|
||||
invocations,
|
||||
} = input;
|
||||
for invocation in invocations {
|
||||
let skill_scope = match invocation.skill_scope {
|
||||
SkillScope::User => "user",
|
||||
SkillScope::Repo => "repo",
|
||||
SkillScope::System => "system",
|
||||
SkillScope::Admin => "admin",
|
||||
};
|
||||
let repo_root = get_git_repo_root(invocation.skill_path.as_path());
|
||||
let repo_url = if let Some(root) = repo_root.as_ref() {
|
||||
collect_git_info(root)
|
||||
.await
|
||||
.and_then(|info| info.repository_url)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let skill_id = skill_id_for_local_skill(
|
||||
repo_url.as_deref(),
|
||||
repo_root.as_deref(),
|
||||
invocation.skill_path.as_path(),
|
||||
invocation.skill_name.as_str(),
|
||||
);
|
||||
out.push(TrackEventRequest::SkillInvocation(
|
||||
SkillInvocationEventRequest {
|
||||
event_type: "skill_invocation",
|
||||
skill_id,
|
||||
skill_name: invocation.skill_name.clone(),
|
||||
event_params: SkillInvocationEventParams {
|
||||
thread_id: Some(tracking.thread_id.clone()),
|
||||
invoke_type: Some(invocation.invocation_type),
|
||||
model_slug: Some(tracking.model_slug.clone()),
|
||||
product_client_id: Some(originator().value),
|
||||
repo_url,
|
||||
skill_scope: Some(skill_scope.to_string()),
|
||||
},
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn ingest_app_mentioned(&mut self, input: AppMentionedInput, out: &mut Vec<TrackEventRequest>) {
|
||||
let AppMentionedInput { tracking, mentions } = input;
|
||||
out.extend(mentions.into_iter().map(|mention| {
|
||||
let event_params = codex_app_metadata(&tracking, mention);
|
||||
TrackEventRequest::AppMentioned(CodexAppMentionedEventRequest {
|
||||
event_type: "codex_app_mentioned",
|
||||
event_params,
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
fn ingest_app_used(&mut self, input: AppUsedInput, out: &mut Vec<TrackEventRequest>) {
|
||||
let AppUsedInput { tracking, app } = input;
|
||||
let event_params = codex_app_metadata(&tracking, app);
|
||||
out.push(TrackEventRequest::AppUsed(CodexAppUsedEventRequest {
|
||||
event_type: "codex_app_used",
|
||||
event_params,
|
||||
}));
|
||||
}
|
||||
|
||||
fn ingest_plugin_used(&mut self, input: PluginUsedInput, out: &mut Vec<TrackEventRequest>) {
|
||||
let PluginUsedInput { tracking, plugin } = input;
|
||||
out.push(TrackEventRequest::PluginUsed(CodexPluginUsedEventRequest {
|
||||
event_type: "codex_plugin_used",
|
||||
event_params: codex_plugin_used_metadata(&tracking, plugin),
|
||||
}));
|
||||
}
|
||||
|
||||
fn ingest_plugin_state_changed(
|
||||
&mut self,
|
||||
input: PluginStateChangedInput,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let PluginStateChangedInput { plugin, state } = input;
|
||||
let event = CodexPluginEventRequest {
|
||||
event_type: plugin_state_event_type(state),
|
||||
event_params: codex_plugin_metadata(plugin),
|
||||
};
|
||||
out.push(match state {
|
||||
PluginState::Installed => TrackEventRequest::PluginInstalled(event),
|
||||
PluginState::Uninstalled => TrackEventRequest::PluginUninstalled(event),
|
||||
PluginState::Enabled => TrackEventRequest::PluginEnabled(event),
|
||||
PluginState::Disabled => TrackEventRequest::PluginDisabled(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_state_event_type(state: PluginState) -> &'static str {
|
||||
match state {
|
||||
PluginState::Installed => "codex_plugin_installed",
|
||||
PluginState::Uninstalled => "codex_plugin_uninstalled",
|
||||
PluginState::Enabled => "codex_plugin_enabled",
|
||||
PluginState::Disabled => "codex_plugin_disabled",
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> CodexAppMetadata {
|
||||
CodexAppMetadata {
|
||||
connector_id: app.connector_id,
|
||||
thread_id: Some(tracking.thread_id.clone()),
|
||||
turn_id: Some(tracking.turn_id.clone()),
|
||||
app_name: app.app_name,
|
||||
product_client_id: Some(originator().value),
|
||||
invoke_type: app.invocation_type,
|
||||
model_slug: Some(tracking.model_slug.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPluginMetadata {
|
||||
let capability_summary = plugin.capability_summary;
|
||||
CodexPluginMetadata {
|
||||
plugin_id: Some(plugin.plugin_id.as_key()),
|
||||
plugin_name: Some(plugin.plugin_id.plugin_name),
|
||||
marketplace_name: Some(plugin.plugin_id.marketplace_name),
|
||||
has_skills: capability_summary
|
||||
.as_ref()
|
||||
.map(|summary| summary.has_skills),
|
||||
mcp_server_count: capability_summary
|
||||
.as_ref()
|
||||
.map(|summary| summary.mcp_server_names.len()),
|
||||
connector_ids: capability_summary.map(|summary| {
|
||||
summary
|
||||
.app_connector_ids
|
||||
.into_iter()
|
||||
.map(|connector_id| connector_id.0)
|
||||
.collect()
|
||||
}),
|
||||
product_client_id: Some(originator().value),
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_plugin_used_metadata(
|
||||
tracking: &TrackEventsContext,
|
||||
plugin: PluginTelemetryMetadata,
|
||||
) -> CodexPluginUsedMetadata {
|
||||
CodexPluginUsedMetadata {
|
||||
plugin: codex_plugin_metadata(plugin),
|
||||
thread_id: Some(tracking.thread_id.clone()),
|
||||
turn_id: Some(tracking.turn_id.clone()),
|
||||
model_slug: Some(tracking.model_slug.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_track_events(
|
||||
auth_manager: &AuthManager,
|
||||
base_url: &str,
|
||||
events: Vec<TrackEventRequest>,
|
||||
) {
|
||||
if events.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(auth) = auth_manager.auth().await else {
|
||||
return;
|
||||
};
|
||||
if !auth.is_chatgpt_auth() {
|
||||
return;
|
||||
}
|
||||
let access_token = match auth.get_token() {
|
||||
Ok(token) => token,
|
||||
Err(_) => return,
|
||||
};
|
||||
let Some(account_id) = auth.get_account_id() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let base_url = base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/codex/analytics-events/events");
|
||||
let payload = TrackEventsRequest { events };
|
||||
|
||||
let response = create_client()
|
||||
.post(&url)
|
||||
.timeout(ANALYTICS_EVENTS_TIMEOUT)
|
||||
.bearer_auth(&access_token)
|
||||
.header("chatgpt-account-id", &account_id)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(response) if response.status().is_success() => {}
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
tracing::warn!("events failed with status {status}: {body}");
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to send events request: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn skill_id_for_local_skill(
|
||||
repo_url: Option<&str>,
|
||||
repo_root: Option<&Path>,
|
||||
skill_path: &Path,
|
||||
skill_name: &str,
|
||||
) -> String {
|
||||
let path = normalize_path_for_skill_id(repo_url, repo_root, skill_path);
|
||||
let prefix = if let Some(url) = repo_url {
|
||||
format!("repo_{url}")
|
||||
} else {
|
||||
"personal".to_string()
|
||||
};
|
||||
let raw_id = format!("{prefix}_{path}_{skill_name}");
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(raw_id.as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
/// Returns a normalized path for skill ID construction.
|
||||
///
|
||||
/// - Repo-scoped skills use a path relative to the repo root.
|
||||
/// - User/admin/system skills use an absolute path.
|
||||
fn normalize_path_for_skill_id(
|
||||
repo_url: Option<&str>,
|
||||
repo_root: Option<&Path>,
|
||||
skill_path: &Path,
|
||||
) -> String {
|
||||
let resolved_path =
|
||||
std::fs::canonicalize(skill_path).unwrap_or_else(|_| skill_path.to_path_buf());
|
||||
match (repo_url, repo_root) {
|
||||
(Some(_), Some(root)) => {
|
||||
let resolved_root = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
|
||||
resolved_path
|
||||
.strip_prefix(&resolved_root)
|
||||
.unwrap_or(resolved_path.as_path())
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/")
|
||||
}
|
||||
_ => resolved_path.to_string_lossy().replace('\\', "/"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "analytics_client_tests.rs"]
|
||||
mod tests;
|
||||
@@ -1,20 +1,31 @@
|
||||
use super::AnalyticsEventsQueue;
|
||||
use super::AnalyticsFact;
|
||||
use super::AnalyticsReducer;
|
||||
use super::AppInvocation;
|
||||
use super::AppMentionedInput;
|
||||
use super::AppUsedInput;
|
||||
use super::CodexAppMentionedEventRequest;
|
||||
use super::CodexAppUsedEventRequest;
|
||||
use super::CodexPluginEventRequest;
|
||||
use super::CodexPluginUsedEventRequest;
|
||||
use super::CustomAnalyticsFact;
|
||||
use super::InvocationType;
|
||||
use super::PluginState;
|
||||
use super::PluginStateChangedInput;
|
||||
use super::PluginUsedInput;
|
||||
use super::SkillInvocation;
|
||||
use super::SkillInvokedInput;
|
||||
use super::TrackEventRequest;
|
||||
use super::TrackEventsContext;
|
||||
use super::codex_app_metadata;
|
||||
use super::codex_plugin_metadata;
|
||||
use super::codex_plugin_used_metadata;
|
||||
use super::normalize_path_for_skill_id;
|
||||
use crate::plugins::AppConnectorId;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
use crate::plugins::PluginId;
|
||||
use crate::plugins::PluginTelemetryMetadata;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_plugin::AppConnectorId;
|
||||
use codex_plugin::PluginCapabilitySummary;
|
||||
use codex_plugin::PluginId;
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::collections::HashSet;
|
||||
@@ -48,7 +59,11 @@ fn normalize_path_for_skill_id_repo_scoped_uses_relative_path() {
|
||||
fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() {
|
||||
let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md");
|
||||
|
||||
let path = normalize_path_for_skill_id(None, None, skill_path.as_path());
|
||||
let path = normalize_path_for_skill_id(
|
||||
/*repo_url*/ None,
|
||||
/*repo_root*/ None,
|
||||
skill_path.as_path(),
|
||||
);
|
||||
let expected = expected_absolute_path(&skill_path);
|
||||
|
||||
assert_eq!(path, expected);
|
||||
@@ -58,7 +73,11 @@ fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() {
|
||||
fn normalize_path_for_skill_id_admin_scoped_uses_absolute_path() {
|
||||
let skill_path = PathBuf::from("/etc/codex/skills/doc/SKILL.md");
|
||||
|
||||
let path = normalize_path_for_skill_id(None, None, skill_path.as_path());
|
||||
let path = normalize_path_for_skill_id(
|
||||
/*repo_url*/ None,
|
||||
/*repo_root*/ None,
|
||||
skill_path.as_path(),
|
||||
);
|
||||
let expected = expected_absolute_path(&skill_path);
|
||||
|
||||
assert_eq!(path, expected);
|
||||
@@ -109,7 +128,7 @@ fn app_mentioned_event_serializes_expected_shape() {
|
||||
"thread_id": "thread-1",
|
||||
"turn_id": "turn-1",
|
||||
"app_name": "Calendar",
|
||||
"product_client_id": crate::default_client::originator().value,
|
||||
"product_client_id": originator().value,
|
||||
"invoke_type": "explicit",
|
||||
"model_slug": "gpt-5"
|
||||
}
|
||||
@@ -147,7 +166,7 @@ fn app_used_event_serializes_expected_shape() {
|
||||
"thread_id": "thread-2",
|
||||
"turn_id": "turn-2",
|
||||
"app_name": "Google Drive",
|
||||
"product_client_id": crate::default_client::originator().value,
|
||||
"product_client_id": originator().value,
|
||||
"invoke_type": "implicit",
|
||||
"model_slug": "gpt-5"
|
||||
}
|
||||
@@ -210,7 +229,7 @@ fn plugin_used_event_serializes_expected_shape() {
|
||||
"has_skills": true,
|
||||
"mcp_server_count": 2,
|
||||
"connector_ids": ["calendar", "drive"],
|
||||
"product_client_id": crate::default_client::originator().value,
|
||||
"product_client_id": originator().value,
|
||||
"thread_id": "thread-3",
|
||||
"turn_id": "turn-3",
|
||||
"model_slug": "gpt-5"
|
||||
@@ -239,7 +258,7 @@ fn plugin_management_event_serializes_expected_shape() {
|
||||
"has_skills": true,
|
||||
"mcp_server_count": 2,
|
||||
"connector_ids": ["calendar", "drive"],
|
||||
"product_client_id": crate::default_client::originator().value
|
||||
"product_client_id": originator().value
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -271,6 +290,145 @@ fn plugin_used_dedupe_is_keyed_by_turn_and_plugin() {
|
||||
assert_eq!(queue.should_enqueue_plugin_used(&turn_2, &plugin), true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_skill_invoked_fact() {
|
||||
let mut reducer = AnalyticsReducer;
|
||||
let mut events = Vec::new();
|
||||
let tracking = TrackEventsContext {
|
||||
model_slug: "gpt-5".to_string(),
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
};
|
||||
let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md");
|
||||
let expected_skill_id = super::skill_id_for_local_skill(
|
||||
/*repo_url*/ None,
|
||||
/*repo_root*/ None,
|
||||
skill_path.as_path(),
|
||||
"doc",
|
||||
);
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked(SkillInvokedInput {
|
||||
tracking,
|
||||
invocations: vec![SkillInvocation {
|
||||
skill_name: "doc".to_string(),
|
||||
skill_scope: codex_protocol::protocol::SkillScope::User,
|
||||
skill_path,
|
||||
invocation_type: InvocationType::Explicit,
|
||||
}],
|
||||
})),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "skill_invocation",
|
||||
"skill_id": expected_skill_id,
|
||||
"skill_name": "doc",
|
||||
"event_params": {
|
||||
"product_client_id": originator().value,
|
||||
"skill_scope": "user",
|
||||
"repo_url": null,
|
||||
"thread_id": "thread-1",
|
||||
"invoke_type": "explicit",
|
||||
"model_slug": "gpt-5"
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_app_and_plugin_facts() {
|
||||
let mut reducer = AnalyticsReducer;
|
||||
let mut events = Vec::new();
|
||||
let tracking = TrackEventsContext {
|
||||
model_slug: "gpt-5".to_string(),
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
};
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::AppMentioned(AppMentionedInput {
|
||||
tracking: tracking.clone(),
|
||||
mentions: vec![AppInvocation {
|
||||
connector_id: Some("calendar".to_string()),
|
||||
app_name: Some("Calendar".to_string()),
|
||||
invocation_type: Some(InvocationType::Explicit),
|
||||
}],
|
||||
})),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(AppUsedInput {
|
||||
tracking: tracking.clone(),
|
||||
app: AppInvocation {
|
||||
connector_id: Some("drive".to_string()),
|
||||
app_name: Some("Drive".to_string()),
|
||||
invocation_type: Some(InvocationType::Implicit),
|
||||
},
|
||||
})),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed(PluginUsedInput {
|
||||
tracking,
|
||||
plugin: sample_plugin_metadata(),
|
||||
})),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 3);
|
||||
assert_eq!(payload[0]["event_type"], "codex_app_mentioned");
|
||||
assert_eq!(payload[1]["event_type"], "codex_app_used");
|
||||
assert_eq!(payload[2]["event_type"], "codex_plugin_used");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_plugin_state_changed_fact() {
|
||||
let mut reducer = AnalyticsReducer;
|
||||
let mut events = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::PluginStateChanged(
|
||||
PluginStateChangedInput {
|
||||
plugin: sample_plugin_metadata(),
|
||||
state: PluginState::Disabled,
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "codex_plugin_disabled",
|
||||
"event_params": {
|
||||
"plugin_id": "sample@test",
|
||||
"plugin_name": "sample",
|
||||
"marketplace_name": "test",
|
||||
"has_skills": true,
|
||||
"mcp_server_count": 2,
|
||||
"connector_ids": ["calendar", "drive"],
|
||||
"product_client_id": originator().value
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
fn sample_plugin_metadata() -> PluginTelemetryMetadata {
|
||||
PluginTelemetryMetadata {
|
||||
plugin_id: PluginId::parse("sample@test").expect("valid plugin id"),
|
||||
7
codex-rs/analytics/src/lib.rs
Normal file
7
codex-rs/analytics/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
include!("native.rs");
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
17
codex-rs/analytics/src/native.rs
Normal file
17
codex-rs/analytics/src/native.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod analytics_client;
|
||||
|
||||
pub use analytics_client::AnalyticsEventsClient;
|
||||
pub use analytics_client::AnalyticsFact;
|
||||
pub use analytics_client::AnalyticsReducer;
|
||||
pub use analytics_client::AppInvocation;
|
||||
pub use analytics_client::AppMentionedInput;
|
||||
pub use analytics_client::AppUsedInput;
|
||||
pub use analytics_client::CustomAnalyticsFact;
|
||||
pub use analytics_client::InvocationType;
|
||||
pub use analytics_client::PluginState;
|
||||
pub use analytics_client::PluginStateChangedInput;
|
||||
pub use analytics_client::PluginUsedInput;
|
||||
pub use analytics_client::SkillInvocation;
|
||||
pub use analytics_client::SkillInvokedInput;
|
||||
pub use analytics_client::TrackEventsContext;
|
||||
pub use analytics_client::build_track_events_context;
|
||||
170
codex-rs/analytics/src/wasm.rs
Normal file
170
codex-rs/analytics/src/wasm.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackEventsContext {
|
||||
pub model_slug: String,
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
pub fn build_track_events_context(
|
||||
model_slug: String,
|
||||
thread_id: String,
|
||||
turn_id: String,
|
||||
) -> TrackEventsContext {
|
||||
TrackEventsContext {
|
||||
model_slug,
|
||||
thread_id,
|
||||
turn_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SkillInvocation {
|
||||
pub skill_name: String,
|
||||
pub skill_scope: SkillScope,
|
||||
pub skill_path: PathBuf,
|
||||
pub invocation_type: InvocationType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum InvocationType {
|
||||
Explicit,
|
||||
Implicit,
|
||||
}
|
||||
|
||||
pub struct AppInvocation {
|
||||
pub connector_id: Option<String>,
|
||||
pub app_name: Option<String>,
|
||||
pub invocation_type: Option<InvocationType>,
|
||||
}
|
||||
|
||||
pub enum AnalyticsFact {
|
||||
Initialize {
|
||||
connection_id: u64,
|
||||
params: codex_app_server_protocol::InitializeParams,
|
||||
},
|
||||
Request {
|
||||
connection_id: u64,
|
||||
request_id: codex_app_server_protocol::RequestId,
|
||||
request: Box<codex_app_server_protocol::ClientRequest>,
|
||||
},
|
||||
Response {
|
||||
connection_id: u64,
|
||||
response: Box<codex_app_server_protocol::ClientResponse>,
|
||||
},
|
||||
Notification(Box<codex_app_server_protocol::ServerNotification>),
|
||||
Custom(CustomAnalyticsFact),
|
||||
}
|
||||
|
||||
pub enum CustomAnalyticsFact {
|
||||
SkillInvoked(SkillInvokedInput),
|
||||
AppMentioned(AppMentionedInput),
|
||||
AppUsed(AppUsedInput),
|
||||
PluginUsed(PluginUsedInput),
|
||||
PluginStateChanged(PluginStateChangedInput),
|
||||
}
|
||||
|
||||
pub struct SkillInvokedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub invocations: Vec<SkillInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppMentionedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub mentions: Vec<AppInvocation>,
|
||||
}
|
||||
|
||||
pub struct AppUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub app: AppInvocation,
|
||||
}
|
||||
|
||||
pub struct PluginUsedInput {
|
||||
pub tracking: TrackEventsContext,
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
}
|
||||
|
||||
pub struct PluginStateChangedInput {
|
||||
pub plugin: PluginTelemetryMetadata,
|
||||
pub state: PluginState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PluginState {
|
||||
Installed,
|
||||
Uninstalled,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnalyticsReducer;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AnalyticsEventsClient {
|
||||
analytics_enabled: Option<bool>,
|
||||
_marker: Arc<()>,
|
||||
}
|
||||
|
||||
impl AnalyticsEventsClient {
|
||||
pub fn new<T>(
|
||||
_auth_manager: Arc<T>,
|
||||
_base_url: String,
|
||||
analytics_enabled: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
analytics_enabled,
|
||||
_marker: Arc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_skill_invocations(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_invocations: Vec<SkillInvocation>,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_app_mentioned(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_mentions: Vec<AppInvocation>,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_app_used(&self, _tracking: TrackEventsContext, _app: AppInvocation) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_used(
|
||||
&self,
|
||||
_tracking: TrackEventsContext,
|
||||
_plugin: PluginTelemetryMetadata,
|
||||
) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_installed(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_uninstalled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_enabled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
|
||||
pub fn track_plugin_disabled(&self, _plugin: PluginTelemetryMetadata) {
|
||||
let _ = self.analytics_enabled;
|
||||
}
|
||||
}
|
||||
@@ -35,18 +35,14 @@ use codex_app_server_protocol::ConfigWarningNotification;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::Result as JsonRpcResult;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -72,7 +68,6 @@ pub type RequestResult = std::result::Result<JsonRpcResult, JSONRPCErrorError>;
|
||||
pub enum AppServerEvent {
|
||||
Lagged { skipped: usize },
|
||||
ServerNotification(ServerNotification),
|
||||
LegacyNotification(JSONRPCNotification),
|
||||
ServerRequest(ServerRequest),
|
||||
Disconnected { message: String },
|
||||
}
|
||||
@@ -84,33 +79,134 @@ impl From<InProcessServerEvent> for AppServerEvent {
|
||||
InProcessServerEvent::ServerNotification(notification) => {
|
||||
Self::ServerNotification(notification)
|
||||
}
|
||||
InProcessServerEvent::LegacyNotification(notification) => {
|
||||
Self::LegacyNotification(notification)
|
||||
}
|
||||
InProcessServerEvent::ServerRequest(request) => Self::ServerRequest(request),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn event_requires_delivery(event: &InProcessServerEvent) -> bool {
|
||||
// These terminal events drive surface shutdown/completion state. Dropping
|
||||
// them under backpressure can leave exec/TUI waiting forever even though
|
||||
// the underlying turn has already ended.
|
||||
// These transcript and terminal events must remain lossless. Dropping
|
||||
// streamed assistant text or the authoritative completed item can leave
|
||||
// the TUI with permanently corrupted markdown, while dropping completion
|
||||
// notifications can leave surfaces waiting forever.
|
||||
match event {
|
||||
InProcessServerEvent::ServerNotification(
|
||||
codex_app_server_protocol::ServerNotification::TurnCompleted(_),
|
||||
) => true,
|
||||
InProcessServerEvent::LegacyNotification(notification) => matches!(
|
||||
notification
|
||||
.method
|
||||
.strip_prefix("codex/event/")
|
||||
.unwrap_or(¬ification.method),
|
||||
"task_complete" | "turn_aborted" | "shutdown_complete"
|
||||
),
|
||||
InProcessServerEvent::ServerNotification(notification) => {
|
||||
server_notification_requires_delivery(notification)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` for notifications that must survive backpressure.
|
||||
///
|
||||
/// Transcript events (`AgentMessageDelta`, `PlanDelta`, reasoning deltas) and
|
||||
/// the authoritative `ItemCompleted` / `TurnCompleted` form the lossless tier
|
||||
/// of the event stream. Dropping any of these corrupts the visible assistant
|
||||
/// output or leaves surfaces waiting for a completion signal that already
|
||||
/// fired. Everything else (`CommandExecutionOutputDelta`, progress, etc.) is
|
||||
/// best-effort and may be dropped with only cosmetic impact.
|
||||
///
|
||||
/// Both the in-process and remote transports delegate to this function so the
|
||||
/// classification stays in sync.
|
||||
pub(crate) fn server_notification_requires_delivery(notification: &ServerNotification) -> bool {
|
||||
matches!(
|
||||
notification,
|
||||
ServerNotification::TurnCompleted(_)
|
||||
| ServerNotification::ItemCompleted(_)
|
||||
| ServerNotification::AgentMessageDelta(_)
|
||||
| ServerNotification::PlanDelta(_)
|
||||
| ServerNotification::ReasoningSummaryTextDelta(_)
|
||||
| ServerNotification::ReasoningTextDelta(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// Outcome of attempting to forward a single event to the consumer channel.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ForwardEventResult {
|
||||
/// The event was delivered (or intentionally dropped); the stream is healthy.
|
||||
Continue,
|
||||
/// The consumer channel is closed; the caller should stop producing events.
|
||||
DisableStream,
|
||||
}
|
||||
|
||||
/// Forwards a single in-process event to the consumer, respecting the
|
||||
/// lossless/best-effort split.
|
||||
///
|
||||
/// Lossless events (transcript deltas, item/turn completions) block until the
|
||||
/// consumer drains capacity. Best-effort events use `try_send` and increment
|
||||
/// `skipped_events` on failure. When a lag marker needs to be flushed before a
|
||||
/// lossless event, the flush itself blocks so the marker is never lost.
|
||||
///
|
||||
/// If a dropped event is a `ServerRequest`, `reject_server_request` is called
|
||||
/// so the server does not wait for a response that will never come.
|
||||
async fn forward_in_process_event<F>(
|
||||
event_tx: &mpsc::Sender<InProcessServerEvent>,
|
||||
skipped_events: &mut usize,
|
||||
event: InProcessServerEvent,
|
||||
mut reject_server_request: F,
|
||||
) -> ForwardEventResult
|
||||
where
|
||||
F: FnMut(ServerRequest),
|
||||
{
|
||||
if *skipped_events > 0 {
|
||||
if event_requires_delivery(&event) {
|
||||
// Surface lag before the lossless event, but do not let the lag marker itself cause
|
||||
// us to drop the transcript/completion notification the caller is blocked on.
|
||||
if event_tx
|
||||
.send(InProcessServerEvent::Lagged {
|
||||
skipped: *skipped_events,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return ForwardEventResult::DisableStream;
|
||||
}
|
||||
*skipped_events = 0;
|
||||
} else {
|
||||
match event_tx.try_send(InProcessServerEvent::Lagged {
|
||||
skipped: *skipped_events,
|
||||
}) {
|
||||
Ok(()) => {
|
||||
*skipped_events = 0;
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
*skipped_events = skipped_events.saturating_add(1);
|
||||
warn!("dropping in-process app-server event because consumer queue is full");
|
||||
if let InProcessServerEvent::ServerRequest(request) = event {
|
||||
reject_server_request(request);
|
||||
}
|
||||
return ForwardEventResult::Continue;
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => {
|
||||
return ForwardEventResult::DisableStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if event_requires_delivery(&event) {
|
||||
// Block until the consumer catches up for transcript/completion notifications; this
|
||||
// preserves the visible assistant output even when the queue is otherwise saturated.
|
||||
if event_tx.send(event).await.is_err() {
|
||||
return ForwardEventResult::DisableStream;
|
||||
}
|
||||
return ForwardEventResult::Continue;
|
||||
}
|
||||
|
||||
match event_tx.try_send(event) {
|
||||
Ok(()) => ForwardEventResult::Continue,
|
||||
Err(mpsc::error::TrySendError::Full(event)) => {
|
||||
*skipped_events = skipped_events.saturating_add(1);
|
||||
warn!("dropping in-process app-server event because consumer queue is full");
|
||||
if let InProcessServerEvent::ServerRequest(request) = event {
|
||||
reject_server_request(request);
|
||||
}
|
||||
ForwardEventResult::Continue
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => ForwardEventResult::DisableStream,
|
||||
}
|
||||
}
|
||||
|
||||
/// Layered error for [`InProcessAppServerClient::request_typed`].
|
||||
///
|
||||
/// This keeps transport failures, server-side JSON-RPC failures, and response
|
||||
@@ -158,16 +254,6 @@ impl Error for TypedRequestError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SharedCoreManagers {
|
||||
// Temporary bootstrap escape hatch for embedders that still need direct
|
||||
// core handles during the in-process app-server migration. Once TUI/exec
|
||||
// stop depending on direct manager access, remove this wrapper and keep
|
||||
// manager ownership entirely inside the app-server runtime.
|
||||
auth_manager: Arc<AuthManager>,
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InProcessClientStartArgs {
|
||||
/// Resolved argv0 dispatch paths used by command execution internals.
|
||||
@@ -201,30 +287,6 @@ pub struct InProcessClientStartArgs {
|
||||
}
|
||||
|
||||
impl InProcessClientStartArgs {
|
||||
fn shared_core_managers(&self) -> SharedCoreManagers {
|
||||
let auth_manager = AuthManager::shared(
|
||||
self.config.codex_home.clone(),
|
||||
self.enable_codex_api_key_env,
|
||||
self.config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let thread_manager = Arc::new(ThreadManager::new(
|
||||
self.config.as_ref(),
|
||||
auth_manager.clone(),
|
||||
self.session_source.clone(),
|
||||
CollaborationModesConfig {
|
||||
default_mode_request_user_input: self
|
||||
.config
|
||||
.features
|
||||
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
|
||||
},
|
||||
));
|
||||
|
||||
SharedCoreManagers {
|
||||
auth_manager,
|
||||
thread_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds initialize params from caller-provided metadata.
|
||||
pub fn initialize_params(&self) -> InitializeParams {
|
||||
let capabilities = InitializeCapabilities {
|
||||
@@ -246,7 +308,7 @@ impl InProcessClientStartArgs {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_runtime_start_args(self, shared_core: &SharedCoreManagers) -> InProcessStartArgs {
|
||||
fn into_runtime_start_args(self) -> InProcessStartArgs {
|
||||
let initialize = self.initialize_params();
|
||||
InProcessStartArgs {
|
||||
arg0_paths: self.arg0_paths,
|
||||
@@ -254,8 +316,6 @@ impl InProcessClientStartArgs {
|
||||
cli_overrides: self.cli_overrides,
|
||||
loader_overrides: self.loader_overrides,
|
||||
cloud_requirements: self.cloud_requirements,
|
||||
auth_manager: Some(shared_core.auth_manager.clone()),
|
||||
thread_manager: Some(shared_core.thread_manager.clone()),
|
||||
feedback: self.feedback,
|
||||
config_warnings: self.config_warnings,
|
||||
session_source: self.session_source,
|
||||
@@ -309,8 +369,6 @@ pub struct InProcessAppServerClient {
|
||||
command_tx: mpsc::Sender<ClientCommand>,
|
||||
event_rx: mpsc::Receiver<InProcessServerEvent>,
|
||||
worker_handle: tokio::task::JoinHandle<()>,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -337,9 +395,8 @@ impl InProcessAppServerClient {
|
||||
/// with overload error instead of being silently dropped.
|
||||
pub async fn start(args: InProcessClientStartArgs) -> IoResult<Self> {
|
||||
let channel_capacity = args.channel_capacity.max(1);
|
||||
let shared_core = args.shared_core_managers();
|
||||
let mut handle =
|
||||
codex_app_server::in_process::start(args.into_runtime_start_args(&shared_core)).await?;
|
||||
codex_app_server::in_process::start(args.into_runtime_start_args()).await?;
|
||||
let request_sender = handle.sender();
|
||||
let (command_tx, mut command_rx) = mpsc::channel::<ClientCommand>(channel_capacity);
|
||||
let (event_tx, event_rx) = mpsc::channel::<InProcessServerEvent>(channel_capacity);
|
||||
@@ -400,84 +457,46 @@ impl InProcessAppServerClient {
|
||||
let Some(event) = event else {
|
||||
break;
|
||||
};
|
||||
|
||||
if skipped_events > 0 {
|
||||
if event_requires_delivery(&event) {
|
||||
// Surface lag before the terminal event, but
|
||||
// do not let the lag marker itself cause us to
|
||||
// drop the completion/abort notification that
|
||||
// the caller is blocked on.
|
||||
if event_tx
|
||||
.send(InProcessServerEvent::Lagged {
|
||||
skipped: skipped_events,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
event_stream_enabled = false;
|
||||
continue;
|
||||
}
|
||||
skipped_events = 0;
|
||||
} else {
|
||||
match event_tx.try_send(InProcessServerEvent::Lagged {
|
||||
skipped: skipped_events,
|
||||
}) {
|
||||
Ok(()) => {
|
||||
skipped_events = 0;
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
skipped_events = skipped_events.saturating_add(1);
|
||||
warn!(
|
||||
"dropping in-process app-server event because consumer queue is full"
|
||||
);
|
||||
if let InProcessServerEvent::ServerRequest(request) = event {
|
||||
let _ = request_sender.fail_server_request(
|
||||
request.id().clone(),
|
||||
JSONRPCErrorError {
|
||||
code: -32001,
|
||||
message: "in-process app-server event queue is full".to_string(),
|
||||
data: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => {
|
||||
event_stream_enabled = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if event_requires_delivery(&event) {
|
||||
// Block until the consumer catches up for
|
||||
// terminal notifications; this preserves the
|
||||
// completion signal even when the queue is
|
||||
// otherwise saturated.
|
||||
if event_tx.send(event).await.is_err() {
|
||||
event_stream_enabled = false;
|
||||
if let InProcessServerEvent::ServerRequest(
|
||||
ServerRequest::ChatgptAuthTokensRefresh { request_id, .. }
|
||||
) = &event
|
||||
{
|
||||
let send_result = request_sender.fail_server_request(
|
||||
request_id.clone(),
|
||||
JSONRPCErrorError {
|
||||
code: -32000,
|
||||
message: "chatgpt auth token refresh is not supported for in-process app-server clients".to_string(),
|
||||
data: None,
|
||||
},
|
||||
);
|
||||
if let Err(err) = send_result {
|
||||
warn!(
|
||||
"failed to reject unsupported chatgpt auth token refresh request: {err}"
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
match event_tx.try_send(event) {
|
||||
Ok(()) => {}
|
||||
Err(mpsc::error::TrySendError::Full(event)) => {
|
||||
skipped_events = skipped_events.saturating_add(1);
|
||||
warn!("dropping in-process app-server event because consumer queue is full");
|
||||
if let InProcessServerEvent::ServerRequest(request) = event {
|
||||
let _ = request_sender.fail_server_request(
|
||||
request.id().clone(),
|
||||
JSONRPCErrorError {
|
||||
code: -32001,
|
||||
message: "in-process app-server event queue is full".to_string(),
|
||||
data: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => {
|
||||
match forward_in_process_event(
|
||||
&event_tx,
|
||||
&mut skipped_events,
|
||||
event,
|
||||
|request| {
|
||||
let _ = request_sender.fail_server_request(
|
||||
request.id().clone(),
|
||||
JSONRPCErrorError {
|
||||
code: -32001,
|
||||
message: "in-process app-server event queue is full"
|
||||
.to_string(),
|
||||
data: None,
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
ForwardEventResult::Continue => {}
|
||||
ForwardEventResult::DisableStream => {
|
||||
event_stream_enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -490,21 +509,9 @@ impl InProcessAppServerClient {
|
||||
command_tx,
|
||||
event_rx,
|
||||
worker_handle,
|
||||
auth_manager: shared_core.auth_manager,
|
||||
thread_manager: shared_core.thread_manager,
|
||||
})
|
||||
}
|
||||
|
||||
/// Temporary bootstrap escape hatch for embedders migrating toward RPC-only usage.
|
||||
pub fn auth_manager(&self) -> Arc<AuthManager> {
|
||||
self.auth_manager.clone()
|
||||
}
|
||||
|
||||
/// Temporary bootstrap escape hatch for embedders migrating toward RPC-only usage.
|
||||
pub fn thread_manager(&self) -> Arc<ThreadManager> {
|
||||
self.thread_manager.clone()
|
||||
}
|
||||
|
||||
pub fn request_handle(&self) -> InProcessAppServerRequestHandle {
|
||||
InProcessAppServerRequestHandle {
|
||||
command_tx: self.command_tx.clone(),
|
||||
@@ -663,8 +670,6 @@ impl InProcessAppServerClient {
|
||||
command_tx,
|
||||
event_rx,
|
||||
worker_handle,
|
||||
auth_manager: _,
|
||||
thread_manager: _,
|
||||
} = self;
|
||||
let mut worker_handle = worker_handle;
|
||||
// Drop the caller-facing receiver before asking the worker to shut
|
||||
@@ -856,8 +861,6 @@ mod tests {
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ToolRequestUserInputParams;
|
||||
use codex_app_server_protocol::ToolRequestUserInputQuestion;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
@@ -865,8 +868,11 @@ mod tests {
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use tokio_tungstenite::accept_hdr_async;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tokio_tungstenite::tungstenite::handshake::server::Request as WebSocketRequest;
|
||||
use tokio_tungstenite::tungstenite::handshake::server::Response as WebSocketResponse;
|
||||
use tokio_tungstenite::tungstenite::http::header::AUTHORIZATION;
|
||||
|
||||
async fn build_test_config() -> Config {
|
||||
match ConfigBuilder::default().build().await {
|
||||
@@ -905,6 +911,19 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn start_test_remote_server<F, Fut>(handler: F) -> String
|
||||
where
|
||||
F: FnOnce(tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>) -> Fut
|
||||
+ Send
|
||||
+ 'static,
|
||||
Fut: std::future::Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
start_test_remote_server_with_auth(/*expected_auth_token*/ None, handler).await
|
||||
}
|
||||
|
||||
async fn start_test_remote_server_with_auth<F, Fut>(
|
||||
expected_auth_token: Option<String>,
|
||||
handler: F,
|
||||
) -> String
|
||||
where
|
||||
F: FnOnce(tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>) -> Fut
|
||||
+ Send
|
||||
@@ -917,9 +936,23 @@ mod tests {
|
||||
let addr = listener.local_addr().expect("listener address");
|
||||
tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.expect("accept should succeed");
|
||||
let websocket = accept_async(stream)
|
||||
.await
|
||||
.expect("websocket upgrade should succeed");
|
||||
let websocket = accept_hdr_async(
|
||||
stream,
|
||||
move |request: &WebSocketRequest, response: WebSocketResponse| {
|
||||
let provided_auth_token = request
|
||||
.headers()
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::to_owned);
|
||||
let expected_auth_token = expected_auth_token
|
||||
.as_ref()
|
||||
.map(|token| format!("Bearer {token}"));
|
||||
assert_eq!(provided_auth_token, expected_auth_token);
|
||||
Ok(response)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("websocket upgrade should succeed");
|
||||
handler(websocket).await;
|
||||
});
|
||||
format!("ws://{addr}")
|
||||
@@ -984,9 +1017,57 @@ mod tests {
|
||||
.expect("message should send");
|
||||
}
|
||||
|
||||
fn command_execution_output_delta_notification(delta: &str) -> ServerNotification {
|
||||
ServerNotification::CommandExecutionOutputDelta(
|
||||
codex_app_server_protocol::CommandExecutionOutputDeltaNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item_id: "item".to_string(),
|
||||
delta: delta.to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_message_delta_notification(delta: &str) -> ServerNotification {
|
||||
ServerNotification::AgentMessageDelta(
|
||||
codex_app_server_protocol::AgentMessageDeltaNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item_id: "item".to_string(),
|
||||
delta: delta.to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn item_completed_notification(text: &str) -> ServerNotification {
|
||||
ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: text.to_string(),
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn turn_completed_notification() -> ServerNotification {
|
||||
ServerNotification::TurnCompleted(codex_app_server_protocol::TurnCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn: codex_app_server_protocol::Turn {
|
||||
id: "turn".to_string(),
|
||||
items: Vec::new(),
|
||||
status: codex_app_server_protocol::TurnStatus::Completed,
|
||||
error: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn test_remote_connect_args(websocket_url: String) -> RemoteAppServerConnectArgs {
|
||||
RemoteAppServerConnectArgs {
|
||||
websocket_url,
|
||||
auth_token: None,
|
||||
client_name: "codex-app-server-client-test".to_string(),
|
||||
client_version: "0.0.0-test".to_string(),
|
||||
experimental_api: true,
|
||||
@@ -1051,7 +1132,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_thread_manager_tracks_threads_started_via_app_server() {
|
||||
async fn threads_started_via_app_server_are_visible_through_typed_requests() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
|
||||
let response: ThreadStartResponse = client
|
||||
@@ -1064,24 +1145,27 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.expect("thread/start should succeed");
|
||||
let created_thread_id = codex_protocol::ThreadId::from_string(&response.thread.id)
|
||||
.expect("thread id should parse");
|
||||
timeout(
|
||||
Duration::from_secs(2),
|
||||
client.thread_manager().get_thread(created_thread_id),
|
||||
)
|
||||
.await
|
||||
.expect("timed out waiting for retained thread manager to observe started thread")
|
||||
.expect("started thread should be visible through the shared thread manager");
|
||||
let thread_ids = client.thread_manager().list_thread_ids().await;
|
||||
assert!(thread_ids.contains(&created_thread_id));
|
||||
let read = client
|
||||
.request_typed::<codex_app_server_protocol::ThreadReadResponse>(
|
||||
ClientRequest::ThreadRead {
|
||||
request_id: RequestId::Integer(4),
|
||||
params: codex_app_server_protocol::ThreadReadParams {
|
||||
thread_id: response.thread.id.clone(),
|
||||
include_turns: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("thread/read should return the newly started thread");
|
||||
assert_eq!(read.thread.id, response.thread.id);
|
||||
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tiny_channel_capacity_still_supports_request_roundtrip() {
|
||||
let client = start_test_client_with_capacity(SessionSource::Exec, 1).await;
|
||||
let client =
|
||||
start_test_client_with_capacity(SessionSource::Exec, /*channel_capacity*/ 1).await;
|
||||
let _response: ConfigRequirementsReadResponse = client
|
||||
.request_typed(ClientRequest::ConfigRequirementsRead {
|
||||
request_id: RequestId::Integer(1),
|
||||
@@ -1092,6 +1176,94 @@ mod tests {
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn forward_in_process_event_preserves_transcript_notifications_under_backpressure() {
|
||||
let (event_tx, mut event_rx) = mpsc::channel(1);
|
||||
event_tx
|
||||
.send(InProcessServerEvent::ServerNotification(
|
||||
command_execution_output_delta_notification("stdout-1"),
|
||||
))
|
||||
.await
|
||||
.expect("initial event should enqueue");
|
||||
|
||||
let mut skipped_events = 0usize;
|
||||
let result = forward_in_process_event(
|
||||
&event_tx,
|
||||
&mut skipped_events,
|
||||
InProcessServerEvent::ServerNotification(command_execution_output_delta_notification(
|
||||
"stdout-2",
|
||||
)),
|
||||
|_| {},
|
||||
)
|
||||
.await;
|
||||
assert_eq!(result, ForwardEventResult::Continue);
|
||||
assert_eq!(skipped_events, 1);
|
||||
|
||||
let receive_task = tokio::spawn(async move {
|
||||
let mut events = Vec::new();
|
||||
for _ in 0..5 {
|
||||
events.push(
|
||||
timeout(Duration::from_secs(2), event_rx.recv())
|
||||
.await
|
||||
.expect("event should arrive before timeout")
|
||||
.expect("event stream should stay open"),
|
||||
);
|
||||
}
|
||||
events
|
||||
});
|
||||
|
||||
for notification in [
|
||||
agent_message_delta_notification("hello"),
|
||||
item_completed_notification("hello"),
|
||||
turn_completed_notification(),
|
||||
] {
|
||||
let result = forward_in_process_event(
|
||||
&event_tx,
|
||||
&mut skipped_events,
|
||||
InProcessServerEvent::ServerNotification(notification),
|
||||
|_| {},
|
||||
)
|
||||
.await;
|
||||
assert_eq!(result, ForwardEventResult::Continue);
|
||||
}
|
||||
assert_eq!(skipped_events, 0);
|
||||
|
||||
let events = receive_task
|
||||
.await
|
||||
.expect("receiver task should join successfully");
|
||||
assert!(matches!(
|
||||
&events[0],
|
||||
InProcessServerEvent::ServerNotification(
|
||||
ServerNotification::CommandExecutionOutputDelta(notification)
|
||||
) if notification.delta == "stdout-1"
|
||||
));
|
||||
assert!(matches!(
|
||||
&events[1],
|
||||
InProcessServerEvent::Lagged { skipped: 1 }
|
||||
));
|
||||
assert!(matches!(
|
||||
&events[2],
|
||||
InProcessServerEvent::ServerNotification(ServerNotification::AgentMessageDelta(
|
||||
notification
|
||||
)) if notification.delta == "hello"
|
||||
));
|
||||
assert!(matches!(
|
||||
&events[3],
|
||||
InProcessServerEvent::ServerNotification(ServerNotification::ItemCompleted(
|
||||
notification
|
||||
)) if matches!(
|
||||
¬ification.item,
|
||||
codex_app_server_protocol::ThreadItem::AgentMessage { text, .. } if text == "hello"
|
||||
)
|
||||
));
|
||||
assert!(matches!(
|
||||
&events[4],
|
||||
InProcessServerEvent::ServerNotification(ServerNotification::TurnCompleted(
|
||||
notification
|
||||
)) if notification.turn.status == codex_app_server_protocol::TurnStatus::Completed
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_typed_request_roundtrip_works() {
|
||||
let websocket_url = start_test_remote_server(|mut websocket| async move {
|
||||
@@ -1113,6 +1285,7 @@ mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
websocket.close(None).await.expect("close should succeed");
|
||||
})
|
||||
.await;
|
||||
let client = RemoteAppServerClient::connect(test_remote_connect_args(websocket_url))
|
||||
@@ -1133,6 +1306,59 @@ mod tests {
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_connect_includes_auth_header_when_configured() {
|
||||
let auth_token = "remote-bearer-token".to_string();
|
||||
let websocket_url = start_test_remote_server_with_auth(
|
||||
Some(auth_token.clone()),
|
||||
|mut websocket| async move {
|
||||
expect_remote_initialize(&mut websocket).await;
|
||||
websocket.close(None).await.expect("close should succeed");
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let client = RemoteAppServerClient::connect(RemoteAppServerConnectArgs {
|
||||
auth_token: Some(auth_token),
|
||||
..test_remote_connect_args(websocket_url)
|
||||
})
|
||||
.await
|
||||
.expect("remote client should connect");
|
||||
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_connect_rejects_non_loopback_ws_when_auth_configured() {
|
||||
let result = RemoteAppServerClient::connect(RemoteAppServerConnectArgs {
|
||||
websocket_url: "ws://example.com:4500".to_string(),
|
||||
auth_token: Some("remote-bearer-token".to_string()),
|
||||
..test_remote_connect_args("ws://127.0.0.1:1".to_string())
|
||||
})
|
||||
.await;
|
||||
let err = match result {
|
||||
Ok(_) => panic!("non-loopback ws should be rejected before connect"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert_eq!(err.kind(), ErrorKind::InvalidInput);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("remote auth tokens require `wss://` or loopback `ws://` URLs")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_auth_token_transport_policy_allows_wss_and_loopback_ws() {
|
||||
assert!(crate::remote::websocket_url_supports_auth_token(
|
||||
&url::Url::parse("wss://example.com:443").expect("wss URL should parse")
|
||||
));
|
||||
assert!(crate::remote::websocket_url_supports_auth_token(
|
||||
&url::Url::parse("ws://127.0.0.1:4500").expect("loopback ws URL should parse")
|
||||
));
|
||||
assert!(!crate::remote::websocket_url_supports_auth_token(
|
||||
&url::Url::parse("ws://example.com:4500").expect("non-loopback ws URL should parse")
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_duplicate_request_id_keeps_original_waiter() {
|
||||
let (first_request_seen_tx, first_request_seen_rx) = tokio::sync::oneshot::channel();
|
||||
@@ -1256,6 +1482,108 @@ mod tests {
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_backpressure_preserves_transcript_notifications() {
|
||||
let (done_tx, done_rx) = tokio::sync::oneshot::channel();
|
||||
let websocket_url = start_test_remote_server(|mut websocket| async move {
|
||||
expect_remote_initialize(&mut websocket).await;
|
||||
for notification in [
|
||||
command_execution_output_delta_notification("stdout-1"),
|
||||
command_execution_output_delta_notification("stdout-2"),
|
||||
agent_message_delta_notification("hello"),
|
||||
item_completed_notification("hello"),
|
||||
turn_completed_notification(),
|
||||
] {
|
||||
write_websocket_message(
|
||||
&mut websocket,
|
||||
JSONRPCMessage::Notification(
|
||||
serde_json::from_value(
|
||||
serde_json::to_value(notification)
|
||||
.expect("notification should serialize"),
|
||||
)
|
||||
.expect("notification should convert to JSON-RPC"),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let _ = done_rx.await;
|
||||
})
|
||||
.await;
|
||||
let mut client = RemoteAppServerClient::connect(RemoteAppServerConnectArgs {
|
||||
websocket_url,
|
||||
auth_token: None,
|
||||
client_name: "codex-app-server-client-test".to_string(),
|
||||
client_version: "0.0.0-test".to_string(),
|
||||
experimental_api: true,
|
||||
opt_out_notification_methods: Vec::new(),
|
||||
channel_capacity: 1,
|
||||
})
|
||||
.await
|
||||
.expect("remote client should connect");
|
||||
|
||||
let first_event = timeout(Duration::from_secs(2), client.next_event())
|
||||
.await
|
||||
.expect("first event should arrive before timeout")
|
||||
.expect("event stream should stay open");
|
||||
assert!(matches!(
|
||||
first_event,
|
||||
AppServerEvent::ServerNotification(ServerNotification::CommandExecutionOutputDelta(
|
||||
notification
|
||||
)) if notification.delta == "stdout-1"
|
||||
));
|
||||
|
||||
let mut remaining_events = Vec::new();
|
||||
for _ in 0..4 {
|
||||
remaining_events.push(
|
||||
timeout(Duration::from_secs(2), client.next_event())
|
||||
.await
|
||||
.expect("event should arrive before timeout")
|
||||
.expect("event stream should stay open"),
|
||||
);
|
||||
}
|
||||
|
||||
let mut transcript_event_names = Vec::new();
|
||||
for event in &remaining_events {
|
||||
match event {
|
||||
AppServerEvent::Lagged { skipped: 1 } => {}
|
||||
AppServerEvent::ServerNotification(
|
||||
ServerNotification::CommandExecutionOutputDelta(notification),
|
||||
) if notification.delta == "stdout-2" => {}
|
||||
AppServerEvent::ServerNotification(ServerNotification::AgentMessageDelta(
|
||||
notification,
|
||||
)) if notification.delta == "hello" => {
|
||||
transcript_event_names.push("agent_message_delta");
|
||||
}
|
||||
AppServerEvent::ServerNotification(ServerNotification::ItemCompleted(
|
||||
notification,
|
||||
)) if matches!(
|
||||
¬ification.item,
|
||||
codex_app_server_protocol::ThreadItem::AgentMessage { text, .. } if text == "hello"
|
||||
) =>
|
||||
{
|
||||
transcript_event_names.push("item_completed");
|
||||
}
|
||||
AppServerEvent::ServerNotification(ServerNotification::TurnCompleted(
|
||||
notification,
|
||||
)) if notification.turn.status
|
||||
== codex_app_server_protocol::TurnStatus::Completed =>
|
||||
{
|
||||
transcript_event_names.push("turn_completed");
|
||||
}
|
||||
_ => panic!("unexpected remaining event: {event:?}"),
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
transcript_event_names,
|
||||
vec!["agent_message_delta", "item_completed", "turn_completed"]
|
||||
);
|
||||
|
||||
done_tx
|
||||
.send(())
|
||||
.expect("server completion signal should send");
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_server_request_resolution_roundtrip_works() {
|
||||
let websocket_url = start_test_remote_server(|mut websocket| async move {
|
||||
@@ -1471,22 +1799,6 @@ mod tests {
|
||||
let (command_tx, _command_rx) = mpsc::channel(1);
|
||||
let (event_tx, event_rx) = mpsc::channel(1);
|
||||
let worker_handle = tokio::spawn(async {});
|
||||
let config = build_test_config().await;
|
||||
let auth_manager = AuthManager::shared(
|
||||
config.codex_home.clone(),
|
||||
false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let thread_manager = Arc::new(ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
SessionSource::Exec,
|
||||
CollaborationModesConfig {
|
||||
default_mode_request_user_input: config
|
||||
.features
|
||||
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
|
||||
},
|
||||
));
|
||||
event_tx
|
||||
.send(InProcessServerEvent::Lagged { skipped: 3 })
|
||||
.await
|
||||
@@ -1497,8 +1809,6 @@ mod tests {
|
||||
command_tx,
|
||||
event_rx,
|
||||
worker_handle,
|
||||
auth_manager,
|
||||
thread_manager,
|
||||
};
|
||||
|
||||
let event = timeout(Duration::from_secs(2), client.next_event())
|
||||
@@ -1513,7 +1823,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_requires_delivery_marks_terminal_events() {
|
||||
fn event_requires_delivery_marks_transcript_and_terminal_events() {
|
||||
assert!(event_requires_delivery(
|
||||
&InProcessServerEvent::ServerNotification(
|
||||
codex_app_server_protocol::ServerNotification::TurnCompleted(
|
||||
@@ -1530,36 +1840,77 @@ mod tests {
|
||||
)
|
||||
));
|
||||
assert!(event_requires_delivery(
|
||||
&InProcessServerEvent::LegacyNotification(
|
||||
codex_app_server_protocol::JSONRPCNotification {
|
||||
method: "codex/event/turn_aborted".to_string(),
|
||||
params: None,
|
||||
}
|
||||
&InProcessServerEvent::ServerNotification(
|
||||
codex_app_server_protocol::ServerNotification::AgentMessageDelta(
|
||||
codex_app_server_protocol::AgentMessageDeltaNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item_id: "item".to_string(),
|
||||
delta: "hello".to_string(),
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
assert!(event_requires_delivery(
|
||||
&InProcessServerEvent::ServerNotification(
|
||||
codex_app_server_protocol::ServerNotification::ItemCompleted(
|
||||
codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item: codex_app_server_protocol::ThreadItem::AgentMessage {
|
||||
id: "item".to_string(),
|
||||
text: "hello".to_string(),
|
||||
phase: None,
|
||||
memory_citation: None,
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
assert!(!event_requires_delivery(&InProcessServerEvent::Lagged {
|
||||
skipped: 1
|
||||
}));
|
||||
assert!(!event_requires_delivery(
|
||||
&InProcessServerEvent::ServerNotification(
|
||||
codex_app_server_protocol::ServerNotification::CommandExecutionOutputDelta(
|
||||
codex_app_server_protocol::CommandExecutionOutputDeltaNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item_id: "item".to_string(),
|
||||
delta: "stdout".to_string(),
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn accessors_expose_retained_shared_managers() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
async fn runtime_start_args_leave_manager_bootstrap_to_app_server() {
|
||||
let config = Arc::new(build_test_config().await);
|
||||
|
||||
assert!(
|
||||
Arc::ptr_eq(&client.auth_manager(), &client.auth_manager()),
|
||||
"auth_manager accessor should clone the retained shared manager"
|
||||
);
|
||||
assert!(
|
||||
Arc::ptr_eq(&client.thread_manager(), &client.thread_manager()),
|
||||
"thread_manager accessor should clone the retained shared manager"
|
||||
);
|
||||
let runtime_args = InProcessClientStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
config: config.clone(),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::Exec,
|
||||
enable_codex_api_key_env: false,
|
||||
client_name: "codex-app-server-client-test".to_string(),
|
||||
client_version: "0.0.0-test".to_string(),
|
||||
experimental_api: true,
|
||||
opt_out_notification_methods: Vec::new(),
|
||||
channel_capacity: DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
|
||||
}
|
||||
.into_runtime_start_args();
|
||||
|
||||
client.shutdown().await.expect("shutdown should complete");
|
||||
assert_eq!(runtime_args.config, config);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shutdown_completes_promptly_with_retained_shared_managers() {
|
||||
async fn shutdown_completes_promptly_without_retained_managers() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
|
||||
timeout(Duration::from_secs(1), client.shutdown())
|
||||
|
||||
@@ -4,9 +4,8 @@ This module implements the websocket-backed app-server client transport.
|
||||
It owns the remote connection lifecycle, including the initialize/initialized
|
||||
handshake, JSON-RPC request/response routing, server-request resolution, and
|
||||
notification streaming. The rest of the crate uses the same `AppServerEvent`
|
||||
surface for both in-process and remote transports, so callers such as
|
||||
`tui_app_server` can switch between them without changing their higher-level
|
||||
session logic.
|
||||
surface for both in-process and remote transports, so callers such as the TUI
|
||||
can switch between them without changing their higher-level session logic.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -21,6 +20,7 @@ use crate::RequestResult;
|
||||
use crate::SHUTDOWN_TIMEOUT;
|
||||
use crate::TypedRequestError;
|
||||
use crate::request_method_name;
|
||||
use crate::server_notification_requires_delivery;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientNotification;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
@@ -47,6 +47,9 @@ use tokio_tungstenite::MaybeTlsStream;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||
use tokio_tungstenite::tungstenite::http::HeaderValue;
|
||||
use tokio_tungstenite::tungstenite::http::header::AUTHORIZATION;
|
||||
use tracing::warn;
|
||||
use url::Url;
|
||||
|
||||
@@ -56,6 +59,7 @@ const INITIALIZE_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteAppServerConnectArgs {
|
||||
pub websocket_url: String,
|
||||
pub auth_token: Option<String>,
|
||||
pub client_name: String,
|
||||
pub client_version: String,
|
||||
pub experimental_api: bool,
|
||||
@@ -85,6 +89,16 @@ impl RemoteAppServerConnectArgs {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn websocket_url_supports_auth_token(url: &Url) -> bool {
|
||||
match (url.scheme(), url.host()) {
|
||||
("wss", Some(_)) => true,
|
||||
("ws", Some(url::Host::Domain(domain))) => domain.eq_ignore_ascii_case("localhost"),
|
||||
("ws", Some(url::Host::Ipv4(addr))) => addr.is_loopback(),
|
||||
("ws", Some(url::Host::Ipv6(addr))) => addr.is_loopback(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
enum RemoteClientCommand {
|
||||
Request {
|
||||
request: Box<ClientRequest>,
|
||||
@@ -131,7 +145,31 @@ impl RemoteAppServerClient {
|
||||
format!("invalid websocket URL `{websocket_url}`: {err}"),
|
||||
)
|
||||
})?;
|
||||
let stream = timeout(CONNECT_TIMEOUT, connect_async(url.as_str()))
|
||||
if args.auth_token.is_some() && !websocket_url_supports_auth_token(&url) {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"remote auth tokens require `wss://` or loopback `ws://` URLs; got `{websocket_url}`"
|
||||
),
|
||||
));
|
||||
}
|
||||
let mut request = url.as_str().into_client_request().map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("invalid websocket URL `{websocket_url}`: {err}"),
|
||||
)
|
||||
})?;
|
||||
if let Some(auth_token) = args.auth_token.as_deref() {
|
||||
let header_value =
|
||||
HeaderValue::from_str(&format!("Bearer {auth_token}")).map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("invalid remote authorization header value: {err}"),
|
||||
)
|
||||
})?;
|
||||
request.headers_mut().insert(AUTHORIZATION, header_value);
|
||||
}
|
||||
let stream = timeout(CONNECT_TIMEOUT, connect_async(request))
|
||||
.await
|
||||
.map_err(|_| {
|
||||
IoError::new(
|
||||
@@ -272,18 +310,19 @@ impl RemoteAppServerClient {
|
||||
}
|
||||
}
|
||||
Ok(JSONRPCMessage::Notification(notification)) => {
|
||||
let event = app_server_event_from_notification(notification);
|
||||
if let Err(err) = deliver_event(
|
||||
&event_tx,
|
||||
&mut skipped_events,
|
||||
event,
|
||||
&mut stream,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(%err, "failed to deliver remote app-server event");
|
||||
break;
|
||||
}
|
||||
if let Some(event) =
|
||||
app_server_event_from_notification(notification)
|
||||
&& let Err(err) = deliver_event(
|
||||
&event_tx,
|
||||
&mut skipped_events,
|
||||
event,
|
||||
&mut stream,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(%err, "failed to deliver remote app-server event");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(JSONRPCMessage::Request(request)) => {
|
||||
let request_id = request.id.clone();
|
||||
@@ -673,7 +712,9 @@ async fn initialize_remote_connection(
|
||||
)));
|
||||
}
|
||||
JSONRPCMessage::Notification(notification) => {
|
||||
pending_events.push(app_server_event_from_notification(notification));
|
||||
if let Some(event) = app_server_event_from_notification(notification) {
|
||||
pending_events.push(event);
|
||||
}
|
||||
}
|
||||
JSONRPCMessage::Request(request) => {
|
||||
let request_id = request.id.clone();
|
||||
@@ -756,10 +797,10 @@ async fn initialize_remote_connection(
|
||||
Ok(pending_events)
|
||||
}
|
||||
|
||||
fn app_server_event_from_notification(notification: JSONRPCNotification) -> AppServerEvent {
|
||||
match ServerNotification::try_from(notification.clone()) {
|
||||
Ok(notification) => AppServerEvent::ServerNotification(notification),
|
||||
Err(_) => AppServerEvent::LegacyNotification(notification),
|
||||
fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent> {
|
||||
match ServerNotification::try_from(notification) {
|
||||
Ok(notification) => Some(AppServerEvent::ServerNotification(notification)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,18 +892,11 @@ async fn reject_if_server_request_dropped(
|
||||
|
||||
fn event_requires_delivery(event: &AppServerEvent) -> bool {
|
||||
match event {
|
||||
AppServerEvent::ServerNotification(ServerNotification::TurnCompleted(_)) => true,
|
||||
AppServerEvent::LegacyNotification(notification) => matches!(
|
||||
notification
|
||||
.method
|
||||
.strip_prefix("codex/event/")
|
||||
.unwrap_or(¬ification.method),
|
||||
"task_complete" | "turn_aborted" | "shutdown_complete"
|
||||
),
|
||||
AppServerEvent::ServerNotification(notification) => {
|
||||
server_notification_requires_delivery(notification)
|
||||
}
|
||||
AppServerEvent::Disconnected { .. } => true,
|
||||
AppServerEvent::Lagged { .. }
|
||||
| AppServerEvent::ServerNotification(_)
|
||||
| AppServerEvent::ServerRequest(_) => false,
|
||||
AppServerEvent::Lagged { .. } | AppServerEvent::ServerRequest(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,3 +943,40 @@ async fn write_jsonrpc_message(
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn event_requires_delivery_marks_transcript_and_disconnect_events() {
|
||||
assert!(event_requires_delivery(
|
||||
&AppServerEvent::ServerNotification(ServerNotification::AgentMessageDelta(
|
||||
codex_app_server_protocol::AgentMessageDeltaNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item_id: "item".to_string(),
|
||||
delta: "hello".to_string(),
|
||||
},
|
||||
),)
|
||||
));
|
||||
assert!(event_requires_delivery(
|
||||
&AppServerEvent::ServerNotification(ServerNotification::ItemCompleted(
|
||||
codex_app_server_protocol::ItemCompletedNotification {
|
||||
thread_id: "thread".to_string(),
|
||||
turn_id: "turn".to_string(),
|
||||
item: codex_app_server_protocol::ThreadItem::Plan {
|
||||
id: "item".to_string(),
|
||||
text: "step".to_string(),
|
||||
},
|
||||
}
|
||||
),)
|
||||
));
|
||||
assert!(event_requires_delivery(&AppServerEvent::Disconnected {
|
||||
message: "closed".to_string(),
|
||||
}));
|
||||
assert!(!event_requires_delivery(&AppServerEvent::Lagged {
|
||||
skipped: 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-experimental-api-macros = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -24,16 +25,18 @@ serde_with = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
ts-rs = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true, features = ["serde", "v7"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"base64",
|
||||
"macros",
|
||||
"schemars",
|
||||
"server",
|
||||
] }
|
||||
ts-rs = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true, features = ["serde", "v7"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -524,6 +524,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureEnablementSetParams": {
|
||||
"properties": {
|
||||
"enablement": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enablement"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureListParams": {
|
||||
"properties": {
|
||||
"cursor": {
|
||||
@@ -781,6 +796,36 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FsUnwatchParams": {
|
||||
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
|
||||
"properties": {
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"watchId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FsWatchParams": {
|
||||
"description": "Start filesystem watch notifications for an absolute path.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Absolute file or directory path to watch."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FsWriteFileParams": {
|
||||
"description": "Write a file on the host filesystem.",
|
||||
"properties": {
|
||||
@@ -1111,6 +1156,22 @@
|
||||
"title": "ChatgptLoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptDeviceCode"
|
||||
],
|
||||
"title": "ChatgptDeviceCodeLoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptDeviceCodeLoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
@@ -1759,6 +1820,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
@@ -2337,13 +2404,27 @@
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name-based selector.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path-based selector."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"path"
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -2875,6 +2956,22 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadShellCommandParams": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSortKey": {
|
||||
"enum": [
|
||||
"created_at",
|
||||
@@ -3580,6 +3677,30 @@
|
||||
"title": "Thread/compact/startRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/shellCommand"
|
||||
],
|
||||
"title": "Thread/shellCommandRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadShellCommandParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/shellCommandRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -3940,6 +4061,54 @@
|
||||
"title": "Fs/copyRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/watch"
|
||||
],
|
||||
"title": "Fs/watchRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsWatchParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/watchRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/unwatch"
|
||||
],
|
||||
"title": "Fs/unwatchRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsUnwatchParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/unwatchRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -4156,6 +4325,30 @@
|
||||
"title": "ExperimentalFeature/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"experimentalFeature/enablement/set"
|
||||
],
|
||||
"title": "ExperimentalFeature/enablement/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ExperimentalFeatureEnablementSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ExperimentalFeature/enablement/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -28,41 +28,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalMacOsPermissions": {
|
||||
"properties": {
|
||||
"accessibility": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"automations": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
},
|
||||
"calendar": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"contacts": {
|
||||
"$ref": "#/definitions/MacOsContactsPermission"
|
||||
},
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
"reminders": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessibility",
|
||||
"automations",
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
@@ -86,16 +51,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalMacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -298,60 +253,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionRequestApprovalSkillMetadata": {
|
||||
"properties": {
|
||||
"pathToSkillsMd": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pathToSkillsMd"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsContactsPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkApprovalContext": {
|
||||
"properties": {
|
||||
"host": {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"FuzzyFileSearchMatchType": {
|
||||
"enum": [
|
||||
"file",
|
||||
"directory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FuzzyFileSearchResult": {
|
||||
"description": "Superset of [`codex_file_search::FileMatch`]",
|
||||
"properties": {
|
||||
@@ -18,6 +25,9 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"match_type": {
|
||||
"$ref": "#/definitions/FuzzyFileSearchMatchType"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -32,6 +42,7 @@
|
||||
},
|
||||
"required": [
|
||||
"file_name",
|
||||
"match_type",
|
||||
"path",
|
||||
"root",
|
||||
"score"
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"FuzzyFileSearchMatchType": {
|
||||
"enum": [
|
||||
"file",
|
||||
"directory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FuzzyFileSearchResult": {
|
||||
"description": "Superset of [`codex_file_search::FileMatch`]",
|
||||
"properties": {
|
||||
@@ -18,6 +25,9 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"match_type": {
|
||||
"$ref": "#/definitions/FuzzyFileSearchMatchType"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -32,6 +42,7 @@
|
||||
},
|
||||
"required": [
|
||||
"file_name",
|
||||
"match_type",
|
||||
"path",
|
||||
"root",
|
||||
"score"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AccountLoginCompletedNotification": {
|
||||
"properties": {
|
||||
"error": {
|
||||
@@ -83,6 +87,9 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"AppBranding": {
|
||||
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
|
||||
"properties": {
|
||||
@@ -511,6 +518,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -745,6 +774,15 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -964,6 +1002,34 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FsChangedNotification": {
|
||||
"description": "Filesystem watch notification emitted for `fs/watch` subscribers.",
|
||||
"properties": {
|
||||
"changedPaths": {
|
||||
"description": "File or directory paths associated with this event.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"changedPaths",
|
||||
"watchId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FuzzyFileSearchMatchType": {
|
||||
"enum": [
|
||||
"file",
|
||||
"directory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FuzzyFileSearchResult": {
|
||||
"description": "Superset of [`codex_file_search::FileMatch`]",
|
||||
"properties": {
|
||||
@@ -981,6 +1047,9 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"match_type": {
|
||||
"$ref": "#/definitions/FuzzyFileSearchMatchType"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -995,6 +1064,7 @@
|
||||
},
|
||||
"required": [
|
||||
"file_name",
|
||||
"match_type",
|
||||
"path",
|
||||
"root",
|
||||
"score"
|
||||
@@ -1135,7 +1205,10 @@
|
||||
},
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
"postToolUse",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -1180,6 +1253,21 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookRunStatus": {
|
||||
"enum": [
|
||||
"running",
|
||||
@@ -1399,6 +1487,36 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerStartupState": {
|
||||
"enum": [
|
||||
"starting",
|
||||
"ready",
|
||||
"failed",
|
||||
"cancelled"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerStatusUpdatedNotification": {
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/McpServerStartupState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -1454,6 +1572,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -1506,6 +1672,13 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -1604,7 +1777,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
@@ -1831,6 +2006,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1872,6 +2060,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -2177,9 +2376,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -2319,6 +2556,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -2644,6 +2889,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2875,6 +3126,26 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadRealtimeTranscriptUpdatedNotification": {
|
||||
"description": "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.",
|
||||
"properties": {
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"role",
|
||||
"text",
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadStartedNotification": {
|
||||
"properties": {
|
||||
"thread": {
|
||||
@@ -4071,6 +4342,26 @@
|
||||
"title": "McpServer/oauthLogin/completedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"mcpServer/startupStatus/updated"
|
||||
],
|
||||
"title": "McpServer/startupStatus/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/McpServerStatusUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "McpServer/startupStatus/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -4131,6 +4422,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/changed"
|
||||
],
|
||||
"title": "Fs/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -4352,6 +4663,26 @@
|
||||
"title": "Thread/realtime/itemAddedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/transcriptUpdated"
|
||||
],
|
||||
"title": "Thread/realtime/transcriptUpdatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeTranscriptUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/transcriptUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
@@ -28,41 +28,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalMacOsPermissions": {
|
||||
"properties": {
|
||||
"accessibility": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"automations": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
},
|
||||
"calendar": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"contacts": {
|
||||
"$ref": "#/definitions/MacOsContactsPermission"
|
||||
},
|
||||
"launchServices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
},
|
||||
"reminders": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessibility",
|
||||
"automations",
|
||||
"calendar",
|
||||
"contacts",
|
||||
"launchServices",
|
||||
"preferences",
|
||||
"reminders"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
@@ -86,16 +51,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalMacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -452,17 +407,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandExecutionRequestApprovalSkillMetadata": {
|
||||
"properties": {
|
||||
"pathToSkillsMd": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pathToSkillsMd"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DynamicToolCallParams": {
|
||||
"properties": {
|
||||
"arguments": true,
|
||||
@@ -638,49 +582,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsContactsPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationArrayType": {
|
||||
"enum": [
|
||||
"array"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -139,6 +139,9 @@
|
||||
"title": "AgentMessageDeltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"AnalyticsConfig": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -492,11 +495,15 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"needsAuth": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
"name",
|
||||
"needsAuth"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1026,6 +1033,30 @@
|
||||
"title": "Thread/compact/startRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/shellCommand"
|
||||
],
|
||||
"title": "Thread/shellCommandRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadShellCommandParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/shellCommandRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1386,6 +1417,54 @@
|
||||
"title": "Fs/copyRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/watch"
|
||||
],
|
||||
"title": "Fs/watchRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsWatchParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/watchRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/unwatch"
|
||||
],
|
||||
"title": "Fs/unwatchRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsUnwatchParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/unwatchRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1602,6 +1681,30 @@
|
||||
"title": "ExperimentalFeature/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"experimentalFeature/enablement/set"
|
||||
],
|
||||
"title": "ExperimentalFeature/enablement/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ExperimentalFeatureEnablementSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "ExperimentalFeature/enablement/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -2220,6 +2323,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2754,6 +2879,15 @@
|
||||
"title": "CommandExecutionOutputDeltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -3699,6 +3833,40 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureEnablementSetParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"enablement": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enablement"
|
||||
],
|
||||
"title": "ExperimentalFeatureEnablementSetParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureEnablementSetResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"enablement": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": "Feature enablement entries updated by this request.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enablement"
|
||||
],
|
||||
"title": "ExperimentalFeatureEnablementSetResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ExperimentalFeatureListParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -3975,6 +4143,29 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FsChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Filesystem watch notification emitted for `fs/watch` subscribers.",
|
||||
"properties": {
|
||||
"changedPaths": {
|
||||
"description": "File or directory paths associated with this event.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"changedPaths",
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsChangedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"FsCopyParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Copy a file or directory tree on the host filesystem.",
|
||||
@@ -4229,6 +4420,70 @@
|
||||
"title": "FsRemoveResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"FsUnwatchParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
|
||||
"properties": {
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsUnwatchParams",
|
||||
"type": "object"
|
||||
},
|
||||
"FsUnwatchResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Successful response for `fs/unwatch`.",
|
||||
"title": "FsUnwatchResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"FsWatchParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Start filesystem watch notifications for an absolute path.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Absolute file or directory path to watch."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FsWatchParams",
|
||||
"type": "object"
|
||||
},
|
||||
"FsWatchResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Created watch handle returned by `fs/watch`.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Canonicalized path associated with the watch."
|
||||
},
|
||||
"watchId": {
|
||||
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsWatchResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"FsWriteFileParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Write a file on the host filesystem.",
|
||||
@@ -4327,6 +4582,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FuzzyFileSearchMatchType": {
|
||||
"enum": [
|
||||
"file",
|
||||
"directory"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FuzzyFileSearchParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -4370,6 +4632,9 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"match_type": {
|
||||
"$ref": "#/definitions/FuzzyFileSearchMatchType"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4384,6 +4649,7 @@
|
||||
},
|
||||
"required": [
|
||||
"file_name",
|
||||
"match_type",
|
||||
"path",
|
||||
"root",
|
||||
"score"
|
||||
@@ -4625,7 +4891,10 @@
|
||||
},
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
"postToolUse",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -4670,6 +4939,21 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookRunStatus": {
|
||||
"enum": [
|
||||
"running",
|
||||
@@ -5099,6 +5383,22 @@
|
||||
"title": "Chatgptv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptDeviceCode"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
@@ -5180,6 +5480,36 @@
|
||||
"title": "Chatgptv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"loginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptDeviceCode"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountResponseType",
|
||||
"type": "string"
|
||||
},
|
||||
"userCode": {
|
||||
"description": "One-time code the user must enter after signing in.",
|
||||
"type": "string"
|
||||
},
|
||||
"verificationUrl": {
|
||||
"description": "URL the client should open in a browser to complete device code authorization.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"loginId",
|
||||
"type",
|
||||
"userCode",
|
||||
"verificationUrl"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -5215,6 +5545,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MarketplaceLoadErrorInfo": {
|
||||
"properties": {
|
||||
"marketplacePath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplacePath",
|
||||
"message"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -5294,6 +5639,15 @@
|
||||
"title": "McpServerRefreshResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerStartupState": {
|
||||
"enum": [
|
||||
"starting",
|
||||
"ready",
|
||||
"failed",
|
||||
"cancelled"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerStatus": {
|
||||
"properties": {
|
||||
"authStatus": {
|
||||
@@ -5330,6 +5684,29 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerStatusUpdatedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/McpServerStartupState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"title": "McpServerStatusUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -5387,6 +5764,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MergeStrategy": {
|
||||
"enum": [
|
||||
"replace",
|
||||
@@ -5638,6 +6063,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkDomainPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
"deny"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkRequirements": {
|
||||
"properties": {
|
||||
"allowLocalBinding": {
|
||||
@@ -5647,6 +6079,7 @@
|
||||
]
|
||||
},
|
||||
"allowUnixSockets": {
|
||||
"description": "Legacy compatibility view derived from `unix_sockets`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5662,6 +6095,7 @@
|
||||
]
|
||||
},
|
||||
"allowedDomains": {
|
||||
"description": "Legacy compatibility view derived from `domains`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5683,6 +6117,7 @@
|
||||
]
|
||||
},
|
||||
"deniedDomains": {
|
||||
"description": "Legacy compatibility view derived from `domains`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5691,6 +6126,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"domains": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/NetworkDomainPermission"
|
||||
},
|
||||
"description": "Canonical network permission map for `experimental_network`.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
@@ -5705,6 +6150,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"managedAllowedDomainsOnly": {
|
||||
"description": "When true, only managed allowlist entries are respected while managed network enforcement is active.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"socksPort": {
|
||||
"format": "uint16",
|
||||
"minimum": 0.0,
|
||||
@@ -5712,10 +6164,34 @@
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"unixSockets": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/NetworkUnixSocketPermission"
|
||||
},
|
||||
"description": "Canonical unix socket permission map for `experimental_network`.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkUnixSocketPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"OverriddenMetadata": {
|
||||
"properties": {
|
||||
"effectiveValue": true,
|
||||
@@ -5841,7 +6317,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
@@ -6079,6 +6557,20 @@
|
||||
"PluginListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"featuredPluginIds": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"marketplaceLoadErrors": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/MarketplaceLoadErrorInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"marketplaces": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginMarketplaceEntry"
|
||||
@@ -7099,6 +7591,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
@@ -8248,6 +8746,26 @@
|
||||
"title": "McpServer/oauthLogin/completedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"mcpServer/startupStatus/updated"
|
||||
],
|
||||
"title": "McpServer/startupStatus/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/McpServerStatusUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "McpServer/startupStatus/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -8308,6 +8826,26 @@
|
||||
"title": "App/list/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"fs/changed"
|
||||
],
|
||||
"title": "Fs/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/FsChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Fs/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -8529,6 +9067,26 @@
|
||||
"title": "Thread/realtime/itemAddedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/realtime/transcriptUpdated"
|
||||
],
|
||||
"title": "Thread/realtime/transcriptUpdatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadRealtimeTranscriptUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/realtime/transcriptUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -8689,6 +9247,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -8870,6 +9441,9 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"interface": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -8895,6 +9469,7 @@
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"enabled",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
@@ -8951,13 +9526,27 @@
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name-based selector.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path-based selector."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"path"
|
||||
"enabled"
|
||||
],
|
||||
"title": "SkillsConfigWriteParams",
|
||||
"type": "object"
|
||||
@@ -9085,6 +9674,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -9597,9 +10197,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -9739,6 +10377,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -10064,6 +10710,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -10546,6 +11198,28 @@
|
||||
"title": "ThreadRealtimeStartedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadRealtimeTranscriptUpdatedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.",
|
||||
"properties": {
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"role",
|
||||
"text",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadRealtimeTranscriptUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadResumeParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
|
||||
@@ -10778,6 +11452,29 @@
|
||||
"title": "ThreadSetNameResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadShellCommandParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadShellCommandParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadShellCommandResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ThreadShellCommandResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSortKey": {
|
||||
"enum": [
|
||||
"created_at",
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"codexHome": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Absolute path to the server's $CODEX_HOME directory."
|
||||
},
|
||||
"platformFamily": {
|
||||
"description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.",
|
||||
"type": "string"
|
||||
@@ -14,6 +28,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"codexHome",
|
||||
"platformFamily",
|
||||
"platformOs",
|
||||
"userAgent"
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
|
||||
@@ -102,6 +102,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkDomainPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
"deny"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkRequirements": {
|
||||
"properties": {
|
||||
"allowLocalBinding": {
|
||||
@@ -111,6 +118,7 @@
|
||||
]
|
||||
},
|
||||
"allowUnixSockets": {
|
||||
"description": "Legacy compatibility view derived from `unix_sockets`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -126,6 +134,7 @@
|
||||
]
|
||||
},
|
||||
"allowedDomains": {
|
||||
"description": "Legacy compatibility view derived from `domains`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -147,6 +156,7 @@
|
||||
]
|
||||
},
|
||||
"deniedDomains": {
|
||||
"description": "Legacy compatibility view derived from `domains`.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -155,6 +165,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"domains": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/NetworkDomainPermission"
|
||||
},
|
||||
"description": "Canonical network permission map for `experimental_network`.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
@@ -169,6 +189,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"managedAllowedDomainsOnly": {
|
||||
"description": "When true, only managed allowlist entries are respected while managed network enforcement is active.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"socksPort": {
|
||||
"format": "uint16",
|
||||
"minimum": 0.0,
|
||||
@@ -176,10 +203,27 @@
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"unixSockets": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/NetworkUnixSocketPermission"
|
||||
},
|
||||
"description": "Canonical unix socket permission map for `experimental_network`.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkUnixSocketPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ResidencyRequirement": {
|
||||
"enum": [
|
||||
"us"
|
||||
|
||||
@@ -112,9 +112,38 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"TurnError": {
|
||||
"properties": {
|
||||
"additionalDetails": {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"enablement": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enablement"
|
||||
],
|
||||
"title": "ExperimentalFeatureEnablementSetParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"enablement": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": "Feature enablement entries updated by this request.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enablement"
|
||||
],
|
||||
"title": "ExperimentalFeatureEnablementSetResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Filesystem watch notification emitted for `fs/watch` subscribers.",
|
||||
"properties": {
|
||||
"changedPaths": {
|
||||
"description": "File or directory paths associated with this event.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"changedPaths",
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsChangedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
|
||||
"properties": {
|
||||
"watchId": {
|
||||
"description": "Watch identifier returned by `fs/watch`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsUnwatchParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Successful response for `fs/unwatch`.",
|
||||
"title": "FsUnwatchResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Start filesystem watch notifications for an absolute path.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Absolute file or directory path to watch."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "FsWatchParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Created watch handle returned by `fs/watch`.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Canonicalized path associated with the watch."
|
||||
},
|
||||
"watchId": {
|
||||
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"watchId"
|
||||
],
|
||||
"title": "FsWatchResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -29,7 +29,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
"plus",
|
||||
"pro",
|
||||
"team",
|
||||
"self_serve_business_usage_based",
|
||||
"business",
|
||||
"enterprise_cbp_usage_based",
|
||||
"enterprise",
|
||||
"edu",
|
||||
"unknown"
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"definitions": {
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
"postToolUse",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"definitions": {
|
||||
"HookEventName": {
|
||||
"enum": [
|
||||
"preToolUse",
|
||||
"postToolUse",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
@@ -177,6 +177,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -257,6 +266,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -289,6 +313,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -441,9 +513,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -583,6 +693,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -908,6 +1026,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -177,6 +177,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -257,6 +266,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -289,6 +313,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -441,9 +513,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -583,6 +693,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -908,6 +1026,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -37,6 +37,22 @@
|
||||
"title": "Chatgptv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptDeviceCode"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.",
|
||||
"properties": {
|
||||
|
||||
@@ -42,6 +42,36 @@
|
||||
"title": "Chatgptv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"loginId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"chatgptDeviceCode"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountResponseType",
|
||||
"type": "string"
|
||||
},
|
||||
"userCode": {
|
||||
"description": "One-time code the user must enter after signing in.",
|
||||
"type": "string"
|
||||
},
|
||||
"verificationUrl": {
|
||||
"description": "URL the client should open in a browser to complete device code authorization.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"loginId",
|
||||
"type",
|
||||
"userCode",
|
||||
"verificationUrl"
|
||||
],
|
||||
"title": "ChatgptDeviceCodev2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"McpServerStartupState": {
|
||||
"enum": [
|
||||
"starting",
|
||||
"ready",
|
||||
"failed",
|
||||
"cancelled"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/McpServerStartupState"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"title": "McpServerStatusUpdatedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -21,11 +21,15 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"needsAuth": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
"name",
|
||||
"needsAuth"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MarketplaceLoadErrorInfo": {
|
||||
"properties": {
|
||||
"marketplacePath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplacePath",
|
||||
"message"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginAuthPolicy": {
|
||||
"enum": [
|
||||
"ON_INSTALL",
|
||||
@@ -239,6 +254,20 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"featuredPluginIds": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"marketplaceLoadErrors": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/MarketplaceLoadErrorInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"marketplaces": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginMarketplaceEntry"
|
||||
|
||||
@@ -25,11 +25,15 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"needsAuth": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
"name",
|
||||
"needsAuth"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -314,6 +318,9 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"interface": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -339,6 +346,7 @@
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"enabled",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
|
||||
@@ -607,6 +607,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
|
||||
@@ -131,6 +131,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -291,6 +313,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -371,6 +402,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -403,6 +449,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -422,6 +516,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -555,9 +656,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -697,6 +836,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1022,6 +1169,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name-based selector.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Path-based selector."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"path"
|
||||
"enabled"
|
||||
],
|
||||
"title": "SkillsConfigWriteParams",
|
||||
"type": "object"
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
|
||||
"enum": [
|
||||
@@ -193,6 +196,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -353,6 +378,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -456,6 +490,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -488,6 +537,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -514,6 +611,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -778,6 +882,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -815,6 +932,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -1035,9 +1163,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1177,6 +1343,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1502,6 +1676,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -131,6 +134,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -291,6 +316,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -394,6 +428,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -426,6 +475,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -445,6 +542,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -536,6 +640,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -573,6 +690,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -793,9 +921,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -935,6 +1101,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1260,6 +1434,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -131,6 +134,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -291,6 +316,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -394,6 +428,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -426,6 +475,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -445,6 +542,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -536,6 +640,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -573,6 +690,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -793,9 +921,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -935,6 +1101,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1260,6 +1434,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -131,6 +134,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -291,6 +316,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -394,6 +428,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -426,6 +475,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -445,6 +542,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -536,6 +640,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -573,6 +690,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -793,9 +921,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -935,6 +1101,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1260,6 +1434,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.",
|
||||
"properties": {
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"role",
|
||||
"text",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadRealtimeTranscriptUpdatedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -673,6 +673,12 @@
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
|
||||
"enum": [
|
||||
@@ -193,6 +196,28 @@
|
||||
],
|
||||
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.",
|
||||
"properties": {
|
||||
"activeTurnNotSteerable": {
|
||||
"properties": {
|
||||
"turnKind": {
|
||||
"$ref": "#/definitions/NonSteerableTurnKind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnKind"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"activeTurnNotSteerable"
|
||||
],
|
||||
"title": "ActiveTurnNotSteerableCodexErrorInfo",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -353,6 +378,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CommandExecutionSource": {
|
||||
"enum": [
|
||||
"agent",
|
||||
"userShell",
|
||||
"unifiedExecStartup",
|
||||
"unifiedExecInteraction"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandExecutionStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -456,6 +490,21 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookPromptFragment": {
|
||||
"properties": {
|
||||
"hookRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookRunId",
|
||||
"text"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpToolCallError": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -488,6 +537,54 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MemoryCitation": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/MemoryCitationEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"threadIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"threadIds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MemoryCitationEntry": {
|
||||
"properties": {
|
||||
"lineEnd": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"lineStart": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lineEnd",
|
||||
"lineStart",
|
||||
"note",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MessagePhase": {
|
||||
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
|
||||
"oneOf": [
|
||||
@@ -514,6 +611,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
"compact"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PatchApplyStatus": {
|
||||
"enum": [
|
||||
"inProgress",
|
||||
@@ -778,6 +882,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"custom": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"custom"
|
||||
],
|
||||
"title": "CustomSessionSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -815,6 +932,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"agent_path": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentPath"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"agent_role": {
|
||||
"default": null,
|
||||
"type": [
|
||||
@@ -1035,9 +1163,47 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"fragments": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookPromptFragment"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"hookPrompt"
|
||||
],
|
||||
"title": "HookPromptThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fragments",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "HookPromptThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryCitation": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MemoryCitation"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"phase": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1177,6 +1343,14 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CommandExecutionSource"
|
||||
}
|
||||
],
|
||||
"default": "agent"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/CommandExecutionStatus"
|
||||
},
|
||||
@@ -1502,6 +1676,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"savedPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user