Merge openai/main: sync latest codex upstream into dev baseline

This commit is contained in:
2026-02-13 17:47:01 -08:00
515 changed files with 36147 additions and 9894 deletions
+2 -8
View File
@@ -1,11 +1,5 @@
iTerm
iTerm2
psuedo
shouldBe
amountIn
MapPin
summar
Nd
nd
edn
lightening
te
TE
+2 -2
View File
@@ -1,6 +1,6 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new
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
ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE
+14
View File
@@ -0,0 +1,14 @@
---
name: test-tui
description: Guide for testing Codex TUI interactively
---
You can start and use Codex TUI to verify changes.
Important notes:
Start interactively.
Always set RUST_LOG="trace" when starting the process.
Pass `-c log_dir=<some_temp_dir>` argument to have logs written to a specific directory to help with debugging.
When sending a test message programmatically, send text first, then send Enter in a separate write (do not send text + Enter in one burst).
Use `just codex` target to run - `just codex -c ...`
+7
View File
@@ -21,6 +21,13 @@ body:
label: What subscription do you have?
validations:
required: true
- type: input
id: platform
attributes:
label: What platform is your computer?
description: |
For macOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
id: actual
attributes:
-18
View File
@@ -1,18 +0,0 @@
You are an assistant that triages new GitHub issues by identifying potential duplicates.
You will receive the following JSON files located in the current working directory:
- `codex-current-issue.json`: JSON object describing the newly created issue (fields: number, title, body).
- `codex-existing-issues.json`: JSON array of recent issues (each element includes number, title, body, createdAt).
Instructions:
- Load both files as JSON and review their contents carefully. The codex-existing-issues.json file is large, ensure you explore all of it.
- Compare the current issue against the existing issues to find up to five that appear to describe the same underlying problem or request.
- Only consider an issue a potential duplicate if there is a clear overlap in symptoms, feature requests, reproduction steps, or error messages.
- Prioritize newer issues when similarity is comparable.
- Ignore pull requests and issues whose similarity is tenuous.
- When unsure, prefer returning fewer matches.
Output requirements:
- Respond with a JSON array of issue numbers (integers), ordered from most likely duplicate to least.
- Include at most five numbers.
- If you find no plausible duplicates, respond with `[]`.
+103 -1
View File
@@ -17,7 +17,7 @@ if [[ -n "${APT_INSTALL_ARGS:-}" ]]; then
fi
sudo apt-get update "${apt_update_args[@]}"
sudo apt-get install -y "${apt_install_args[@]}" musl-tools pkg-config g++ clang libc++-dev libc++abi-dev lld
sudo apt-get install -y "${apt_install_args[@]}" ca-certificates curl musl-tools pkg-config libcap-dev g++ clang libc++-dev libc++abi-dev lld xz-utils
case "${TARGET}" in
x86_64-unknown-linux-musl)
@@ -32,6 +32,11 @@ case "${TARGET}" in
;;
esac
libcap_version="2.75"
libcap_sha256="de4e7e064c9ba451d5234dd46e897d7c71c96a9ebf9a0c445bc04f4742d83632"
libcap_tarball_name="libcap-${libcap_version}.tar.xz"
libcap_download_url="https://mirrors.edge.kernel.org/pub/linux/libs/security/linux-privs/libcap2/${libcap_tarball_name}"
# Use the musl toolchain as the Rust linker to avoid Zig injecting its own CRT.
if command -v "${arch}-linux-musl-gcc" >/dev/null; then
musl_linker="$(command -v "${arch}-linux-musl-gcc")"
@@ -47,6 +52,43 @@ runner_temp="${RUNNER_TEMP:-/tmp}"
tool_root="${runner_temp}/codex-musl-tools-${TARGET}"
mkdir -p "${tool_root}"
libcap_root="${tool_root}/libcap-${libcap_version}"
libcap_src_root="${libcap_root}/src"
libcap_prefix="${libcap_root}/prefix"
libcap_pkgconfig_dir="${libcap_prefix}/lib/pkgconfig"
if [[ ! -f "${libcap_prefix}/lib/libcap.a" ]]; then
mkdir -p "${libcap_src_root}" "${libcap_prefix}/lib" "${libcap_prefix}/include/sys" "${libcap_prefix}/include/linux" "${libcap_pkgconfig_dir}"
libcap_tarball="${libcap_root}/${libcap_tarball_name}"
curl -fsSL "${libcap_download_url}" -o "${libcap_tarball}"
echo "${libcap_sha256} ${libcap_tarball}" | sha256sum -c -
tar -xJf "${libcap_tarball}" -C "${libcap_src_root}"
libcap_source_dir="${libcap_src_root}/libcap-${libcap_version}"
make -C "${libcap_source_dir}/libcap" -j"$(nproc)" \
CC="${musl_linker}" \
AR=ar \
RANLIB=ranlib
cp "${libcap_source_dir}/libcap/libcap.a" "${libcap_prefix}/lib/libcap.a"
cp "${libcap_source_dir}/libcap/include/uapi/linux/capability.h" "${libcap_prefix}/include/linux/capability.h"
cp "${libcap_source_dir}/libcap/../libcap/include/sys/capability.h" "${libcap_prefix}/include/sys/capability.h"
cat > "${libcap_pkgconfig_dir}/libcap.pc" <<EOF
prefix=${libcap_prefix}
exec_prefix=\${prefix}
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: libcap
Description: Linux capabilities
Version: ${libcap_version}
Libs: -L\${libdir} -lcap
Cflags: -I\${includedir}
EOF
fi
sysroot=""
if command -v zig >/dev/null; then
zig_bin="$(command -v zig)"
@@ -59,7 +101,19 @@ set -euo pipefail
args=()
skip_next=0
pending_include=0
for arg in "\$@"; do
if [[ "\${pending_include}" -eq 1 ]]; then
pending_include=0
if [[ "\${arg}" == /usr/include || "\${arg}" == /usr/include/* ]]; then
# Keep host-only headers available, but after the target sysroot headers.
args+=("-idirafter" "\${arg}")
else
args+=("-I" "\${arg}")
fi
continue
fi
if [[ "\${skip_next}" -eq 1 ]]; then
skip_next=0
continue
@@ -77,6 +131,15 @@ for arg in "\$@"; do
fi
continue
;;
-I)
pending_include=1
continue
;;
-I/usr/include|-I/usr/include/*)
# Avoid making glibc headers win over musl headers.
args+=("-idirafter" "\${arg#-I}")
continue
;;
-Wp,-U_FORTIFY_SOURCE)
# aws-lc-sys emits this GCC preprocessor forwarding form in debug
# builds, but zig cc expects the define flag directly.
@@ -95,7 +158,19 @@ set -euo pipefail
args=()
skip_next=0
pending_include=0
for arg in "\$@"; do
if [[ "\${pending_include}" -eq 1 ]]; then
pending_include=0
if [[ "\${arg}" == /usr/include || "\${arg}" == /usr/include/* ]]; then
# Keep host-only headers available, but after the target sysroot headers.
args+=("-idirafter" "\${arg}")
else
args+=("-I" "\${arg}")
fi
continue
fi
if [[ "\${skip_next}" -eq 1 ]]; then
skip_next=0
continue
@@ -113,6 +188,15 @@ for arg in "\$@"; do
fi
continue
;;
-I)
pending_include=1
continue
;;
-I/usr/include|-I/usr/include/*)
# Avoid making glibc headers win over musl headers.
args+=("-idirafter" "\${arg#-I}")
continue
;;
-Wp,-U_FORTIFY_SOURCE)
# aws-lc-sys emits this GCC forwarding form in debug builds; zig c++
# expects the define flag directly.
@@ -175,3 +259,21 @@ echo "${cargo_linker_var}=${musl_linker}" >> "$GITHUB_ENV"
echo "CMAKE_C_COMPILER=${cc}" >> "$GITHUB_ENV"
echo "CMAKE_CXX_COMPILER=${cxx}" >> "$GITHUB_ENV"
echo "CMAKE_ARGS=-DCMAKE_HAVE_THREADS_LIBRARY=1 -DCMAKE_USE_PTHREADS_INIT=1 -DCMAKE_THREAD_LIBS_INIT=-pthread -DTHREADS_PREFER_PTHREAD_FLAG=ON" >> "$GITHUB_ENV"
# Allow pkg-config resolution during cross-compilation.
echo "PKG_CONFIG_ALLOW_CROSS=1" >> "$GITHUB_ENV"
pkg_config_path="${libcap_pkgconfig_dir}"
if [[ -n "${PKG_CONFIG_PATH:-}" ]]; then
pkg_config_path="${pkg_config_path}:${PKG_CONFIG_PATH}"
fi
echo "PKG_CONFIG_PATH=${pkg_config_path}" >> "$GITHUB_ENV"
pkg_config_path_var="PKG_CONFIG_PATH_${TARGET}"
pkg_config_path_var="${pkg_config_path_var//-/_}"
echo "${pkg_config_path_var}=${libcap_pkgconfig_dir}" >> "$GITHUB_ENV"
if [[ -n "${sysroot}" && "${sysroot}" != "/" ]]; then
echo "PKG_CONFIG_SYSROOT_DIR=${sysroot}" >> "$GITHUB_ENV"
pkg_config_sysroot_var="PKG_CONFIG_SYSROOT_DIR_${TARGET}"
pkg_config_sysroot_var="${pkg_config_sysroot_var//-/_}"
echo "${pkg_config_sysroot_var}=${sysroot}" >> "$GITHUB_ENV"
fi
+144
View File
@@ -0,0 +1,144 @@
name: Bazel (experimental)
# Note this workflow was originally derived from:
# https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml
on:
pull_request: {}
push:
branches:
- main
workflow_dispatch:
concurrency:
# Cancel previous actions from the same PR or branch except 'main' branch.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency and https://docs.github.com/en/actions/learn-github-actions/contexts for more info.
group: concurrency-group::${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}${{ github.ref_name == 'main' && format('::{0}', github.run_id) || ''}}
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
test:
strategy:
fail-fast: false
matrix:
include:
# macOS
- os: macos-15-xlarge
target: aarch64-apple-darwin
- os: macos-15-xlarge
target: x86_64-apple-darwin
# Linux
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
- os: ubuntu-24.04
target: x86_64-unknown-linux-musl
# TODO: Enable Windows once we fix the toolchain issues there.
#- 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
# 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
# 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: |
bazel_args=(
test
//...
--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
)
if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then
echo "BuildBuddy API key is available; using remote Bazel configuration."
bazel $BAZEL_STARTUP_ARGS \
--bazelrc=.github/workflows/ci.bazelrc \
"${bazel_args[@]}" \
"--remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY"
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 $BAZEL_STARTUP_ARGS \
--noexperimental_remote_repo_contents_cache \
"${bazel_args[@]}" \
--remote_cache= \
--remote_executor=
fi
+356
View File
@@ -0,0 +1,356 @@
name: Issue Deduplicator
on:
issues:
types:
- opened
- labeled
jobs:
gather-duplicates:
name: Identify potential duplicates
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-deduplicate'))
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
codex_output: ${{ steps.select-final.outputs.codex_output }}
steps:
- uses: actions/checkout@v6
- name: Prepare Codex inputs
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
CURRENT_ISSUE_FILE=codex-current-issue.json
EXISTING_ALL_FILE=codex-existing-issues-all.json
EXISTING_OPEN_FILE=codex-existing-issues-open.json
gh issue list --repo "$REPO" \
--json number,title,body,createdAt,updatedAt,state,labels \
--limit 1000 \
--state all \
--search "sort:created-desc" \
| jq '[.[] | {
number,
title,
body: ((.body // "")[0:4000]),
createdAt,
updatedAt,
state,
labels: ((.labels // []) | map(.name))
}]' \
> "$EXISTING_ALL_FILE"
gh issue list --repo "$REPO" \
--json number,title,body,createdAt,updatedAt,state,labels \
--limit 1000 \
--state open \
--search "sort:created-desc" \
| jq '[.[] | {
number,
title,
body: ((.body // "")[0:4000]),
createdAt,
updatedAt,
state,
labels: ((.labels // []) | map(.name))
}]' \
> "$EXISTING_OPEN_FILE"
gh issue view "$ISSUE_NUMBER" \
--repo "$REPO" \
--json number,title,body \
| jq '{number, title, body: ((.body // "")[0:4000])}' \
> "$CURRENT_ISSUE_FILE"
echo "Prepared duplicate detection input files."
echo "all_issue_count=$(jq 'length' "$EXISTING_ALL_FILE")"
echo "open_issue_count=$(jq 'length' "$EXISTING_OPEN_FILE")"
# Prompt instructions are intentionally inline in this workflow. The old
# .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
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
You will receive the following JSON files located in the current working directory:
- `codex-current-issue.json`: JSON object describing the newly created issue (fields: number, title, body).
- `codex-existing-issues-all.json`: JSON array of recent issues with states, timestamps, and labels.
Instructions:
- Compare the current issue against the existing issues to find up to five that appear to describe the same underlying problem or request.
- Prioritize concrete overlap in symptoms, reproduction details, error signatures, and user intent.
- Prefer active unresolved issues when confidence is similar.
- Closed issues can still be valid duplicates if they clearly match.
- Return fewer matches rather than speculative ones.
- If confidence is low, return an empty list.
- Include at most five issue numbers.
- After analysis, provide a short reason for your decision.
output-schema: |
{
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "string"
}
},
"reason": { "type": "string" }
},
"required": ["issues", "reason"],
"additionalProperties": false
}
- id: normalize-all
name: Normalize pass 1 output
env:
CODEX_OUTPUT: ${{ steps.codex-all.outputs.final-message }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
raw=${CODEX_OUTPUT//$'\r'/}
parsed=false
issues='[]'
reason=''
if [ -n "$raw" ] && printf '%s' "$raw" | jq -e 'type == "object" and (.issues | type == "array")' >/dev/null 2>&1; then
parsed=true
issues=$(printf '%s' "$raw" | jq -c '[.issues[] | tostring]')
reason=$(printf '%s' "$raw" | jq -r '.reason // ""')
else
reason='Pass 1 output was empty or invalid JSON.'
fi
filtered=$(jq -cn --argjson issues "$issues" --arg current "$CURRENT_ISSUE_NUMBER" '[
$issues[]
| tostring
| select(. != $current)
] | reduce .[] as $issue ([]; if index($issue) then . else . + [$issue] end) | .[:5]')
has_matches=false
if [ "$(jq 'length' <<< "$filtered")" -gt 0 ]; then
has_matches=true
fi
echo "Pass 1 parsed: $parsed"
echo "Pass 1 matches after filtering: $(jq 'length' <<< "$filtered")"
echo "Pass 1 reason: $reason"
{
echo "issues_json=$filtered"
echo "reason<<EOF"
echo "$reason"
echo "EOF"
echo "has_matches=$has_matches"
} >> "$GITHUB_OUTPUT"
- id: codex-open
name: Find duplicates (pass 2, open issues)
if: ${{ steps.normalize-all.outputs.has_matches != 'true' }}
uses: openai/codex-action@main
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
This is a fallback pass because a broad search did not find convincing matches.
You will receive the following JSON files located in the current working directory:
- `codex-current-issue.json`: JSON object describing the newly created issue (fields: number, title, body).
- `codex-existing-issues-open.json`: JSON array of open issues only.
Instructions:
- Search only these active unresolved issues for duplicates of the current issue.
- Prioritize concrete overlap in symptoms, reproduction details, error signatures, and user intent.
- Prefer fewer, higher-confidence matches.
- If confidence is low, return an empty list.
- Include at most five issue numbers.
- After analysis, provide a short reason for your decision.
output-schema: |
{
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "string"
}
},
"reason": { "type": "string" }
},
"required": ["issues", "reason"],
"additionalProperties": false
}
- id: normalize-open
name: Normalize pass 2 output
if: ${{ steps.normalize-all.outputs.has_matches != 'true' }}
env:
CODEX_OUTPUT: ${{ steps.codex-open.outputs.final-message }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
raw=${CODEX_OUTPUT//$'\r'/}
parsed=false
issues='[]'
reason=''
if [ -n "$raw" ] && printf '%s' "$raw" | jq -e 'type == "object" and (.issues | type == "array")' >/dev/null 2>&1; then
parsed=true
issues=$(printf '%s' "$raw" | jq -c '[.issues[] | tostring]')
reason=$(printf '%s' "$raw" | jq -r '.reason // ""')
else
reason='Pass 2 output was empty or invalid JSON.'
fi
filtered=$(jq -cn --argjson issues "$issues" --arg current "$CURRENT_ISSUE_NUMBER" '[
$issues[]
| tostring
| select(. != $current)
] | reduce .[] as $issue ([]; if index($issue) then . else . + [$issue] end) | .[:5]')
has_matches=false
if [ "$(jq 'length' <<< "$filtered")" -gt 0 ]; then
has_matches=true
fi
echo "Pass 2 parsed: $parsed"
echo "Pass 2 matches after filtering: $(jq 'length' <<< "$filtered")"
echo "Pass 2 reason: $reason"
{
echo "issues_json=$filtered"
echo "reason<<EOF"
echo "$reason"
echo "EOF"
echo "has_matches=$has_matches"
} >> "$GITHUB_OUTPUT"
- id: select-final
name: Select final duplicate set
env:
PASS1_ISSUES: ${{ steps.normalize-all.outputs.issues_json }}
PASS1_REASON: ${{ steps.normalize-all.outputs.reason }}
PASS2_ISSUES: ${{ steps.normalize-open.outputs.issues_json }}
PASS2_REASON: ${{ steps.normalize-open.outputs.reason }}
PASS1_HAS_MATCHES: ${{ steps.normalize-all.outputs.has_matches }}
PASS2_HAS_MATCHES: ${{ steps.normalize-open.outputs.has_matches }}
run: |
set -eo pipefail
selected_issues='[]'
selected_reason='No plausible duplicates found.'
selected_pass='none'
if [ "$PASS1_HAS_MATCHES" = "true" ]; then
selected_issues=${PASS1_ISSUES:-'[]'}
selected_reason=${PASS1_REASON:-'Pass 1 found duplicates.'}
selected_pass='all'
fi
if [ "$PASS2_HAS_MATCHES" = "true" ]; then
selected_issues=${PASS2_ISSUES:-'[]'}
selected_reason=${PASS2_REASON:-'Pass 2 found duplicates.'}
selected_pass='open-fallback'
fi
final_json=$(jq -cn \
--argjson issues "$selected_issues" \
--arg reason "$selected_reason" \
--arg pass "$selected_pass" \
'{issues: $issues, reason: $reason, pass: $pass}')
echo "Final pass used: $selected_pass"
echo "Final duplicate count: $(jq '.issues | length' <<< "$final_json")"
echo "Final reason: $(jq -r '.reason' <<< "$final_json")"
{
echo "codex_output<<EOF"
echo "$final_json"
echo "EOF"
} >> "$GITHUB_OUTPUT"
comment-on-issue:
name: Comment with potential duplicates
needs: gather-duplicates
if: ${{ needs.gather-duplicates.result != 'skipped' }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Comment on issue
uses: actions/github-script@v8
env:
CODEX_OUTPUT: ${{ needs.gather-duplicates.outputs.codex_output }}
with:
github-token: ${{ github.token }}
script: |
const raw = process.env.CODEX_OUTPUT ?? '';
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
core.info(`Codex output was not valid JSON. Raw output: ${raw}`);
core.info(`Parse error: ${error.message}`);
return;
}
const issues = Array.isArray(parsed?.issues) ? parsed.issues : [];
const currentIssueNumber = String(context.payload.issue.number);
const passUsed = typeof parsed?.pass === 'string' ? parsed.pass : 'unknown';
const reason = typeof parsed?.reason === 'string' ? parsed.reason : '';
console.log(`Current issue number: ${currentIssueNumber}`);
console.log(`Pass used: ${passUsed}`);
if (reason) {
console.log(`Reason: ${reason}`);
}
console.log(issues);
const filteredIssues = [...new Set(issues.map((value) => String(value)))].filter((value) => value !== currentIssueNumber).slice(0, 5);
if (filteredIssues.length === 0) {
core.info('Codex reported no potential duplicates.');
return;
}
const lines = [
'Potential duplicates detected. Please review them and close your issue if it is a duplicate.',
'',
...filteredIssues.map((value) => `- #${String(value)}`),
'',
'*Powered by [Codex Action](https://github.com/openai/codex-action)*'];
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: lines.join("\n"),
});
- name: Remove codex-deduplicate label
if: ${{ always() && github.event.action == 'labeled' && github.event.label.name == 'codex-deduplicate' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
gh issue edit "${{ github.event.issue.number }}" --remove-label codex-deduplicate || true
echo "Attempted to remove label: codex-deduplicate"
+663 -14
View File
@@ -1,24 +1,673 @@
name: rust-ci
on:
pull_request: {}
push:
branches: [main]
pull_request:
branches:
- main
workflow_dispatch:
jobs:
noop:
name: No-op CI (placeholder)
runs-on: ubuntu-latest
steps:
- name: Success
run: echo "rust-ci placeholder job succeeded"
# CI builds in debug (dev) for faster signal.
jobs:
# --- Detect what changed to detect which tests to run (always runs) -------------------------------------
changed:
name: Detect changed areas
runs-on: ubuntu-24.04
outputs:
codex: ${{ steps.detect.outputs.codex }}
workflows: ${{ steps.detect.outputs.workflows }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Detect changed paths (no external action)
id: detect
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA='${{ github.event.pull_request.base.sha }}'
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")
fi
codex=false
workflows=false
for f in "${files[@]}"; do
[[ $f == codex-rs/* ]] && codex=true
[[ $f == .github/* ]] && workflows=true
done
echo "codex=$codex" >> "$GITHUB_OUTPUT"
echo "workflows=$workflows" >> "$GITHUB_OUTPUT"
# --- CI that doesn't need specific targets ---------------------------------
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' }}
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v6
- uses: dtolnay/[email protected]
with:
components: rustfmt
- name: cargo fmt
run: cargo fmt -- --config imports_granularity=Item --check
cargo_shear:
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' }}
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v6
- uses: dtolnay/[email protected]
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2
with:
tool: cargo-shear
version: 1.5.1
- name: cargo shear
run: cargo shear
# --- 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
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' }}
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@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/[email protected]
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
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"
- 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@v6
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: 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/[email protected]
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: 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@v6
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'
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
# --- Gatherer job that you mark as the ONLY required status -----------------
results:
name: CI results (required)
needs: [noop]
runs-on: ubuntu-latest
needs: [changed, general, cargo_shear, lint_build, tests]
if: always()
runs-on: ubuntu-24.04
steps:
- run: |
[[ "${{ needs.noop.result }}" == "success" ]] || exit 1
- name: Summarize
shell: bash
run: |
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.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then
echo 'No relevant changes -> CI not required.'
exit 0
fi
# Otherwise require the jobs to have succeeded
[[ '${{ 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."
+264
View File
@@ -0,0 +1,264 @@
name: rust-release-windows
on:
workflow_call:
inputs:
release-lto:
required: true
type: string
secrets:
AZURE_TRUSTED_SIGNING_CLIENT_ID:
required: true
AZURE_TRUSTED_SIGNING_TENANT_ID:
required: true
AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID:
required: true
AZURE_TRUSTED_SIGNING_ENDPOINT:
required: true
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME:
required: true
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME:
required: true
jobs:
build-windows-binaries:
name: Build Windows binaries - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 60
permissions:
contents: read
defaults:
run:
working-directory: codex-rs
env:
CARGO_PROFILE_RELEASE_LTO: ${{ inputs.release-lto }}
strategy:
fail-fast: false
matrix:
include:
- runner: windows-x64
target: x86_64-pc-windows-msvc
bundle: primary
build_args: --bin codex --bin codex-responses-api-proxy
runs_on:
group: codex-runners
labels: codex-windows-x64
- runner: windows-arm64
target: aarch64-pc-windows-msvc
bundle: primary
build_args: --bin codex --bin codex-responses-api-proxy
runs_on:
group: codex-runners
labels: codex-windows-arm64
- runner: windows-x64
target: x86_64-pc-windows-msvc
bundle: helpers
build_args: --bin codex-windows-sandbox-setup --bin codex-command-runner
runs_on:
group: codex-runners
labels: codex-windows-x64
- runner: windows-arm64
target: aarch64-pc-windows-msvc
bundle: helpers
build_args: --bin codex-windows-sandbox-setup --bin codex-command-runner
runs_on:
group: codex-runners
labels: codex-windows-arm64
steps:
- uses: actions/checkout@v6
- name: Print runner specs (Windows)
shell: powershell
run: |
$computer = Get-CimInstance Win32_ComputerSystem
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
$ramGiB = [math]::Round($computer.TotalPhysicalMemory / 1GB, 1)
Write-Host "Runner: $env:RUNNER_NAME"
Write-Host "OS: $([System.Environment]::OSVersion.VersionString)"
Write-Host "CPU: $($cpu.Name)"
Write-Host "Logical CPUs: $($computer.NumberOfLogicalProcessors)"
Write-Host "Physical CPUs: $($computer.NumberOfProcessors)"
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/[email protected]
with:
targets: ${{ matrix.target }}
- name: Cargo build (Windows binaries)
shell: bash
run: |
cargo build --target ${{ matrix.target }} --release --timings ${{ matrix.build_args }}
- name: Upload Cargo timings
uses: actions/upload-artifact@v6
with:
name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }}
path: codex-rs/target/**/cargo-timings/cargo-timing.html
if-no-files-found: warn
- name: Stage Windows binaries
shell: bash
run: |
output_dir="target/${{ matrix.target }}/release/staged-${{ matrix.bundle }}"
mkdir -p "$output_dir"
if [[ "${{ matrix.bundle }}" == "primary" ]]; then
cp target/${{ matrix.target }}/release/codex.exe "$output_dir/codex.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$output_dir/codex-responses-api-proxy.exe"
else
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$output_dir/codex-windows-sandbox-setup.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$output_dir/codex-command-runner.exe"
fi
- name: Upload Windows binaries
uses: actions/upload-artifact@v6
with:
name: windows-binaries-${{ matrix.target }}-${{ matrix.bundle }}
path: |
codex-rs/target/${{ matrix.target }}/release/staged-${{ matrix.bundle }}/*
build-windows:
needs:
- build-windows-binaries
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 60
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: codex-rs
strategy:
fail-fast: false
matrix:
include:
- runner: windows-x64
target: x86_64-pc-windows-msvc
runs_on:
group: codex-runners
labels: codex-windows-x64
- runner: windows-arm64
target: aarch64-pc-windows-msvc
runs_on:
group: codex-runners
labels: codex-windows-arm64
steps:
- uses: actions/checkout@v6
- name: Download prebuilt Windows primary binaries
uses: actions/download-artifact@v7
with:
name: windows-binaries-${{ matrix.target }}-primary
path: codex-rs/target/${{ matrix.target }}/release
- name: Download prebuilt Windows helper binaries
uses: actions/download-artifact@v7
with:
name: windows-binaries-${{ matrix.target }}-helpers
path: codex-rs/target/${{ matrix.target }}/release
- name: Verify binaries
shell: bash
run: |
set -euo pipefail
ls -lh target/${{ matrix.target }}/release/codex.exe
ls -lh target/${{ matrix.target }}/release/codex-responses-api-proxy.exe
ls -lh target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe
ls -lh target/${{ matrix.target }}/release/codex-command-runner.exe
- name: Sign Windows binaries with Azure Trusted Signing
uses: ./.github/actions/windows-code-sign
with:
target: ${{ matrix.target }}
client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID }}
endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
- name: Stage artifacts
shell: bash
run: |
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Compress artifacts
shell: bash
run: |
# Path that contains the uncompressed binaries for the current
# ${{ matrix.target }}
dest="dist/${{ matrix.target }}"
repo_root=$PWD
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` and `.zip` for every Windows binary.
# The end result is:
# codex-<target>.zst
# codex-<target>.tar.gz
# codex-<target>.zip
for f in "$dest"/*; do
base="$(basename "$f")"
# Skip files that are already archives (shouldn't happen, but be
# safe).
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
# Don't try to compress signature bundles.
if [[ "$base" == *.sigstore ]]; then
continue
fi
# Create per-binary tar.gz
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
# Create zip archive for Windows binaries.
# Must run from inside the dest dir so 7z won't embed the
# directory path inside the zip.
if [[ "$base" == "codex-${{ matrix.target }}.exe" ]]; then
# Bundle the sandbox helper binaries into the main codex zip so
# WinGet installs include the required helpers next to codex.exe.
# Fall back to the single-binary zip if the helpers are missing
# to avoid breaking releases.
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${{ matrix.target }}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
# Use an absolute path so bundle zips land in the real dist
# dir even when 7z runs from a temp directory.
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
# Keep raw executables and produce .zst alongside them.
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
done
- uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: |
codex-rs/dist/${{ matrix.target }}/*
+60 -102
View File
@@ -48,7 +48,7 @@ jobs:
build:
needs: tag-check
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
timeout-minutes: 60
permissions:
contents: read
@@ -57,7 +57,6 @@ jobs:
run:
working-directory: codex-rs
env:
CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }}
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
strategy:
@@ -76,13 +75,38 @@ jobs:
target: aarch64-unknown-linux-musl
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runner: windows-latest
target: x86_64-pc-windows-msvc
- runner: windows-11-arm
target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v6
- name: Print runner specs (Linux)
if: ${{ runner.os == 'Linux' }}
shell: bash
run: |
set -euo pipefail
cpu_model="$(lscpu | awk -F: '/Model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}')"
total_ram="$(awk '/MemTotal/ {printf "%.1f GiB\n", $2 / 1024 / 1024}' /proc/meminfo)"
echo "Runner: ${RUNNER_NAME:-unknown}"
echo "OS: $(uname -a)"
echo "CPU model: ${cpu_model}"
echo "Logical CPUs: $(nproc)"
echo "Total RAM: ${total_ram}"
echo "Disk usage:"
df -h .
- name: Print runner specs (macOS)
if: ${{ runner.os == 'macOS' }}
shell: bash
run: |
set -euo pipefail
total_ram="$(sysctl -n hw.memsize | awk '{printf "%.1f GiB\n", $1 / 1024 / 1024 / 1024}')"
echo "Runner: ${RUNNER_NAME:-unknown}"
echo "OS: $(sw_vers -productName) $(sw_vers -productVersion)"
echo "Hardware model: $(sysctl -n hw.model)"
echo "CPU architecture: $(uname -m)"
echo "Logical CPUs: $(sysctl -n hw.logicalcpu)"
echo "Physical CPUs: $(sysctl -n hw.physicalcpu)"
echo "Total RAM: ${total_ram}"
echo "Disk usage:"
df -h .
- name: Install Linux bwrap build dependencies
if: ${{ runner.os == 'Linux' }}
shell: bash
@@ -99,7 +123,7 @@ jobs:
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
fi
- uses: dtolnay/[email protected]
- uses: dtolnay/[email protected].0
with:
targets: ${{ matrix.target }}
@@ -114,20 +138,6 @@ jobs:
echo "${cargo_home}/bin" >> "$GITHUB_PATH"
: > "${cargo_home}/config.toml"
- uses: actions/cache@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/
${{ github.workspace }}/codex-rs/target/
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
name: Install Zig
uses: mlugg/setup-zig@v2
@@ -195,11 +205,14 @@ jobs:
- name: Cargo build
shell: bash
run: |
if [[ "${{ contains(matrix.target, 'windows') }}" == 'true' ]]; then
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy --bin codex-windows-sandbox-setup --bin codex-command-runner
else
cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy
fi
cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy
- name: Upload Cargo timings
uses: actions/upload-artifact@v6
with:
name: cargo-timings-rust-release-${{ matrix.target }}
path: codex-rs/target/**/cargo-timings/cargo-timing.html
if-no-files-found: warn
- if: ${{ contains(matrix.target, 'linux') }}
name: Cosign Linux artifacts
@@ -208,18 +221,6 @@ jobs:
target: ${{ matrix.target }}
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
- if: ${{ contains(matrix.target, 'windows') }}
name: Sign Windows binaries with Azure Trusted Signing
uses: ./.github/actions/windows-code-sign
with:
target: ${{ matrix.target }}
client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID }}
endpoint: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
- if: ${{ runner.os == 'macOS' }}
name: MacOS code signing (binaries)
uses: ./.github/actions/macos-code-sign
@@ -298,15 +299,8 @@ jobs:
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
if [[ "${{ matrix.runner }}" == windows* ]]; then
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
else
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}"
fi
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}"
if [[ "${{ matrix.target }}" == *linux* ]]; then
cp target/${{ matrix.target }}/release/codex.sigstore "$dest/codex-${{ matrix.target }}.sigstore"
@@ -317,34 +311,18 @@ jobs:
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
fi
- if: ${{ matrix.runner == 'windows-11-arm' }}
name: Install zstd
shell: powershell
run: choco install -y zstandard
- name: Compress artifacts
shell: bash
run: |
# Path that contains the uncompressed binaries for the current
# ${{ matrix.target }}
dest="dist/${{ matrix.target }}"
repo_root=$PWD
# We want to ship the raw Windows executables in the GitHub Release
# in addition to the compressed archives. Keep the originals for
# Windows targets; remove them elsewhere to limit the number of
# artifacts that end up in the GitHub Release.
keep_originals=false
if [[ "${{ matrix.runner }}" == windows* ]]; then
keep_originals=true
fi
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` for all platforms and `.zip` for
# Windows alongside every single binary that we publish. The end result is:
# additionally create a `.tar.gz` alongside every binary we publish.
# The end result is:
# codex-<target>.zst (existing)
# codex-<target>.tar.gz (new)
# codex-<target>.zip (only for Windows)
# 1. Produce a .tar.gz for every file in the directory *before* we
# run `zstd --rm`, because that flag deletes the original files.
@@ -364,43 +342,9 @@ jobs:
# Create per-binary tar.gz
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
# Create zip archive for Windows binaries
# Must run from inside the dest dir so 7z won't
# embed the directory path inside the zip.
if [[ "${{ matrix.runner }}" == windows* ]]; then
if [[ "$base" == "codex-${{ matrix.target }}.exe" ]]; then
# Bundle the sandbox helper binaries into the main codex zip so
# WinGet installs include the required helpers next to codex.exe.
# Fall back to the single-binary zip if the helpers are missing
# to avoid breaking releases.
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${{ matrix.target }}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
# Use an absolute path so bundle zips land in the real dist
# dir even when 7z runs from a temp directory.
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
fi
# Also create .zst (existing behaviour) *and* remove the original
# uncompressed binary to keep the directory small.
zstd_args=(-T0 -19)
if [[ "${keep_originals}" == false ]]; then
zstd_args+=(--rm)
fi
zstd "${zstd_args[@]}" "$dest/$base"
# Also create .zst and remove the uncompressed binaries to keep
# non-Windows artifact directories small.
zstd -T0 -19 --rm "$dest/$base"
done
- uses: actions/upload-artifact@v6
@@ -411,6 +355,13 @@ jobs:
path: |
codex-rs/dist/${{ matrix.target }}/*
build-windows:
needs: tag-check
uses: ./.github/workflows/rust-release-windows.yml
with:
release-lto: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
secrets: inherit
shell-tool-mcp:
name: shell-tool-mcp
needs: tag-check
@@ -423,6 +374,7 @@ jobs:
release:
needs:
- build
- build-windows
- shell-tool-mcp
name: release
runs-on: ubuntu-latest
@@ -471,6 +423,12 @@ jobs:
- 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
# asset names and can fail with 404s.
find dist -type f -name 'cargo-timing.html' -delete
find dist -type d -empty -delete
ls -R dist/
+50
View File
@@ -0,0 +1,50 @@
name: sdk
on:
push:
branches: [main]
pull_request: {}
jobs:
sdks:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Linux bwrap build dependencies
shell: bash
run: |
set -euo pipefail
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
- uses: dtolnay/[email protected]
- name: build codex
run: cargo build --bin codex
working-directory: codex-rs
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build SDK packages
run: pnpm -r --filter ./sdk/typescript run build
- name: Lint SDK packages
run: pnpm -r --filter ./sdk/typescript run lint
- name: Test SDK packages
run: pnpm -r --filter ./sdk/typescript run test
+214 -5
View File
@@ -105,7 +105,7 @@ jobs:
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
fi
- uses: dtolnay/[email protected]
- uses: dtolnay/[email protected].0
with:
targets: ${{ matrix.target }}
@@ -251,11 +251,11 @@ jobs:
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
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
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
yum install -y git gcc gcc-c++ make bison autoconf gettext ncurses-devel
else
echo "Unsupported package manager in container"
exit 1
@@ -329,6 +329,212 @@ jobs:
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 --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
cd /tmp/zsh
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
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@v6
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 --depth 1 https://git.code.sf.net/p/zsh/code /tmp/zsh
cd /tmp/zsh
git fetch --depth 1 origin 77045ef899e53b9598bebc5a41db93a548a40ca6
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@v6
with:
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
path: artifacts/**
if-no-files-found: error
package:
name: Package npm module
needs:
@@ -336,6 +542,8 @@ jobs:
- rust-binaries
- bash-linux
- bash-darwin
- zsh-linux
- zsh-darwin
runs-on: ubuntu-latest
env:
PACKAGE_VERSION: ${{ needs.metadata.outputs.version }}
@@ -409,7 +617,8 @@ jobs:
chmod +x \
"$staging"/vendor/*/codex-exec-mcp-server \
"$staging"/vendor/*/codex-execve-wrapper \
"$staging"/vendor/*/bash/*/bash
"$staging"/vendor/*/bash/*/bash \
"$staging"/vendor/*/zsh/*/zsh
- name: Create npm tarball
shell: bash
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env dotslash
// This DotSlash file wraps zstd for Windows runners.
// The upstream release provides win32/win64 binaries; for windows-aarch64 we
// use the win64 artifact via Windows x64 emulation.
{
"name": "zstd",
"platforms": {
"windows-x86_64": {
"size": 1747181,
"hash": "sha256",
"digest": "acb4e8111511749dc7a3ebedca9b04190e37a17afeb73f55d4425dbf0b90fad9",
"format": "zip",
"path": "zstd-v1.5.7-win64/zstd.exe",
"providers": [
{
"url": "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-v1.5.7-win64.zip"
},
{
"type": "github-release",
"repo": "facebook/zstd",
"tag": "v1.5.7",
"name": "zstd-v1.5.7-win64.zip"
}
]
},
"windows-aarch64": {
"size": 1747181,
"hash": "sha256",
"digest": "acb4e8111511749dc7a3ebedca9b04190e37a17afeb73f55d4425dbf0b90fad9",
"format": "zip",
"path": "zstd-v1.5.7-win64/zstd.exe",
"providers": [
{
"url": "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-v1.5.7-win64.zip"
},
{
"type": "github-release",
"repo": "facebook/zstd",
"tag": "v1.5.7",
"name": "zstd-v1.5.7-win64.zip"
}
]
}
}
}
+132 -108
View File
@@ -1,145 +1,169 @@
# Rust/hanzo-dev
# Rust/codex-rs
The primary Rust workspace lives in `hanzo-dev/`:
In the codex-rs folder where the rust code lives:
- Crate names are prefixed with `hanzo-`. For example, the `core` folder's crate is named `hanzo-core`
- The CLI binary is named `dev` (crate: `hanzo-dev`)
- Crate names are prefixed with `codex-`. For example, the `core` folder's crate is named `codex-core`
- When using format! and you can inline variables into {}, always do that.
- Treat `codex-rs` as a read-only mirror of `openai/codex:main`; edit Rust sources under `hanzo-dev/` instead.
- Install any commands the repo relies on (for example `just`, `rg`, or `cargo-insta`) if they aren't already available before running instructions here.
- Never add or modify any code related to `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` or `CODEX_SANDBOX_ENV_VAR`.
- You operate in a sandbox where `CODEX_SANDBOX_NETWORK_DISABLED=1` will be set whenever you use the `shell` tool. Any existing code that uses `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` was authored with this fact in mind. It is often used to early exit out of tests that the author knew you would not be able to run given your sandbox limitations.
- Similarly, when you spawn a process using Seatbelt (`/usr/bin/sandbox-exec`), `CODEX_SANDBOX=seatbelt` will be set on the child process. Integration tests that want to run Seatbelt themselves cannot be run under Seatbelt, so checks for `CODEX_SANDBOX=seatbelt` are also often used to early exit out of tests, as appropriate.
- Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
- Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args
- Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
- When possible, make `match` statements exhaustive and avoid wildcard arms.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`.
- Do not create small helper methods that are referenced only once.
Completion/build step
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:
- Always validate using `./build-fast.sh` from the repo root. This is the single required check and must pass cleanly.
- `./build-fast.sh` can take 20+min to run from a cold cache!!! Please use long timeout when running `./build-fast.sh` or waiting for it to complete.
- Policy: All errors AND all warnings must be fixed before you’re done. Treat any compiler warning as a failure and address it (rename unused vars with `_`, remove `mut`, delete dead code, etc.).
- Do not run additional format/lint/test commands on completion (e.g., `just fmt`, `just fix`, `cargo test`) unless explicitly requested for a specific task.
- ***NEVER run rustfmt***
- Before pushing to `main`, run `./pre-release.sh` to mirror the release preflight (dev-fast build, CLI smokes, workspace nextest).
1. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `cargo test -p codex-tui`.
2. Once those pass, if any changes were made in common, core, or protocol, run the complete test suite with `cargo test --all-features`. project-specific or individual tests can be run without asking the user, but do ask the user before running the complete test suite.
Optional regression checks (recommended when touching the Rust workspace):
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`.
- `cargo nextest run --no-fail-fast` — runs all workspace tests with the TUI helpers automatically enabled. The suite is green after the resume fixtures/git-init fallback updates; older Git builds may print a warning when falling back from `--initial-branch`, but tests still pass.
- Focused sweeps stay quick and green: `cargo test -p hanzo-tui --features test-helpers`, `cargo test -p hanzo-cloud-tasks --tests`, and `cargo test -p mcp-types --tests`.
## TUI style conventions
When debugging regressions or bugs, write a failing test (or targeted reproduction script) first and confirm it captures the issue before touching code—if it can’t fail, you can’t be confident the fix works.
See `codex-rs/tui/styles.md`.
## Documentation hygiene
## TUI code conventions
- Keep docs clean, clear, and current; prune stale instructions instead of piling on caveats.
- Avoid excessive verbosity; prioritize concise guidance over long narratives.
- Do not document minor or non-core features; focus on system-critical flows and expectations.
- Never commit temporary "working" docs, plans, or scratch notes.
- 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.
- Prefer these over constructing styles with `Span::styled` and `Style` directly.
- Example: patch summary file lines
- Desired: vec![" └ ".into(), "M".red(), " ".dim(), "tui/src/app.rs".dim()]
## Strict Ordering In The TUI History
### TUI Styling (ratatui)
The TUI enforces strict, per‑turn ordering for all streamed content. Every
stream insert (Answer or Reasoning) must be associated with a stable
`(request_ordinal, output_index, sequence_number)` key provided by the model.
- Prefer Stylize helpers: use "text".dim(), .bold(), .cyan(), .italic(), .underlined() instead of manual Style where possible.
- Prefer simple conversions: use "text".into() for spans and vec![…].into() for lines; when inference is ambiguous (e.g., Paragraph::new/Cell::from), use Line::from(spans) or Span::from(text).
- Computed styles: if the Style is computed at runtime, using `Span::styled` is OK (`Span::from(text).set_style(style)` is also acceptable).
- Avoid hardcoded white: do not use `.white()`; prefer the default foreground (no color).
- Chaining: combine helpers by chaining for readability (e.g., url.cyan().underlined()).
- Single items: prefer "text".into(); use Line::from(text) or Span::from(text) only when the target type isn’t obvious from context, or when using .into() would require extra type annotations.
- Building lines: use vec![…].into() to construct a Line when the target type is obvious and no extra type annotations are needed; otherwise use Line::from(vec![…]).
- Avoid churn: don’t refactor between equivalent forms (Span::styled ↔ set_style, Line::from ↔ .into()) without a clear readability or functional gain; follow file‑local conventions and do not introduce type annotations solely to satisfy .into().
- Compactness: prefer the form that stays on one line after rustfmt; if only one of Line::from(vec![…]) or vec![…].into() avoids wrapping, choose that. If both wrap, pick the one with fewer wrapped lines.
- A stream insert MUST carry a non‑empty stream id. The UI seeds an order key
for `(kind, id)` from the event's `OrderMeta` before any insert.
- The TUI WILL NOT insert streaming content without a stream id. Any attempt to
insert without an id is dropped with an error log to make the issue visible
during development.
### Text wrapping
## Commit Messages
- Always use textwrap::wrap to wrap plain strings.
- If you have a ratatui Line and you want to wrap it, use the helpers in tui/src/wrapping.rs, e.g. word_wrap_lines / word_wrap_line.
- If you need to indent wrapped lines, use the initial_indent / subsequent_indent options from RtOptions if you can, rather than writing custom logic.
- If you have a list of lines and you need to prefix them all with some prefix (optionally different on the first vs subsequent lines), use the `prefix_lines` helper from line_utils.
- Review staged changes before every commit: `git --no-pager diff --staged --stat` (and skim `git --no-pager diff --staged` if needed).
- Write a descriptive subject that explains what changed and why. Avoid placeholders like "chore: commit local work".
- Prefer Conventional Commits with an optional scope: `feat(tui/history): …`, `fix(core/exec): …`, `docs(agents): …`.
- Keep the subject ≤ 72 chars; add a short body if rationale or context helps future readers.
- Use imperative, present tense: "add", "fix", "update" (not "added", "fixes").
- For merge commits, skip custom prefixes like `merge(main<-origin/main):`. Use a clear subject such as `Merge origin/main: <what changed and how conflicts were resolved>`.
## Tests
Examples:
### Snapshot tests
- `feat(tui/history): show exit code and duration for Exec cells`
- `fix(core/codex): handle SIGINT in on_exec_command_begin to avoid orphaned child`
- `docs(agents): clarify commit-message expectations`
This repo uses snapshot tests (via `insta`), especially in `codex-rs/tui`, to validate rendered output.
## How to Git Push
**Requirement:** any change that affects user-visible UI (including adding new UI) must include
corresponding `insta` snapshot coverage (add a new snapshot test if one doesn't exist yet, or
update the existing snapshot). Review and accept snapshot updates as part of the PR so UI impact
is easy to review and future diffs stay visual.
### Merge-and-Push Policy (Do Not Rebase)
When UI or text output changes intentionally, update the snapshots as follows:
When the user asks you to "push" local work:
- Run tests to generate any updated snapshots:
- `cargo test -p codex-tui`
- Check what’s pending:
- `cargo insta pending-snapshots -p codex-tui`
- Review changes by reading the generated `*.snap.new` files directly in the repo, or preview a specific file:
- `cargo insta show -p codex-tui path/to/file.snap.new`
- Only if you intend to accept all new snapshots in this crate, run:
- `cargo insta accept -p codex-tui`
- Never rebase in this flow. Do not use `git pull --rebase` or attempt to replay local commits.
- Prefer a simple merge of `origin/main` into the current branch, keeping our local history intact.
- If the remote only has trivial release metadata changes (e.g., `codex-cli/package.json` version bumps), adopt the remote version for those files and keep ours for everything else unless the user specifies otherwise.
- If in doubt or if conflicts touch non-trivial areas, pause and ask before resolving.
If you don’t have the tool:
Quick procedure (merge-only):
- `cargo install cargo-insta`
- Commit your local work first:
- Review: `git --no-pager diff --stat` and `git --no-pager diff`
- Stage + commit: `git add -A && git commit -m "<descriptive message of local changes>"`
- Fetch remote: `git fetch origin`
- Merge without auto-commit: `git merge --no-ff --no-commit origin/main` (stops before committing so you can choose sides)
- Resolve policy:
- Default to ours: `git checkout --ours .`
- Take remote for trivial package/version files as needed, e.g.: `git checkout --theirs codex-cli/package.json`
- Stage and commit the merge with a descriptive message, e.g.:
- `git add -A && git commit -m "Merge origin/main: adopt remote version bumps; keep ours elsewhere (<areas>)"`
- Run `./build-fast.sh` and then `git push`
### Test assertions
## Command Execution Architecture
- Tests should use pretty_assertions::assert_eq for clearer diffs. Import this at the top of the test module if it isn't already.
- Prefer deep equals comparisons whenever possible. Perform `assert_eq!()` on entire objects, rather than individual fields.
- Avoid mutating process environment in tests; prefer passing environment-derived flags or dependencies from above.
The command execution flow in Codex follows an event-driven pattern:
### Spawning workspace binaries in tests (Cargo vs Bazel)
1. **Core Layer** (`codex-core/src/codex.rs`):
- `on_exec_command_begin()` initiates command execution
- Creates `EventMsg::ExecCommandBegin` events with command details
- Prefer `codex_utils_cargo_bin::cargo_bin("...")` over `assert_cmd::Command::cargo_bin(...)` or `escargot` when tests need to spawn first-party binaries.
- Under Bazel, binaries and resources may live under runfiles; use `codex_utils_cargo_bin::cargo_bin` to resolve absolute paths that remain stable after `chdir`.
- When locating fixture files or test resources under Bazel, avoid `env!("CARGO_MANIFEST_DIR")`. Prefer `codex_utils_cargo_bin::find_resource!` so paths resolve correctly under both Cargo and Bazel runfiles.
2. **TUI Layer** (`codex-tui/src/chatwidget.rs`):
- `handle_codex_event()` processes execution events
- Manages `RunningCommand` state for active commands
- Creates `HistoryCell::Exec` for UI rendering
### Integration tests (core)
3. **History Cell** (`codex-tui/src/history_cell.rs`):
- `new_active_exec_command()` - Creates cell for running command
- `new_completed_exec_command()` - Updates with final output
- Handles syntax highlighting via `ParsedCommand`
- Prefer the utilities in `core_test_support::responses` when writing end-to-end Codex tests.
This architecture separates concerns between execution logic (core), UI state management (chatwidget), and rendering (history_cell).
- All `mount_sse*` helpers return a `ResponseMock`; hold onto it so you can assert against outbound `/responses` POST bodies.
- Use `ResponseMock::single_request()` when a test should only issue one POST, or `ResponseMock::requests()` to inspect every captured `ResponsesRequest`.
- `ResponsesRequest` exposes helpers (`body_json`, `input`, `function_call_output`, `custom_tool_call_output`, `call_output`, `header`, `path`, `query_param`) so assertions can target structured payloads instead of manual JSON digging.
- Build SSE payloads with the provided `ev_*` constructors and the `sse(...)`.
- Prefer `wait_for_event` over `wait_for_event_with_timeout`.
- Prefer `mount_sse_once` over `mount_sse_once_match` or `mount_sse_sequence`
### Auto Drive Escape Handling
- Typical pattern:
- All Auto Drive escape routing lives in `hanzo-dev/tui/src/chatwidget.rs`. The
`ChatWidget::auto_should_handle_global_esc` helper decides whether the global
Esc handler in `app.rs` should defer to Auto Drive, and
`ChatWidget::handle_key_event` owns the actual stop / pause behaviour. When
you need to tweak Esc semantics, update those two locations together.
- The approval pane must *never* swallow Esc. `hanzo-dev/tui/src/bottom_pane/auto_coordinator_view.rs`
intentionally lets Esc (and the other approval shortcuts) bubble back to the
chat widget; keep this contract intact when editing the view layer.
- Avoid adding additional Esc handlers elsewhere for Auto Drive flows. Doing
so breaks the modal-first ordering in `app.rs` and prevents users from
reliably stopping a run.
```rust
let mock = responses::mount_sse_once(&server, responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "shell", &serde_json::to_string(&args)?),
responses::ev_completed("resp-1"),
])).await;
## Writing New UI Regression Tests
codex.submit(Op::UserTurn { ... }).await?;
- Start with `make_chatwidget_manual()` (or `make_chatwidget_manual_with_sender()`) to build a `ChatWidget` in isolation with in-memory channels.
- Simulate user input by defining a small enum (`ScriptStep`) and feeding key events via `chat.handle_key_event()`; see `run_script()` in `tests.rs` for a ready-to-use helper that also pumps `AppEvent`s.
- After the scripted interaction, render with a `ratatui::Terminal`/`TestBackend`, then use `buffer_to_string()` (wraps `strip_ansi_escapes`) to normalize ANSI output before asserting.
- Prefer snapshot assertions (`assert_snapshot!`) or rich string comparisons so UI regressions are obvious. Keep snapshots deterministic by trimming trailing space and driving commit ticks just like the existing tests do.
- When adding fixtures or updating snapshots, gate rewrites behind an opt-in env var (e.g., `UPDATE_IDEAL=1`) so baseline refreshes remain explicit.
// Assert request body if needed.
let request = mock.single_request();
// assert using request.function_call_output(call_id) or request.json_body() or other helpers.
```
## VT100 Snapshot Harness
## App-server API Development Best Practices
- The VT100 harness lives under `hanzo-dev/tui/tests/vt100_chatwidget_snapshot.rs`. It renders the live `ChatWidget` UI into a `Terminal<VT100Backend>` so snapshots capture the exact PTY output the user sees (including frame chrome, composer rows, and streaming inserts).
- Use `ChatWidgetHarness` helpers from `code_tui::test_helpers` to seed history events and drain `AppEvent`s. Call `render_chat_widget_to_vt100(width, height)` for a single frame, or `render_chat_widget_frames_to_vt100(&[(w,h), ...])` to simulate successive draws while streaming.
- The harness now exports `layout_metrics()` so tests can assert scroll offsets and viewport heights without spelunking through private fields.
- Snapshots are deterministic: tests set `CODEX_TUI_FAKE_HOUR=12` automatically so greeting text (“What can I code for you today?”) doesn’t oscillate. If you need a different hour in a test, override the env var before constructing the harness.
- To add a new scenario, push history/events onto the harness, call `render_*_to_vt100`, and either `insta::assert_snapshot!` the frame(s) or manually assert string contents. For multi-frame streaming, push deltas/events first, then capture frames in the order the UI would display them.
- Run all VT100 snapshots via:
- `cargo test -p code-tui --test vt100_chatwidget_snapshot --features test-helpers -- --nocapture`
- When you intentionally change rendering, review the `.snap.new` files that appear in `hanzo-dev/tui/tests/snapshots/` and accept them with `cargo insta review` / `cargo insta accept` (limit to this test where possible).
These guidelines apply to app-server protocol work in `codex-rs`, especially:
### Monitor Release Workflows After Pushing
- `app-server-protocol/src/protocol/common.rs`
- `app-server-protocol/src/protocol/v2.rs`
- `app-server/README.md`
- Use `scripts/wait-for-gh-run.sh` to follow GitHub Actions releases without spamming manual `gh` commands.
- Typical release check right after a push: `scripts/wait-for-gh-run.sh --workflow Release --branch main`.
- If you already know the run ID (e.g., from webhook output), run `scripts/wait-for-gh-run.sh --run <run-id>`.
- Adjust the poll cadence via `--interval <seconds>` (defaults to 8). The script exits 0 on success and 1 on failure, so it can gate local automation.
- Pass `--failure-logs` to automatically dump logs for any job that does not finish successfully.
- Dependencies: GitHub CLI (`gh`) and `jq` must be available in `PATH`.
### Core Rules
- All active API development should happen in app-server v2. Do not add new API surface area to v1.
- Follow payload naming consistently:
`*Params` for request payloads, `*Response` for responses, and `*Notification` for notifications.
- Expose RPC methods as `<resource>/<method>` and keep `<resource>` singular (for example, `thread/read`, `app/list`).
- Always expose fields as camelCase on the wire with `#[serde(rename_all = "camelCase")]` unless a tagged union or explicit compatibility requirement needs a targeted rename.
- Exception: config RPC payloads are expected to use snake_case to mirror config.toml keys (see the config read/write/list APIs in `app-server-protocol/src/protocol/v2.rs`).
- Always set `#[ts(export_to = "v2/")]` on v2 request/response/notification types so generated TypeScript lands in the correct namespace.
- Never use `#[serde(skip_serializing_if = "Option::is_none")]` for v2 API payload fields.
Exception: client->server requests that intentionally have no params may use:
`params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>`.
- Keep Rust and TS wire renames aligned. If a field or variant uses `#[serde(rename = "...")]`, add matching `#[ts(rename = "...")]`.
- For discriminated unions, use explicit tagging in both serializers:
`#[serde(tag = "type", ...)]` and `#[ts(tag = "type", ...)]`.
- Prefer plain `String` IDs at the API boundary (do UUID parsing/conversion internally if needed).
- Timestamps should be integer Unix seconds (`i64`) and named `*_at` (for example, `created_at`, `updated_at`, `resets_at`).
- For experimental API surface area:
use `#[experimental("method/or/field")]`, derive `ExperimentalApi` when field-level gating is needed, and use `inspect_params: true` in `common.rs` when only some fields of a method are experimental.
### Client->server request payloads (`*Params`)
- Every optional field must be annotated with `#[ts(optional = nullable)]`. Do not use `#[ts(optional = nullable)]` outside client->server request payloads (`*Params`).
- Optional collection fields (for example `Vec`, `HashMap`) must use `Option<...>` + `#[ts(optional = nullable)]`. Do not use `#[serde(default)]` to model optional collections, and do not use `skip_serializing_if` on v2 payload fields.
- When you want omission to mean `false` for boolean fields, use `#[serde(default, skip_serializing_if = "std::ops::Not::not")] pub field: bool` over `Option<bool>`.
- For new list methods, implement cursor pagination by default:
request fields `pub cursor: Option<String>` and `pub limit: Option<u32>`,
response fields `pub data: Vec<...>` and `pub next_cursor: Option<String>`.
### Development Workflow
- Update docs/examples when API behavior changes (at minimum `app-server/README.md`).
- Regenerate schema fixtures when API shapes change:
`just write-app-server-schema`
(and `just write-app-server-schema --experimental` when experimental API fixtures are affected).
- Validate with `cargo test -p codex-app-server-protocol`.
- Avoid boilerplate tests that only assert experimental field markers for individual
request fields in `common.rs`; rely on schema generation/tests and behavioral coverage instead.
+1
View File
@@ -96,6 +96,7 @@ crate.annotation(
inject_repo(crate, "zstd")
bazel_dep(name = "bzip2", version = "1.0.8.bcr.3")
bazel_dep(name = "libcap", version = "2.27.bcr.1")
crate.annotation(
crate = "bzip2-sys",
+8
View File
@@ -82,6 +82,8 @@
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0",
"https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000",
"https://bcr.bazel.build/modules/libcap/2.27.bcr.1/MODULE.bazel": "7c034d7a4d92b2293294934377f5d1cbc88119710a11079fa8142120f6f08768",
"https://bcr.bazel.build/modules/libcap/2.27.bcr.1/source.json": "3b116cbdbd25a68ffb587b672205f6d353a4c19a35452e480d58fc89531e0a10",
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
@@ -204,6 +206,8 @@
"https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd",
"https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400",
"https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66",
"https://bcr.bazel.build/modules/sed/4.9.bcr.3/MODULE.bazel": "3aca45895b85b6ef65366cc12a45217ba6870f8931d2d62e09c99c772d9736ab",
"https://bcr.bazel.build/modules/sed/4.9.bcr.3/source.json": "31c0cf4c135ed3fa58298cd7bcfd4301c54ea4cf59d7c4e2ea0a180ce68eb34f",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
"https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
@@ -608,6 +612,10 @@
"arrayvec_0.7.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.4\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"matches\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}",
"ascii-canvas_3.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"diff\",\"req\":\"^0.1\"},{\"name\":\"term\",\"req\":\"^0.7\"}],\"features\":{}}",
"ascii_1.1.0": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"name\":\"serde_test\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}",
"askama_0.15.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"askama_macros\",\"optional\":true,\"req\":\"=0.15.4\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"itoa\",\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[\"askama_macros?/alloc\",\"serde?/alloc\",\"serde_json?/alloc\",\"percent-encoding?/alloc\"],\"code-in-doc\":[\"askama_macros?/code-in-doc\"],\"config\":[\"askama_macros?/config\"],\"default\":[\"config\",\"derive\",\"std\",\"urlencode\"],\"derive\":[\"dep:askama_macros\",\"dep:askama_macros\"],\"full\":[\"default\",\"code-in-doc\",\"serde_json\"],\"nightly-spans\":[\"askama_macros/nightly-spans\"],\"serde_json\":[\"std\",\"askama_macros?/serde_json\",\"dep:serde\",\"dep:serde_json\"],\"std\":[\"alloc\",\"askama_macros?/std\",\"serde?/std\",\"serde_json?/std\",\"percent-encoding?/std\"],\"urlencode\":[\"askama_macros?/urlencode\",\"dep:percent-encoding\"]}}",
"askama_derive_0.15.4": "{\"dependencies\":[{\"name\":\"basic-toml\",\"optional\":true,\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"console\",\"req\":\"^0.16.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"memchr\",\"req\":\"^2\"},{\"name\":\"parser\",\"package\":\"askama_parser\",\"req\":\"=0.15.4\"},{\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2.20\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.13.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"rustc-hash\",\"req\":\"^2.0.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2.6.0\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"full\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.3\"}],\"features\":{\"alloc\":[],\"code-in-doc\":[\"dep:pulldown-cmark\"],\"config\":[\"external-sources\",\"dep:basic-toml\",\"dep:serde\",\"dep:serde_derive\",\"parser/config\"],\"default\":[\"alloc\",\"code-in-doc\",\"config\",\"external-sources\",\"proc-macro\",\"serde_json\",\"std\",\"urlencode\"],\"external-sources\":[],\"nightly-spans\":[],\"proc-macro\":[\"proc-macro2/proc-macro\"],\"serde_json\":[],\"std\":[\"alloc\"],\"urlencode\":[]}}",
"askama_macros_0.15.4": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"external-sources\",\"proc-macro\"],\"name\":\"askama_derive\",\"package\":\"askama_derive\",\"req\":\"=0.15.4\"}],\"features\":{\"alloc\":[\"askama_derive/alloc\"],\"code-in-doc\":[\"askama_derive/code-in-doc\"],\"config\":[\"askama_derive/config\"],\"default\":[\"config\",\"derive\",\"std\",\"urlencode\"],\"derive\":[],\"full\":[\"default\",\"code-in-doc\",\"serde_json\"],\"nightly-spans\":[\"askama_derive/nightly-spans\"],\"serde_json\":[\"askama_derive/serde_json\"],\"std\":[\"askama_derive/std\"],\"urlencode\":[\"askama_derive/urlencode\"]}}",
"askama_parser_0.15.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\"},{\"name\":\"rustc-hash\",\"req\":\"^2.0.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0.12\"},{\"features\":[\"simd\"],\"name\":\"winnow\",\"req\":\"^0.7.0\"}],\"features\":{\"config\":[\"dep:serde\",\"dep:serde_derive\"]}}",
"asn1-rs-derive_0.6.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"synstructure\",\"req\":\"^0.13\"}],\"features\":{}}",
"asn1-rs-impl_0.2.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}",
"asn1-rs_0.7.1": "{\"dependencies\":[{\"name\":\"asn1-rs-derive\",\"req\":\"^0.6\"},{\"name\":\"asn1-rs-impl\",\"req\":\"^0.2\"},{\"name\":\"bitvec\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"colored\",\"optional\":true,\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"colored\",\"req\":\"^3.0\"},{\"name\":\"cookie-factory\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"displaydoc\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7.0\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2.14\"},{\"kind\":\"dev\",\"name\":\"pem\",\"req\":\"^3.0\"},{\"name\":\"rusticata-macros\",\"req\":\"^4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.0\"},{\"features\":[\"macros\",\"parsing\",\"formatting\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"}],\"features\":{\"bigint\":[\"num-bigint\"],\"bits\":[\"bitvec\"],\"datetime\":[\"time\"],\"debug\":[\"std\",\"colored\"],\"default\":[\"std\"],\"serialize\":[\"cookie-factory\"],\"std\":[],\"trace\":[\"debug\"]}}",
+9
View File
@@ -0,0 +1,9 @@
OpenAI Codex
Copyright 2025 OpenAI
This project includes code derived from [Ratatui](https://github.com/ratatui/ratatui), licensed under the MIT license.
Copyright (c) 2016-2022 Florian Dehau
Copyright (c) 2023-2025 The Ratatui Developers
This project includes Meriyah parser assets from [meriyah](https://github.com/meriyah/meriyah), licensed under the ISC license.
Copyright (c) 2019 and later, KFlash and others.
+6 -8
View File
@@ -76,7 +76,7 @@ PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
"codex-win32-arm64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
"codex-responses-api-proxy": ["codex-responses-api-proxy"],
"codex-sdk": ["codex"],
"codex-sdk": [],
}
PACKAGE_TARGET_FILTERS: dict[str, str] = {
@@ -205,7 +205,6 @@ def main() -> int:
f"Staged version {version} for release in {staging_dir_str}\n\n"
"Verify the SDK contents:\n"
f" ls {staging_dir_str}/dist\n"
f" ls {staging_dir_str}/vendor\n"
" node -e \"import('./dist/index.js').then(() => console.log('ok'))\"\n\n"
)
else:
@@ -318,12 +317,11 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
if isinstance(scripts, dict):
scripts.pop("prepare", None)
files = package_json.get("files")
if isinstance(files, list):
if "vendor" not in files:
files.append("vendor")
else:
package_json["files"] = ["dist", "vendor"]
dependencies = package_json.get("dependencies")
if not isinstance(dependencies, dict):
dependencies = {}
dependencies[CODEX_NPM_NAME] = version
package_json["dependencies"] = dependencies
with open(staging_dir / "package.json", "w", encoding="utf-8") as out:
json.dump(package_json, out, indent=2)
+3 -1
View File
@@ -1 +1,3 @@
exports_files([
"node-version.txt",
])
+129 -21
View File
@@ -1348,12 +1348,12 @@ dependencies = [
"axum",
"base64 0.22.1",
"chrono",
"clap",
"codex-app-server-protocol",
"codex-arg0",
"codex-backend-client",
"codex-chatgpt",
"codex-cloud-requirements",
"codex-common",
"codex-core",
"codex-execpolicy",
"codex-feedback",
@@ -1362,9 +1362,13 @@ dependencies = [
"codex-protocol",
"codex-rmcp-client",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-json-to-toml",
"core_test_support",
"futures",
"os_info",
"owo-colors",
"pretty_assertions",
"rmcp",
"serde",
@@ -1374,6 +1378,7 @@ dependencies = [
"tempfile",
"time",
"tokio",
"tokio-tungstenite",
"toml 0.9.11+spec-1.1.0",
"tracing",
"tracing-subscriber",
@@ -1396,6 +1401,7 @@ dependencies = [
"schemars 0.8.22",
"serde",
"serde_json",
"shlex",
"similar",
"strum_macros 0.27.2",
"tempfile",
@@ -1414,6 +1420,8 @@ dependencies = [
"codex-protocol",
"serde",
"serde_json",
"tungstenite",
"url",
"uuid",
]
@@ -1485,10 +1493,10 @@ version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"codex-common",
"codex-core",
"codex-git",
"codex-utils-cargo-bin",
"codex-utils-cli",
"pretty_assertions",
"serde",
"serde_json",
@@ -1512,7 +1520,6 @@ dependencies = [
"codex-arg0",
"codex-chatgpt",
"codex-cloud-tasks",
"codex-common",
"codex-core",
"codex-exec",
"codex-execpolicy",
@@ -1524,6 +1531,7 @@ dependencies = [
"codex-stdio-to-uds",
"codex-tui",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-windows-sandbox",
"libc",
"owo-colors",
@@ -1567,13 +1575,18 @@ version = "0.0.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"chrono",
"codex-backend-client",
"codex-core",
"codex-otel",
"codex-protocol",
"hmac",
"pretty_assertions",
"serde",
"serde_json",
"sha2",
"tempfile",
"thiserror 2.0.18",
"tokio",
"toml 0.9.11+spec-1.1.0",
"tracing",
@@ -1589,10 +1602,10 @@ dependencies = [
"chrono",
"clap",
"codex-cloud-tasks-client",
"codex-common",
"codex-core",
"codex-login",
"codex-tui",
"codex-utils-cli",
"crossterm",
"owo-colors",
"pretty_assertions",
@@ -1624,17 +1637,22 @@ dependencies = [
]
[[package]]
name = "codex-common"
name = "codex-config"
version = "0.0.0"
dependencies = [
"clap",
"codex-core",
"codex-lmstudio",
"codex-ollama",
"anyhow",
"codex-app-server-protocol",
"codex-execpolicy",
"codex-protocol",
"codex-utils-absolute-path",
"futures",
"multimap",
"pretty_assertions",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.18",
"tokio",
"toml 0.9.11+spec-1.1.0",
]
@@ -1660,7 +1678,7 @@ dependencies = [
"codex-arg0",
"codex-async-utils",
"codex-client",
"codex-core",
"codex-config",
"codex-execpolicy",
"codex-file-search",
"codex-git",
@@ -1670,12 +1688,14 @@ dependencies = [
"codex-otel",
"codex-protocol",
"codex-rmcp-client",
"codex-shell-command",
"codex-state",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-home-dir",
"codex-utils-pty",
"codex-utils-readiness",
"codex-utils-sanitizer",
"codex-utils-string",
"codex-windows-sandbox",
"core-foundation 0.9.4",
@@ -1696,7 +1716,6 @@ dependencies = [
"landlock",
"libc",
"maplit",
"multimap",
"notify",
"once_cell",
"openssl-sys",
@@ -1705,7 +1724,6 @@ dependencies = [
"predicates",
"pretty_assertions",
"rand 0.9.2",
"regex",
"regex-lite",
"reqwest",
"rmcp",
@@ -1733,8 +1751,6 @@ dependencies = [
"tracing",
"tracing-subscriber",
"tracing-test",
"tree-sitter",
"tree-sitter-bash",
"url",
"uuid",
"walkdir",
@@ -1767,11 +1783,14 @@ dependencies = [
"clap",
"codex-arg0",
"codex-cloud-requirements",
"codex-common",
"codex-core",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-elapsed",
"codex-utils-oss",
"codex-utils-sandbox-summary",
"core_test_support",
"libc",
"owo-colors",
@@ -1992,9 +2011,9 @@ version = "0.0.0"
dependencies = [
"anyhow",
"codex-arg0",
"codex-common",
"codex-core",
"codex-protocol",
"codex-utils-cli",
"codex-utils-json-to-toml",
"core_test_support",
"mcp_test_support",
@@ -2018,6 +2037,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"base64 0.22.1",
"clap",
"codex-utils-absolute-path",
"codex-utils-rustls-provider",
@@ -2190,6 +2210,26 @@ dependencies = [
"tracing",
]
[[package]]
name = "codex-shell-command"
version = "0.0.0"
dependencies = [
"anyhow",
"base64 0.22.1",
"codex-protocol",
"codex-utils-absolute-path",
"once_cell",
"pretty_assertions",
"regex",
"serde",
"serde_json",
"shlex",
"tree-sitter",
"tree-sitter-bash",
"url",
"which",
]
[[package]]
name = "codex-state"
version = "0.0.0"
@@ -2241,7 +2281,6 @@ dependencies = [
"codex-chatgpt",
"codex-cli",
"codex-cloud-requirements",
"codex-common",
"codex-core",
"codex-feedback",
"codex-file-search",
@@ -2250,8 +2289,15 @@ dependencies = [
"codex-protocol",
"codex-state",
"codex-utils-absolute-path",
"codex-utils-approval-presets",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-elapsed",
"codex-utils-fuzzy-match",
"codex-utils-oss",
"codex-utils-pty",
"codex-utils-sandbox-summary",
"codex-utils-sleep-inhibitor",
"codex-windows-sandbox",
"color-eyre",
"crossterm",
@@ -2317,6 +2363,13 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "codex-utils-approval-presets"
version = "0.0.0"
dependencies = [
"codex-core",
]
[[package]]
name = "codex-utils-cache"
version = "0.0.0"
@@ -2335,6 +2388,26 @@ dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "codex-utils-cli"
version = "0.0.0"
dependencies = [
"clap",
"codex-core",
"codex-protocol",
"pretty_assertions",
"serde",
"toml 0.9.11+spec-1.1.0",
]
[[package]]
name = "codex-utils-elapsed"
version = "0.0.0"
[[package]]
name = "codex-utils-fuzzy-match"
version = "0.0.0"
[[package]]
name = "codex-utils-home-dir"
version = "0.0.0"
@@ -2365,6 +2438,15 @@ dependencies = [
"toml 0.9.11+spec-1.1.0",
]
[[package]]
name = "codex-utils-oss"
version = "0.0.0"
dependencies = [
"codex-core",
"codex-lmstudio",
"codex-ollama",
]
[[package]]
name = "codex-utils-pty"
version = "0.0.0"
@@ -2399,6 +2481,31 @@ dependencies = [
"rustls",
]
[[package]]
name = "codex-utils-sandbox-summary"
version = "0.0.0"
dependencies = [
"codex-core",
"codex-utils-absolute-path",
"pretty_assertions",
]
[[package]]
name = "codex-utils-sanitizer"
version = "0.0.0"
dependencies = [
"regex",
]
[[package]]
name = "codex-utils-sleep-inhibitor"
version = "0.0.0"
dependencies = [
"core-foundation 0.9.4",
"libc",
"tracing",
]
[[package]]
name = "codex-utils-string"
version = "0.0.0"
@@ -2620,6 +2727,7 @@ dependencies = [
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"ctor 0.6.3",
"futures",
"notify",
"pretty_assertions",
@@ -7308,9 +7416,9 @@ dependencies = [
[[package]]
name = "rmcp"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a621b37a548ff6ab6292d57841eb25785a7f146d89391a19c9f199414bd13da"
checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee"
dependencies = [
"async-trait",
"axum",
@@ -7344,9 +7452,9 @@ dependencies = [
[[package]]
name = "rmcp-macros"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b79ed92303f9262db79575aa8c3652581668e9d136be6fd0b9ededa78954c95"
checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe"
dependencies = [
"darling 0.23.0",
"proc-macro2",
+57 -19
View File
@@ -15,7 +15,8 @@ members = [
"cloud-tasks",
"cloud-tasks-client",
"cli",
"common",
"config",
"shell-command",
"core",
"hooks",
"secrets",
@@ -49,6 +50,14 @@ members = [
"utils/readiness",
"utils/rustls-provider",
"utils/string",
"utils/cli",
"utils/elapsed",
"utils/sandbox-summary",
"utils/sanitizer",
"utils/sleep-inhibitor",
"utils/approval-presets",
"utils/oss",
"utils/fuzzy-match",
"codex-client",
"codex-api",
"state",
@@ -77,20 +86,19 @@ codex-apply-patch = { path = "apply-patch" }
codex-arg0 = { path = "arg0" }
codex-async-utils = { path = "async-utils" }
codex-backend-client = { path = "backend-client" }
codex-cloud-requirements = { path = "cloud-requirements" }
codex-chatgpt = { path = "chatgpt" }
codex-cli = { path = "cli"}
codex-cli = { path = "cli" }
codex-client = { path = "codex-client" }
codex-common = { path = "common" }
codex-cloud-requirements = { path = "cloud-requirements" }
codex-config = { path = "config" }
codex-core = { path = "core" }
codex-hooks = { path = "hooks" }
codex-secrets = { path = "secrets" }
codex-exec = { path = "exec" }
codex-execpolicy = { path = "execpolicy" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-feedback = { path = "feedback" }
codex-file-search = { path = "file-search" }
codex-git = { path = "utils/git" }
codex-hooks = { path = "hooks" }
codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
codex-lmstudio = { path = "lmstudio" }
@@ -103,18 +111,28 @@ codex-process-hardening = { path = "process-hardening" }
codex-protocol = { path = "protocol" }
codex-responses-api-proxy = { path = "responses-api-proxy" }
codex-rmcp-client = { path = "rmcp-client" }
codex-secrets = { path = "secrets" }
codex-shell-command = { path = "shell-command" }
codex-state = { path = "state" }
codex-stdio-to-uds = { path = "stdio-to-uds" }
codex-tui = { path = "tui" }
codex-utils-absolute-path = { path = "utils/absolute-path" }
codex-utils-approval-presets = { path = "utils/approval-presets" }
codex-utils-cache = { path = "utils/cache" }
codex-utils-cargo-bin = { path = "utils/cargo-bin" }
codex-utils-cli = { path = "utils/cli" }
codex-utils-elapsed = { path = "utils/elapsed" }
codex-utils-fuzzy-match = { path = "utils/fuzzy-match" }
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-home-dir = { path = "utils/home-dir" }
codex-utils-oss = { path = "utils/oss" }
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-sanitizer = { path = "utils/sanitizer" }
codex-utils-sleep-inhibitor = { path = "utils/sleep-inhibitor" }
codex-utils-string = { path = "utils/string" }
codex-windows-sandbox = { path = "windows-sandbox-rs" }
core_test_support = { path = "core/tests/common" }
@@ -127,12 +145,12 @@ allocative = "0.3.3"
ansi-to-tui = "7.0.0"
anyhow = "1"
arboard = { version = "3", features = ["wayland-data-control"] }
askama = "0.15.4"
assert_cmd = "2"
assert_matches = "1.5.0"
async-channel = "2.3.1"
async-stream = "0.3.6"
async-trait = "0.1.89"
askama = "0.15.4"
axum = { version = "0.8", default-features = false }
base64 = "0.22.1"
bm25 = "2.3.2"
@@ -142,8 +160,8 @@ chrono = "0.4.43"
clap = "4"
clap_complete = "4"
color-eyre = "0.6.3"
crossterm = "0.28.1"
crossbeam-channel = "0.5.15"
crossterm = "0.28.1"
ctor = "0.6.3"
derive_more = "2"
diffy = "0.4.2"
@@ -161,10 +179,10 @@ icu_decimal = "2.1"
icu_locale_core = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
ignore = "0.4.23"
indoc = "2.0"
image = { version = "^0.25.9", default-features = false }
include_dir = "0.7.4"
indexmap = "2.12.0"
indoc = "2.0"
insta = "1.46.3"
inventory = "0.3.19"
itertools = "0.14.0"
@@ -186,7 +204,6 @@ opentelemetry-appender-tracing = "0.31.0"
opentelemetry-otlp = "0.31.0"
opentelemetry-semantic-conventions = "0.31.0"
opentelemetry_sdk = "0.31.0"
tracing-opentelemetry = "0.32.0"
os_info = "3.12.0"
owo-colors = "4.2.0"
path-absolutize = "3.1.1"
@@ -201,11 +218,15 @@ ratatui-macros = "0.6.0"
regex = "1.12.3"
regex-lite = "0.1.8"
reqwest = "0.12"
rmcp = { version = "0.14.0", default-features = false }
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
rmcp = { version = "0.15.0", default-features = false }
runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" }
rustls = { version = "0.23", default-features = false, features = [
"ring",
"std",
] }
schemars = "0.8.22"
seccompiler = "0.5.0"
semver = "1.0"
sentry = "0.46.0"
serde = "1"
serde_json = "1"
@@ -215,11 +236,19 @@ serde_yaml = "0.9"
serial_test = "3.2.0"
sha1 = "0.10.6"
sha2 = "0.10"
semver = "1.0"
shlex = "1.3.0"
similar = "2.7.0"
socket2 = "0.6.1"
sqlx = { version = "0.8.6", default-features = false, features = ["chrono", "json", "macros", "migrate", "runtime-tokio-rustls", "sqlite", "time", "uuid"] }
sqlx = { version = "0.8.6", default-features = false, features = [
"chrono",
"json",
"macros",
"migrate",
"runtime-tokio-rustls",
"sqlite",
"time",
"uuid",
] }
starlark = "0.13.0"
strum = "0.27.2"
strum_macros = "0.27.2"
@@ -234,20 +263,23 @@ tiny_http = "0.12"
tokio = "1"
tokio-stream = "0.1.18"
tokio-test = "0.4"
tokio-tungstenite = { version = "0.28.0", features = ["proxy", "rustls-tls-native-roots"] }
tungstenite = { version = "0.27.0", features = ["deflate", "proxy"] }
tokio-tungstenite = { version = "0.28.0", features = [
"proxy",
"rustls-tls-native-roots",
] }
tokio-util = "0.7.18"
toml = "0.9.5"
toml_edit = "0.24.0"
tracing = "0.1.44"
tracing-appender = "0.2.3"
tracing-opentelemetry = "0.32.0"
tracing-subscriber = "0.3.22"
tracing-test = "0.2.5"
tree-sitter = "0.25.10"
tree-sitter-bash = "0.25"
zstd = "0.13"
tree-sitter-highlight = "0.25.10"
ts-rs = "11"
tungstenite = { version = "0.27.0", features = ["deflate", "proxy"] }
uds_windows = "1.1.0"
unicode-segmentation = "1.12.0"
unicode-width = "0.2"
@@ -260,6 +292,7 @@ webbrowser = "1.0"
which = "8"
wildmatch = "2.6.1"
zip = "2.4.2"
zstd = "0.13"
wiremock = "0.6"
zeroize = "1.8.2"
@@ -305,7 +338,12 @@ unwrap_used = "deny"
# cargo-shear cannot see the platform-specific openssl-sys usage, so we
# silence the false positive here instead of deleting a real dependency.
[workspace.metadata.cargo-shear]
ignored = ["icu_provider", "openssl-sys", "codex-utils-readiness", "codex-secrets"]
ignored = [
"icu_provider",
"openssl-sys",
"codex-utils-readiness",
"codex-secrets",
]
[profile.release]
lto = "fat"
+1
View File
@@ -20,6 +20,7 @@ codex-utils-absolute-path = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
shlex = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
ts-rs = { workspace = true }
@@ -88,7 +88,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -1128,6 +1128,13 @@
"null"
]
},
"includeHidden": {
"description": "When true, include models that are hidden from the default picker list.",
"type": [
"boolean",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -1243,6 +1250,104 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReadOnlyAccess2": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccess2Type",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess2",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccess2Type",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess2",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -1922,6 +2027,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -1974,6 +2089,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -2018,8 +2143,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess2"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -2078,6 +2211,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess2"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -2431,6 +2572,13 @@
"null"
]
},
"cwd": {
"description": "Optional cwd filter; when set, only threads whose session cwd exactly matches this path are returned.",
"type": [
"string",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -104,7 +104,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -175,6 +175,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -184,35 +185,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -560,6 +532,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -569,6 +544,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -583,6 +559,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -592,6 +571,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -1369,6 +1349,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -1397,6 +1385,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -1825,6 +1814,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -1852,6 +1849,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -2129,6 +2127,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -2887,6 +2891,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
@@ -3414,6 +3426,14 @@
}
]
},
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
@@ -3456,6 +3476,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -3518,6 +3550,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -4355,8 +4438,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -4415,6 +4506,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -5303,6 +5402,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -5312,6 +5414,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -5326,6 +5429,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -5335,6 +5441,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -6112,6 +6219,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -6140,6 +6255,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -6568,6 +6684,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -6595,6 +6719,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -6872,6 +6997,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"sessionId": {
"type": "string"
}
},
"required": [
"sessionId"
],
"title": "FuzzyFileSearchSessionCompletedNotification",
"type": "object"
}
@@ -0,0 +1,63 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FuzzyFileSearchResult": {
"description": "Superset of [`codex_file_search::FileMatch`]",
"properties": {
"file_name": {
"type": "string"
},
"indices": {
"items": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"type": [
"array",
"null"
]
},
"path": {
"type": "string"
},
"root": {
"type": "string"
},
"score": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"file_name",
"path",
"root",
"score"
],
"type": "object"
}
},
"properties": {
"files": {
"items": {
"$ref": "#/definitions/FuzzyFileSearchResult"
},
"type": "array"
},
"query": {
"type": "string"
},
"sessionId": {
"type": "string"
}
},
"required": [
"files",
"query",
"sessionId"
],
"title": "FuzzyFileSearchSessionUpdatedNotification",
"type": "object"
}
@@ -193,6 +193,11 @@
"default": false,
"type": "boolean"
},
"isEnabled": {
"default": true,
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"logoUrl": {
"type": [
"string",
@@ -241,7 +246,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -373,6 +378,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -382,35 +388,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -515,6 +492,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -524,35 +502,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo2",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -1204,6 +1153,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -1213,6 +1165,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -1227,6 +1180,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -1236,6 +1192,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -2013,6 +1970,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -2041,6 +2006,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -2469,6 +2435,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus2"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -2496,6 +2470,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -2773,6 +2748,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -3531,6 +3512,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
@@ -3726,6 +3715,76 @@
],
"type": "object"
},
"FuzzyFileSearchResult": {
"description": "Superset of [`codex_file_search::FileMatch`]",
"properties": {
"file_name": {
"type": "string"
},
"indices": {
"items": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"type": [
"array",
"null"
]
},
"path": {
"type": "string"
},
"root": {
"type": "string"
},
"score": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"file_name",
"path",
"root",
"score"
],
"type": "object"
},
"FuzzyFileSearchSessionCompletedNotification": {
"properties": {
"sessionId": {
"type": "string"
}
},
"required": [
"sessionId"
],
"type": "object"
},
"FuzzyFileSearchSessionUpdatedNotification": {
"properties": {
"files": {
"items": {
"$ref": "#/definitions/FuzzyFileSearchResult"
},
"type": "array"
},
"query": {
"type": "string"
},
"sessionId": {
"type": "string"
}
},
"required": [
"files",
"query",
"sessionId"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
@@ -4268,6 +4327,14 @@
],
"type": "string"
},
"PatchApplyStatus2": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PatchChangeKind": {
"oneOf": [
{
@@ -4392,6 +4459,18 @@
}
]
},
"limitId": {
"type": [
"string",
"null"
]
},
"limitName": {
"type": [
"string",
"null"
]
},
"planType": {
"anyOf": [
{
@@ -4437,6 +4516,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -4544,6 +4635,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -5461,8 +5603,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -5521,6 +5671,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -5850,7 +6008,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -8122,6 +8281,46 @@
"title": "ConfigWarningNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"fuzzyFileSearch/sessionUpdated"
],
"title": "FuzzyFileSearch/sessionUpdatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FuzzyFileSearchSessionUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "FuzzyFileSearch/sessionUpdatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"fuzzyFileSearch/sessionCompleted"
],
"title": "FuzzyFileSearch/sessionCompletedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FuzzyFileSearchSessionCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "FuzzyFileSearch/sessionCompletedNotification",
"type": "object"
},
{
"description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.",
"properties": {
@@ -208,7 +208,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -1884,6 +1884,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -1893,35 +1894,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -2573,6 +2545,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -2582,6 +2557,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -2596,6 +2572,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -2605,6 +2584,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -3382,6 +3362,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -3410,6 +3398,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -3838,6 +3827,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/v2/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -3865,6 +3862,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -4142,6 +4140,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -4956,6 +4960,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOneOffCommandParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -5400,6 +5412,43 @@
],
"type": "object"
},
"FuzzyFileSearchSessionCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"sessionId": {
"type": "string"
}
},
"required": [
"sessionId"
],
"title": "FuzzyFileSearchSessionCompletedNotification",
"type": "object"
},
"FuzzyFileSearchSessionUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"files": {
"items": {
"$ref": "#/definitions/FuzzyFileSearchResult"
},
"type": "array"
},
"query": {
"type": "string"
},
"sessionId": {
"type": "string"
}
},
"required": [
"files",
"query",
"sessionId"
],
"title": "FuzzyFileSearchSessionUpdatedNotification",
"type": "object"
},
"GetAuthStatusParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -6436,6 +6485,14 @@
}
]
},
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
@@ -6541,6 +6598,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -6603,6 +6672,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -7616,8 +7736,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -7676,6 +7804,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -8360,6 +8496,46 @@
"title": "ConfigWarningNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"fuzzyFileSearch/sessionUpdated"
],
"title": "FuzzyFileSearch/sessionUpdatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FuzzyFileSearchSessionUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "FuzzyFileSearch/sessionUpdatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"fuzzyFileSearch/sessionCompleted"
],
"title": "FuzzyFileSearch/sessionCompletedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/FuzzyFileSearchSessionCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "FuzzyFileSearch/sessionCompletedNotification",
"type": "object"
},
{
"description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.",
"properties": {
@@ -8983,7 +9159,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -10058,6 +10235,11 @@
"default": false,
"type": "boolean"
},
"isEnabled": {
"default": true,
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"logoUrl": {
"type": [
"string",
@@ -10253,6 +10435,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -10262,35 +10445,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -11823,7 +11977,22 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"rateLimits": {
"$ref": "#/definitions/v2/RateLimitSnapshot"
"allOf": [
{
"$ref": "#/definitions/v2/RateLimitSnapshot"
}
],
"description": "Backward-compatible single-bucket view; mirrors the historical payload."
},
"rateLimitsByLimitId": {
"additionalProperties": {
"$ref": "#/definitions/v2/RateLimitSnapshot"
},
"description": "Multi-bucket view keyed by metered `limit_id` (for example, `codex`).",
"type": [
"object",
"null"
]
}
},
"required": [
@@ -12442,6 +12611,9 @@
"displayName": {
"type": "string"
},
"hidden": {
"type": "boolean"
},
"id": {
"type": "string"
},
@@ -12482,6 +12654,7 @@
"defaultReasoningEffort",
"description",
"displayName",
"hidden",
"id",
"isDefault",
"model",
@@ -12499,6 +12672,13 @@
"null"
]
},
"includeHidden": {
"description": "When true, include models that are hidden from the default picker list.",
"type": [
"boolean",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -12838,6 +13018,18 @@
}
]
},
"limitId": {
"type": [
"string",
"null"
]
},
"limitName": {
"type": [
"string",
"null"
]
},
"planType": {
"anyOf": [
{
@@ -12918,6 +13110,53 @@
"title": "RawResponseItemCompletedNotification",
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -13758,6 +13997,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/v2/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -13810,6 +14059,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/v2/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -14266,7 +14525,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -15120,6 +15380,13 @@
"null"
]
},
"cwd": {
"description": "Optional cwd filter; when set, only threads whose session cwd exactly matches this path are returned.",
"type": [
"string",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -13,6 +13,57 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"SandboxPolicy": {
"description": "Determines execution restrictions for model shell commands.",
"oneOf": [
@@ -34,8 +85,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -94,6 +153,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -12,7 +12,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -104,7 +104,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -175,6 +175,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -184,35 +185,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -560,6 +532,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -569,6 +544,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -583,6 +559,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -592,6 +571,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -1369,6 +1349,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -1397,6 +1385,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -1825,6 +1814,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -1852,6 +1849,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -2129,6 +2127,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -2887,6 +2891,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
@@ -3414,6 +3426,14 @@
}
]
},
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
@@ -3456,6 +3476,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -3518,6 +3550,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -4355,8 +4438,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -4415,6 +4506,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -113,7 +113,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -16,7 +16,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -113,7 +113,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -12,7 +12,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -12,7 +12,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -104,7 +104,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -175,6 +175,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -184,35 +185,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -560,6 +532,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -569,6 +544,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -583,6 +559,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -592,6 +571,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -1369,6 +1349,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -1397,6 +1385,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -1825,6 +1814,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -1852,6 +1849,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -2129,6 +2127,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -2887,6 +2891,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
@@ -3414,6 +3426,14 @@
}
]
},
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
@@ -3456,6 +3476,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -3518,6 +3550,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -4355,8 +4438,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -4415,6 +4506,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -16,7 +16,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -142,6 +142,57 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -195,8 +246,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -255,6 +314,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -104,7 +104,7 @@
"type": "string"
},
{
"description": "*All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox.",
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
@@ -175,6 +175,7 @@
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
@@ -184,35 +185,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"model_cap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"model_cap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -560,6 +532,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
@@ -569,6 +544,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
@@ -583,6 +559,9 @@
"null"
]
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
@@ -592,6 +571,7 @@
}
},
"required": [
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
@@ -1369,6 +1349,14 @@
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
@@ -1397,6 +1385,7 @@
"exit_code",
"formatted_output",
"parsed_cmd",
"status",
"stderr",
"stdout",
"turn_id",
@@ -1825,6 +1814,14 @@
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
@@ -1852,6 +1849,7 @@
},
"required": [
"call_id",
"status",
"stderr",
"stdout",
"success",
@@ -2129,6 +2127,12 @@
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
@@ -2887,6 +2891,14 @@
],
"type": "string"
},
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
@@ -3414,6 +3426,14 @@
}
]
},
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
@@ -3456,6 +3476,18 @@
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
@@ -3518,6 +3550,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -4355,8 +4438,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -4415,6 +4506,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
@@ -48,6 +48,18 @@
}
]
},
"limitId": {
"type": [
"string",
"null"
]
},
"limitName": {
"type": [
"string",
"null"
]
},
"planType": {
"anyOf": [
{
@@ -29,6 +29,11 @@
"default": false,
"type": "boolean"
},
"isEnabled": {
"default": true,
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"logoUrl": {
"type": [
"string",
@@ -29,6 +29,11 @@
"default": false,
"type": "boolean"
},
"isEnabled": {
"default": true,
"description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```",
"type": "boolean"
},
"logoUrl": {
"type": [
"string",
@@ -12,6 +12,53 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"SandboxPolicy": {
"oneOf": [
{
@@ -32,6 +79,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -84,6 +141,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -8,6 +8,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -17,35 +18,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -48,6 +48,18 @@
}
]
},
"limitId": {
"type": [
"string",
"null"
]
},
"limitName": {
"type": [
"string",
"null"
]
},
"planType": {
"anyOf": [
{
@@ -110,7 +122,22 @@
},
"properties": {
"rateLimits": {
"$ref": "#/definitions/RateLimitSnapshot"
"allOf": [
{
"$ref": "#/definitions/RateLimitSnapshot"
}
],
"description": "Backward-compatible single-bucket view; mirrors the historical payload."
},
"rateLimitsByLimitId": {
"additionalProperties": {
"$ref": "#/definitions/RateLimitSnapshot"
},
"description": "Multi-bucket view keyed by metered `limit_id` (for example, `codex`).",
"type": [
"object",
"null"
]
}
},
"required": [
@@ -8,6 +8,13 @@
"null"
]
},
"includeHidden": {
"description": "When true, include models that are hidden from the default picker list.",
"type": [
"boolean",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -31,6 +31,9 @@
"displayName": {
"type": "string"
},
"hidden": {
"type": "boolean"
},
"id": {
"type": "string"
},
@@ -71,6 +74,7 @@
"defaultReasoningEffort",
"description",
"displayName",
"hidden",
"id",
"isDefault",
"model",
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -40,6 +40,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -49,35 +50,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -488,6 +460,53 @@
}
]
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -520,6 +539,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -572,6 +601,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -627,7 +666,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -39,6 +39,13 @@
"null"
]
},
"cwd": {
"description": "Optional cwd filter; when set, only threads whose session cwd exactly matches this path are returned.",
"type": [
"string",
"null"
]
},
"limit": {
"description": "Optional page size; defaults to a reasonable server-side value.",
"format": "uint32",
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -500,7 +472,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -500,7 +472,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -40,6 +40,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -49,35 +50,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -488,6 +460,53 @@
}
]
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -520,6 +539,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -572,6 +601,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -627,7 +666,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -500,7 +472,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -40,6 +40,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -49,35 +50,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -488,6 +460,53 @@
}
]
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -520,6 +539,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -572,6 +601,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -627,7 +666,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -500,7 +472,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -500,7 +472,8 @@
{
"enum": [
"review",
"compact"
"compact",
"memory_consolidation"
],
"type": "string"
},
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -72,6 +72,53 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -124,6 +171,16 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"readOnly"
@@ -176,6 +233,16 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -27,6 +27,7 @@
"enum": [
"contextWindowExceeded",
"usageLimitExceeded",
"serverOverloaded",
"internalServerError",
"unauthorized",
"badRequest",
@@ -36,35 +37,6 @@
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"modelCap": {
"properties": {
"model": {
"type": "string"
},
"reset_after_seconds": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"model"
],
"type": "object"
}
},
"required": [
"modelCap"
],
"title": "ModelCapCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -5,4 +5,4 @@
/**
* Codex errors that we expose to clients.
*/
export type CodexErrorInfo = "context_window_exceeded" | "usage_limit_exceeded" | { "model_cap": { model: string, reset_after_seconds: bigint | null, } } | { "http_connection_failed": { http_status_code: number | null, } } | { "response_stream_connection_failed": { http_status_code: number | null, } } | "internal_server_error" | "unauthorized" | "bad_request" | "sandbox_error" | { "response_stream_disconnected": { http_status_code: number | null, } } | { "response_too_many_failed_attempts": { http_status_code: number | null, } } | "thread_rollback_failed" | "other";
export type CodexErrorInfo = "context_window_exceeded" | "usage_limit_exceeded" | "server_overloaded" | { "http_connection_failed": { http_status_code: number | null, } } | { "response_stream_connection_failed": { http_status_code: number | null, } } | "internal_server_error" | "unauthorized" | "bad_request" | "sandbox_error" | { "response_stream_disconnected": { http_status_code: number | null, } } | { "response_too_many_failed_attempts": { http_status_code: number | null, } } | "thread_rollback_failed" | "other";
@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExecCommandSource } from "./ExecCommandSource";
import type { ExecCommandStatus } from "./ExecCommandStatus";
import type { ParsedCommand } from "./ParsedCommand";
export type ExecCommandEndEvent = {
@@ -56,4 +57,8 @@ duration: string,
/**
* Formatted output from the command, as seen by the model.
*/
formatted_output: string, };
formatted_output: string,
/**
* Completion status for this command execution.
*/
status: ExecCommandStatus, };
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ExecCommandStatus = "completed" | "failed" | "declined";
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FuzzyFileSearchSessionCompletedNotification = { sessionId: string, };
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult";
export type FuzzyFileSearchSessionUpdatedNotification = { sessionId: string, query: string, files: Array<FuzzyFileSearchResult>, };
@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FileChange } from "./FileChange";
import type { PatchApplyStatus } from "./PatchApplyStatus";
export type PatchApplyEndEvent = {
/**
@@ -28,4 +29,8 @@ success: boolean,
/**
* The changes that were applied (mirrors PatchApplyBeginEvent::changes).
*/
changes: { [key in string]?: FileChange }, };
changes: { [key in string]?: FileChange },
/**
* Completion status for this patch application.
*/
status: PatchApplyStatus, };
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PatchApplyStatus = "completed" | "failed" | "declined";
@@ -5,4 +5,4 @@ import type { CreditsSnapshot } from "./CreditsSnapshot";
import type { PlanType } from "./PlanType";
import type { RateLimitWindow } from "./RateLimitWindow";
export type RateLimitSnapshot = { primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, plan_type: PlanType | null, };
export type RateLimitSnapshot = { limit_id: string | null, limit_name: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, plan_type: PlanType | null, };
@@ -0,0 +1,19 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "./AbsolutePathBuf";
/**
* Determines how read-only file access is granted inside a restricted
* sandbox.
*/
export type ReadOnlyAccess = { "type": "restricted",
/**
* Include built-in platform read roots required for basic process
* execution.
*/
include_platform_defaults: boolean,
/**
* Additional absolute roots that should be readable.
*/
readable_roots?: Array<AbsolutePathBuf>, } | { "type": "full-access" };
@@ -3,11 +3,16 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "./AbsolutePathBuf";
import type { NetworkAccess } from "./NetworkAccess";
import type { ReadOnlyAccess } from "./ReadOnlyAccess";
/**
* Determines execution restrictions for model shell commands.
*/
export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only" } | { "type": "external-sandbox",
export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only",
/**
* Read access granted while running under this policy.
*/
access?: ReadOnlyAccess, } | { "type": "external-sandbox",
/**
* Whether the external sandbox permits outbound network traffic.
*/
@@ -17,6 +22,10 @@ network_access: NetworkAccess, } | { "type": "workspace-write",
* writable from within the sandbox.
*/
writable_roots?: Array<AbsolutePathBuf>,
/**
* Read access granted while running under this policy.
*/
read_only_access?: ReadOnlyAccess,
/**
* When set to `true`, outbound network access is allowed. `false` by
* default.
@@ -2,6 +2,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification";
import type { FuzzyFileSearchSessionCompletedNotification } from "./FuzzyFileSearchSessionCompletedNotification";
import type { FuzzyFileSearchSessionUpdatedNotification } from "./FuzzyFileSearchSessionUpdatedNotification";
import type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification";
import type { SessionConfiguredNotification } from "./SessionConfiguredNotification";
import type { AccountLoginCompletedNotification } from "./v2/AccountLoginCompletedNotification";
@@ -37,4 +39,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
/**
* Notification sent from the server to the client.
*/
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification };
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification };
@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadId } from "./ThreadId";
export type SubAgentSource = "review" | "compact" | { "thread_spawn": { parent_thread_id: ThreadId, depth: number, } } | { "other": string };
export type SubAgentSource = "review" | "compact" | { "thread_spawn": { parent_thread_id: ThreadId, depth: number, } } | "memory_consolidation" | { "other": string };
@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TurnAbortReason } from "./TurnAbortReason";
export type TurnAbortedEvent = { reason: TurnAbortReason, };
export type TurnAbortedEvent = { turn_id: string | null, reason: TurnAbortReason, };
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type TurnCompleteEvent = { last_agent_message: string | null, };
export type TurnCompleteEvent = { turn_id: string, last_agent_message: string | null, };
@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ModeKind } from "./ModeKind";
export type TurnStartedEvent = { model_context_window: bigint | null, collaboration_mode_kind: ModeKind, };
export type TurnStartedEvent = { turn_id: string, model_context_window: bigint | null, collaboration_mode_kind: ModeKind, };
@@ -62,6 +62,7 @@ export type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent";
export type { ExecCommandEndEvent } from "./ExecCommandEndEvent";
export type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent";
export type { ExecCommandSource } from "./ExecCommandSource";
export type { ExecCommandStatus } from "./ExecCommandStatus";
export type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams";
export type { ExecOneOffCommandResponse } from "./ExecOneOffCommandResponse";
export type { ExecOutputStream } from "./ExecOutputStream";
@@ -77,6 +78,8 @@ export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload";
export type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams";
export type { FuzzyFileSearchResponse } from "./FuzzyFileSearchResponse";
export type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult";
export type { FuzzyFileSearchSessionCompletedNotification } from "./FuzzyFileSearchSessionCompletedNotification";
export type { FuzzyFileSearchSessionUpdatedNotification } from "./FuzzyFileSearchSessionUpdatedNotification";
export type { GetAuthStatusParams } from "./GetAuthStatusParams";
export type { GetAuthStatusResponse } from "./GetAuthStatusResponse";
export type { GetConversationSummaryParams } from "./GetConversationSummaryParams";
@@ -128,6 +131,7 @@ export type { NewConversationResponse } from "./NewConversationResponse";
export type { ParsedCommand } from "./ParsedCommand";
export type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent";
export type { PatchApplyEndEvent } from "./PatchApplyEndEvent";
export type { PatchApplyStatus } from "./PatchApplyStatus";
export type { Personality } from "./Personality";
export type { PlanDeltaEvent } from "./PlanDeltaEvent";
export type { PlanItem } from "./PlanItem";
@@ -137,6 +141,7 @@ export type { Profile } from "./Profile";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type { RateLimitWindow } from "./RateLimitWindow";
export type { RawResponseItemEvent } from "./RawResponseItemEvent";
export type { ReadOnlyAccess } from "./ReadOnlyAccess";
export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent";
export type { ReasoningEffort } from "./ReasoningEffort";
export type { ReasoningItem } from "./ReasoningItem";
@@ -5,4 +5,13 @@
/**
* EXPERIMENTAL - app metadata returned by app-list APIs.
*/
export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, installUrl: string | null, isAccessible: boolean, };
export type AppInfo = { id: string, name: string, description: string | null, logoUrl: string | null, logoUrlDark: string | null, distributionChannel: string | null, installUrl: string | null, isAccessible: boolean,
/**
* Whether this app is enabled in config.toml.
* Example:
* ```toml
* [apps.bad_app]
* enabled = false
* ```
*/
isEnabled: boolean, };
@@ -8,4 +8,4 @@
* When an upstream HTTP status is available (for example, from the Responses API or a provider),
* it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
*/
export type CodexErrorInfo = "contextWindowExceeded" | "usageLimitExceeded" | { "modelCap": { model: string, reset_after_seconds: bigint | null, } } | { "httpConnectionFailed": { httpStatusCode: number | null, } } | { "responseStreamConnectionFailed": { httpStatusCode: number | null, } } | "internalServerError" | "unauthorized" | "badRequest" | "threadRollbackFailed" | "sandboxError" | { "responseStreamDisconnected": { httpStatusCode: number | null, } } | { "responseTooManyFailedAttempts": { httpStatusCode: number | null, } } | "other";
export type CodexErrorInfo = "contextWindowExceeded" | "usageLimitExceeded" | "serverOverloaded" | { "httpConnectionFailed": { httpStatusCode: number | null, } } | { "responseStreamConnectionFailed": { httpStatusCode: number | null, } } | "internalServerError" | "unauthorized" | "badRequest" | "threadRollbackFailed" | "sandboxError" | { "responseStreamDisconnected": { httpStatusCode: number | null, } } | { "responseTooManyFailedAttempts": { httpStatusCode: number | null, } } | "other";
@@ -3,4 +3,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type GetAccountRateLimitsResponse = { rateLimits: RateLimitSnapshot, };
export type GetAccountRateLimitsResponse = {
/**
* Backward-compatible single-bucket view; mirrors the historical payload.
*/
rateLimits: RateLimitSnapshot,
/**
* Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
*/
rateLimitsByLimitId: { [key in string]?: RateLimitSnapshot } | null, };
@@ -5,4 +5,4 @@ import type { InputModality } from "../InputModality";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningEffortOption } from "./ReasoningEffortOption";
export type Model = { id: string, model: string, upgrade: string | null, displayName: string, description: string, supportedReasoningEfforts: Array<ReasoningEffortOption>, defaultReasoningEffort: ReasoningEffort, inputModalities: Array<InputModality>, supportsPersonality: boolean, isDefault: boolean, };
export type Model = { id: string, model: string, upgrade: string | null, displayName: string, description: string, hidden: boolean, supportedReasoningEfforts: Array<ReasoningEffortOption>, defaultReasoningEffort: ReasoningEffort, inputModalities: Array<InputModality>, supportsPersonality: boolean, isDefault: boolean, };
@@ -10,4 +10,8 @@ cursor?: string | null,
/**
* Optional page size; defaults to a reasonable server-side value.
*/
limit?: number | null, };
limit?: number | null,
/**
* When true, include models that are hidden from the default picker list.
*/
includeHidden?: boolean | null, };
@@ -5,4 +5,4 @@ import type { PlanType } from "../PlanType";
import type { CreditsSnapshot } from "./CreditsSnapshot";
import type { RateLimitWindow } from "./RateLimitWindow";
export type RateLimitSnapshot = { primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, };
export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, };
@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type ReadOnlyAccess = { "type": "restricted", includePlatformDefaults: boolean, readableRoots: Array<AbsolutePathBuf>, } | { "type": "fullAccess" };
@@ -3,5 +3,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { NetworkAccess } from "./NetworkAccess";
import type { ReadOnlyAccess } from "./ReadOnlyAccess";
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly" } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", access: ReadOnlyAccess, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, readOnlyAccess: ReadOnlyAccess, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
@@ -21,4 +21,8 @@ export type ThreadForkParams = {threadId: string, /**
path?: string | null, /**
* Configuration overrides for the forked thread, if any.
*/
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null};
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
persistExtendedHistory: boolean};
@@ -31,4 +31,9 @@ sourceKinds?: Array<ThreadSourceKind> | null,
* Optional archived filter; when set to true, only archived threads are returned.
* If false or null, only non-archived threads are returned.
*/
archived?: boolean | null, };
archived?: boolean | null,
/**
* Optional cwd filter; when set, only threads whose session cwd exactly
* matches this path are returned.
*/
cwd?: string | null, };
@@ -30,4 +30,8 @@ history?: Array<ResponseItem> | null, /**
path?: string | null, /**
* Configuration overrides for the resumed thread, if any.
*/
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null};
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
persistExtendedHistory: boolean};
@@ -10,4 +10,8 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* If true, opt into emitting raw Responses API items on the event stream.
* This is for internal use only (e.g. Codex Cloud).
*/
experimentalRawEvents: boolean};
experimentalRawEvents: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on resume/fork/read.
*/
persistExtendedHistory: boolean};
@@ -101,6 +101,7 @@ export type { ProfileV2 } from "./ProfileV2";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type { RateLimitWindow } from "./RateLimitWindow";
export type { RawResponseItemCompletedNotification } from "./RawResponseItemCompletedNotification";
export type { ReadOnlyAccess } from "./ReadOnlyAccess";
export type { ReasoningEffortOption } from "./ReasoningEffortOption";
export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification";
export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification";
@@ -458,6 +458,21 @@ client_request_definitions! {
params: FuzzyFileSearchParams,
response: FuzzyFileSearchResponse,
},
#[experimental("fuzzyFileSearch/sessionStart")]
FuzzyFileSearchSessionStart => "fuzzyFileSearch/sessionStart" {
params: FuzzyFileSearchSessionStartParams,
response: FuzzyFileSearchSessionStartResponse,
},
#[experimental("fuzzyFileSearch/sessionUpdate")]
FuzzyFileSearchSessionUpdate => "fuzzyFileSearch/sessionUpdate" {
params: FuzzyFileSearchSessionUpdateParams,
response: FuzzyFileSearchSessionUpdateResponse,
},
#[experimental("fuzzyFileSearch/sessionStop")]
FuzzyFileSearchSessionStop => "fuzzyFileSearch/sessionStop" {
params: FuzzyFileSearchSessionStopParams,
response: FuzzyFileSearchSessionStopResponse,
},
/// Execute a command (argv vector) under the server's sandbox.
ExecOneOffCommand {
params: v1::ExecOneOffCommandParams,
@@ -702,6 +717,54 @@ pub struct FuzzyFileSearchResponse {
pub files: Vec<FuzzyFileSearchResult>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchSessionStartParams {
pub session_id: String,
pub roots: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Default)]
pub struct FuzzyFileSearchSessionStartResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchSessionUpdateParams {
pub session_id: String,
pub query: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Default)]
pub struct FuzzyFileSearchSessionUpdateResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchSessionStopParams {
pub session_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Default)]
pub struct FuzzyFileSearchSessionStopResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchSessionUpdatedNotification {
pub session_id: String,
pub query: String,
pub files: Vec<FuzzyFileSearchResult>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
pub struct FuzzyFileSearchSessionCompletedNotification {
pub session_id: String,
}
server_notification_definitions! {
/// NEW NOTIFICATIONS
Error => "error" (v2::ErrorNotification),
@@ -734,6 +797,8 @@ server_notification_definitions! {
ContextCompacted => "thread/compacted" (v2::ContextCompactedNotification),
DeprecationNotice => "deprecationNotice" (v2::DeprecationNoticeNotification),
ConfigWarning => "configWarning" (v2::ConfigWarningNotification),
FuzzyFileSearchSessionUpdated => "fuzzyFileSearch/sessionUpdated" (FuzzyFileSearchSessionUpdatedNotification),
FuzzyFileSearchSessionCompleted => "fuzzyFileSearch/sessionCompleted" (FuzzyFileSearchSessionCompletedNotification),
/// Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.
WindowsWorldWritableWarning => "windows/worldWritableWarning" (v2::WindowsWorldWritableWarningNotification),
@@ -1170,7 +1235,8 @@ mod tests {
"id": 6,
"params": {
"limit": null,
"cursor": null
"cursor": null,
"includeHidden": null
}
}),
serde_json::to_value(&request)?,
@@ -1245,17 +1311,4 @@ mod tests {
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
assert_eq!(reason, Some("mock/experimentalMethod"));
}
#[test]
fn thread_start_mock_field_is_marked_experimental() {
let request = ClientRequest::ThreadStart {
request_id: RequestId::Integer(1),
params: v2::ThreadStartParams {
mock_experimental_field: Some("mock".to_string()),
..Default::default()
},
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
assert_eq!(reason, Some("thread/start.mockExperimentalField"));
}
}
File diff suppressed because it is too large Load Diff
+252 -14
View File
@@ -29,9 +29,12 @@ use codex_protocol::protocol::AgentStatus as CoreAgentStatus;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::protocol::SessionSource as CoreSessionSource;
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo;
@@ -88,10 +91,7 @@ macro_rules! v2_enum_from_core {
pub enum CodexErrorInfo {
ContextWindowExceeded,
UsageLimitExceeded,
ModelCap {
model: String,
reset_after_seconds: Option<u64>,
},
ServerOverloaded,
HttpConnectionFailed {
#[serde(rename = "httpStatusCode")]
#[ts(rename = "httpStatusCode")]
@@ -128,13 +128,7 @@ impl From<CoreCodexErrorInfo> for CodexErrorInfo {
match value {
CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
CoreCodexErrorInfo::ModelCap {
model,
reset_after_seconds,
} => CodexErrorInfo::ModelCap {
model,
reset_after_seconds,
},
CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => {
CodexErrorInfo::HttpConnectionFailed { http_status_code }
}
@@ -404,6 +398,10 @@ const fn default_enabled() -> bool {
true
}
const fn default_include_platform_defaults() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
@@ -647,13 +645,65 @@ pub enum NetworkAccess {
Enabled,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum ReadOnlyAccess {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Restricted {
#[serde(default = "default_include_platform_defaults")]
include_platform_defaults: bool,
#[serde(default)]
readable_roots: Vec<AbsolutePathBuf>,
},
#[default]
FullAccess,
}
impl ReadOnlyAccess {
pub fn to_core(&self) -> CoreReadOnlyAccess {
match self {
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => CoreReadOnlyAccess::Restricted {
include_platform_defaults: *include_platform_defaults,
readable_roots: readable_roots.clone(),
},
ReadOnlyAccess::FullAccess => CoreReadOnlyAccess::FullAccess,
}
}
}
impl From<CoreReadOnlyAccess> for ReadOnlyAccess {
fn from(value: CoreReadOnlyAccess) -> Self {
match value {
CoreReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
},
CoreReadOnlyAccess::FullAccess => ReadOnlyAccess::FullAccess,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum SandboxPolicy {
DangerFullAccess,
ReadOnly,
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ReadOnly {
#[serde(default)]
access: ReadOnlyAccess,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ExternalSandbox {
@@ -666,6 +716,8 @@ pub enum SandboxPolicy {
#[serde(default)]
writable_roots: Vec<AbsolutePathBuf>,
#[serde(default)]
read_only_access: ReadOnlyAccess,
#[serde(default)]
network_access: bool,
#[serde(default)]
exclude_tmpdir_env_var: bool,
@@ -680,7 +732,11 @@ impl SandboxPolicy {
SandboxPolicy::DangerFullAccess => {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
}
SandboxPolicy::ReadOnly => codex_protocol::protocol::SandboxPolicy::ReadOnly,
SandboxPolicy::ReadOnly { access } => {
codex_protocol::protocol::SandboxPolicy::ReadOnly {
access: access.to_core(),
}
}
SandboxPolicy::ExternalSandbox { network_access } => {
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
network_access: match network_access {
@@ -691,11 +747,13 @@ impl SandboxPolicy {
}
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: writable_roots.clone(),
read_only_access: read_only_access.to_core(),
network_access: *network_access,
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
exclude_slash_tmp: *exclude_slash_tmp,
@@ -710,7 +768,11 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
SandboxPolicy::DangerFullAccess
}
codex_protocol::protocol::SandboxPolicy::ReadOnly => SandboxPolicy::ReadOnly,
codex_protocol::protocol::SandboxPolicy::ReadOnly { access } => {
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::from(access),
}
}
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
SandboxPolicy::ExternalSandbox {
network_access: match network_access {
@@ -721,11 +783,13 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
}
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access: ReadOnlyAccess::from(read_only_access),
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
@@ -1009,7 +1073,10 @@ pub struct ChatgptAuthTokensRefreshResponse {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountRateLimitsResponse {
/// Backward-compatible single-bucket view; mirrors the historical payload.
pub rate_limits: RateLimitSnapshot,
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1043,6 +1110,9 @@ pub struct ModelListParams {
/// Optional page size; defaults to a reasonable server-side value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
/// When true, include models that are hidden from the default picker list.
#[ts(optional = nullable)]
pub include_hidden: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1054,6 +1124,7 @@ pub struct Model {
pub upgrade: Option<String>,
pub display_name: String,
pub description: String,
pub hidden: bool,
pub supported_reasoning_efforts: Vec<ReasoningEffortOption>,
pub default_reasoning_effort: ReasoningEffort,
#[serde(default = "default_input_modalities")]
@@ -1223,6 +1294,14 @@ pub struct AppInfo {
pub install_url: Option<String>,
#[serde(default)]
pub is_accessible: bool,
/// Whether this app is enabled in config.toml.
/// Example:
/// ```toml
/// [apps.bad_app]
/// enabled = false
/// ```
#[serde(default = "default_enabled")]
pub is_enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1357,6 +1436,11 @@ pub struct ThreadStartParams {
#[experimental("thread/start.experimentalRawEvents")]
#[serde(default)]
pub experimental_raw_events: bool,
/// If true, persist additional rollout EventMsg variants required to
/// reconstruct a richer thread history on resume/fork/read.
#[experimental("thread/start.persistFullHistory")]
#[serde(default)]
pub persist_extended_history: bool,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
@@ -1438,6 +1522,11 @@ pub struct ThreadResumeParams {
pub developer_instructions: Option<String>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
/// If true, persist additional rollout EventMsg variants required to
/// reconstruct a richer thread history on subsequent resume/fork/read.
#[experimental("thread/resume.persistFullHistory")]
#[serde(default)]
pub persist_extended_history: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1491,6 +1580,11 @@ pub struct ThreadForkParams {
pub base_instructions: Option<String>,
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
/// If true, persist additional rollout EventMsg variants required to
/// reconstruct a richer thread history on subsequent resume/fork/read.
#[experimental("thread/fork.persistFullHistory")]
#[serde(default)]
pub persist_extended_history: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1618,6 +1712,10 @@ pub struct ThreadListParams {
/// If false or null, only non-archived threads are returned.
#[ts(optional = nullable)]
pub archived: Option<bool>,
/// Optional cwd filter; when set, only threads whose session cwd exactly
/// matches this path are returned.
#[ts(optional = nullable)]
pub cwd: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
@@ -2557,6 +2655,22 @@ pub enum CommandExecutionStatus {
Declined,
}
impl From<CoreExecCommandStatus> for CommandExecutionStatus {
fn from(value: CoreExecCommandStatus) -> Self {
Self::from(&value)
}
}
impl From<&CoreExecCommandStatus> for CommandExecutionStatus {
fn from(value: &CoreExecCommandStatus) -> Self {
match value {
CoreExecCommandStatus::Completed => CommandExecutionStatus::Completed,
CoreExecCommandStatus::Failed => CommandExecutionStatus::Failed,
CoreExecCommandStatus::Declined => CommandExecutionStatus::Declined,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2597,6 +2711,22 @@ pub enum PatchApplyStatus {
Declined,
}
impl From<CorePatchApplyStatus> for PatchApplyStatus {
fn from(value: CorePatchApplyStatus) -> Self {
Self::from(&value)
}
}
impl From<&CorePatchApplyStatus> for PatchApplyStatus {
fn from(value: &CorePatchApplyStatus) -> Self {
match value {
CorePatchApplyStatus::Completed => PatchApplyStatus::Completed,
CorePatchApplyStatus::Failed => PatchApplyStatus::Failed,
CorePatchApplyStatus::Declined => PatchApplyStatus::Declined,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3103,6 +3233,8 @@ pub struct AccountRateLimitsUpdatedNotification {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct RateLimitSnapshot {
pub limit_id: Option<String>,
pub limit_name: Option<String>,
pub primary: Option<RateLimitWindow>,
pub secondary: Option<RateLimitWindow>,
pub credits: Option<CreditsSnapshot>,
@@ -3112,6 +3244,8 @@ pub struct RateLimitSnapshot {
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
fn from(value: CoreRateLimitSnapshot) -> Self {
Self {
limit_id: value.limit_id,
limit_name: value.limit_name,
primary: value.primary.map(RateLimitWindow::from),
secondary: value.secondary.map(RateLimitWindow::from),
credits: value.credits.map(CreditsSnapshot::from),
@@ -3228,11 +3362,21 @@ mod tests {
use codex_protocol::items::WebSearchItem;
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::user_input::UserInput as CoreUserInput;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
fn test_absolute_path() -> AbsolutePathBuf {
let path = if cfg!(windows) {
r"C:\readable"
} else {
"/readable"
};
AbsolutePathBuf::from_absolute_path(path).expect("path must be absolute")
}
#[test]
fn sandbox_policy_round_trips_external_sandbox_network_access() {
let v2_policy = SandboxPolicy::ExternalSandbox {
@@ -3251,6 +3395,100 @@ mod tests {
assert_eq!(back_to_v2, v2_policy);
}
#[test]
fn sandbox_policy_round_trips_read_only_access() {
let readable_root = test_absolute_path();
let v2_policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root.clone()],
},
};
let core_policy = v2_policy.to_core();
assert_eq!(
core_policy,
codex_protocol::protocol::SandboxPolicy::ReadOnly {
access: CoreReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root],
},
}
);
let back_to_v2 = SandboxPolicy::from(core_policy);
assert_eq!(back_to_v2, v2_policy);
}
#[test]
fn sandbox_policy_round_trips_workspace_write_read_only_access() {
let readable_root = test_absolute_path();
let v2_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root.clone()],
},
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
let core_policy = v2_policy.to_core();
assert_eq!(
core_policy,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: CoreReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root],
},
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
}
);
let back_to_v2 = SandboxPolicy::from(core_policy);
assert_eq!(back_to_v2, v2_policy);
}
#[test]
fn sandbox_policy_deserializes_legacy_read_only_without_access_field() {
let policy: SandboxPolicy = serde_json::from_value(json!({
"type": "readOnly"
}))
.expect("read-only policy should deserialize");
assert_eq!(
policy,
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
}
);
}
#[test]
fn sandbox_policy_deserializes_legacy_workspace_write_without_read_only_access_field() {
let policy: SandboxPolicy = serde_json::from_value(json!({
"type": "workspaceWrite",
"writableRoots": [],
"networkAccess": false,
"excludeTmpdirEnvVar": false,
"excludeSlashTmp": false
}))
.expect("workspace-write policy should deserialize");
assert_eq!(
policy,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
}
);
}
#[test]
fn core_turn_item_into_thread_item_converts_supported_variants() {
let user_item = TurnItem::UserMessage(UserMessageItem {
File diff suppressed because it is too large Load Diff
@@ -14,4 +14,6 @@ codex-app-server-protocol = { workspace = true }
codex-protocol = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tungstenite = { workspace = true }
url = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
+48 -1
View File
@@ -1,2 +1,49 @@
# App Server Test Client
Exercises simple `codex app-server` flows end-to-end, logging JSON-RPC messages sent between client and server to stdout.
Quickstart for running and hitting `codex app-server`.
## Quickstart
Run from `<reporoot>/codex-rs`.
```bash
# 1) Build debug codex binary
cargo build -p codex-cli --bin codex
# 2) Start websocket app-server in background
cargo run -p codex-app-server-test-client -- \
--codex-bin ./target/debug/codex \
serve --listen ws://127.0.0.1:4222 --kill
# 3) Call app-server (defaults to ws://127.0.0.1:4222)
cargo run -p codex-app-server-test-client -- model-list
```
## Testing Thread Rejoin Behavior
Build and start an app server using commands above. The app-server log is written to `/tmp/codex-app-server-test-client/app-server.log`
### 1) Get a thread id
Create at least one thread, then list threads:
```bash
cargo run -p codex-app-server-test-client -- send-message-v2 "seed thread for rejoin test"
cargo run -p codex-app-server-test-client -- thread-list --limit 5
```
Copy a thread id from the `thread-list` output.
### 2) Rejoin while a turn is in progress (two terminals)
Terminal A:
```bash
cargo run --bin codex-app-server-test-client -- \
resume-message-v2 <THREAD_ID> "respond with thorough docs on the rust core"
```
Terminal B (while Terminal A is still streaming):
```bash
cargo run --bin codex-app-server-test-client -- thread-resume <THREAD_ID>
```
+492 -81
View File
@@ -1,8 +1,10 @@
use std::collections::VecDeque;
use std::fs;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::net::TcpStream;
use std::path::Path;
use std::path::PathBuf;
use std::process::Child;
@@ -46,12 +48,17 @@ use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::NewConversationResponse;
use codex_app_server_protocol::ReadOnlyAccess;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserMessageResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadListResponse;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
@@ -64,15 +71,28 @@ use codex_protocol::protocol::EventMsg;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use tungstenite::Message;
use tungstenite::WebSocket;
use tungstenite::connect;
use tungstenite::stream::MaybeTlsStream;
use url::Url;
use uuid::Uuid;
/// Minimal launcher that initializes the Codex app-server and logs the handshake.
#[derive(Parser)]
#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)]
struct Cli {
/// Path to the `codex` CLI binary.
#[arg(long, env = "CODEX_BIN", default_value = "codex")]
codex_bin: PathBuf,
/// Path to the `codex` CLI binary. When set, requests use stdio by
/// spawning `codex app-server` as a child process.
#[arg(long, env = "CODEX_BIN", global = true)]
codex_bin: Option<PathBuf>,
/// Existing websocket server URL to connect to.
///
/// If neither `--codex-bin` nor `--url` is provided, defaults to
/// `ws://127.0.0.1:4222`.
#[arg(long, env = "CODEX_APP_SERVER_URL", global = true)]
url: Option<String>,
/// Forwarded to the `codex` CLI as `--config key=value`. Repeatable.
///
@@ -102,6 +122,18 @@ struct Cli {
#[derive(Subcommand)]
enum CliCommand {
/// Start `codex app-server` on a websocket endpoint in the background.
///
/// Logs are written to:
/// `/tmp/codex-app-server-test-client/`
Serve {
/// WebSocket listen URL passed to `codex app-server --listen`.
#[arg(long, default_value = "ws://127.0.0.1:4222")]
listen: String,
/// Kill any process listening on the same port before starting.
#[arg(long, default_value_t = false)]
kill: bool,
},
/// Send a user message through the Codex app-server.
SendMessage {
/// User message to send to Codex.
@@ -112,6 +144,20 @@ enum CliCommand {
/// User message to send to Codex.
user_message: String,
},
/// Resume a V2 thread by id, then send a user message.
ResumeMessageV2 {
/// Existing thread id to resume.
thread_id: String,
/// User message to send to Codex.
user_message: String,
},
/// Resume a V2 thread and continuously stream notifications/events.
///
/// This command does not auto-exit; stop it with SIGINT/SIGTERM/SIGKILL.
ThreadResume {
/// Existing thread id to resume.
thread_id: String,
},
/// Start a V2 turn that elicits an ExecCommand approval.
#[command(name = "trigger-cmd-approval")]
TriggerCmdApproval {
@@ -141,11 +187,19 @@ enum CliCommand {
/// List the available models from the Codex app-server.
#[command(name = "model-list")]
ModelList,
/// List stored threads from the Codex app-server.
#[command(name = "thread-list")]
ThreadList {
/// Number of threads to return.
#[arg(long, default_value_t = 20)]
limit: u32,
},
}
pub fn run() -> Result<()> {
let Cli {
codex_bin,
url,
config_overrides,
dynamic_tools,
command,
@@ -154,49 +208,222 @@ pub fn run() -> Result<()> {
let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?;
match command {
CliCommand::Serve { listen, kill } => {
ensure_dynamic_tools_unused(&dynamic_tools, "serve")?;
let codex_bin = codex_bin.unwrap_or_else(|| PathBuf::from("codex"));
serve(&codex_bin, &config_overrides, &listen, kill)
}
CliCommand::SendMessage { user_message } => {
ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?;
send_message(&codex_bin, &config_overrides, user_message)
let endpoint = resolve_endpoint(codex_bin, url)?;
send_message(&endpoint, &config_overrides, user_message)
}
CliCommand::SendMessageV2 { user_message } => {
send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools)
let endpoint = resolve_endpoint(codex_bin, url)?;
send_message_v2_endpoint(&endpoint, &config_overrides, user_message, &dynamic_tools)
}
CliCommand::ResumeMessageV2 {
thread_id,
user_message,
} => {
let endpoint = resolve_endpoint(codex_bin, url)?;
resume_message_v2(
&endpoint,
&config_overrides,
thread_id,
user_message,
&dynamic_tools,
)
}
CliCommand::ThreadResume { thread_id } => {
ensure_dynamic_tools_unused(&dynamic_tools, "thread-resume")?;
let endpoint = resolve_endpoint(codex_bin, url)?;
thread_resume_follow(&endpoint, &config_overrides, thread_id)
}
CliCommand::TriggerCmdApproval { user_message } => {
trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools)
let endpoint = resolve_endpoint(codex_bin, url)?;
trigger_cmd_approval(&endpoint, &config_overrides, user_message, &dynamic_tools)
}
CliCommand::TriggerPatchApproval { user_message } => {
trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools)
let endpoint = resolve_endpoint(codex_bin, url)?;
trigger_patch_approval(&endpoint, &config_overrides, user_message, &dynamic_tools)
}
CliCommand::NoTriggerCmdApproval => {
no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools)
let endpoint = resolve_endpoint(codex_bin, url)?;
no_trigger_cmd_approval(&endpoint, &config_overrides, &dynamic_tools)
}
CliCommand::SendFollowUpV2 {
first_message,
follow_up_message,
} => send_follow_up_v2(
&codex_bin,
&config_overrides,
first_message,
follow_up_message,
&dynamic_tools,
),
} => {
let endpoint = resolve_endpoint(codex_bin, url)?;
send_follow_up_v2(
&endpoint,
&config_overrides,
first_message,
follow_up_message,
&dynamic_tools,
)
}
CliCommand::TestLogin => {
ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?;
test_login(&codex_bin, &config_overrides)
let endpoint = resolve_endpoint(codex_bin, url)?;
test_login(&endpoint, &config_overrides)
}
CliCommand::GetAccountRateLimits => {
ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?;
get_account_rate_limits(&codex_bin, &config_overrides)
let endpoint = resolve_endpoint(codex_bin, url)?;
get_account_rate_limits(&endpoint, &config_overrides)
}
CliCommand::ModelList => {
ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?;
model_list(&codex_bin, &config_overrides)
let endpoint = resolve_endpoint(codex_bin, url)?;
model_list(&endpoint, &config_overrides)
}
CliCommand::ThreadList { limit } => {
ensure_dynamic_tools_unused(&dynamic_tools, "thread-list")?;
let endpoint = resolve_endpoint(codex_bin, url)?;
thread_list(&endpoint, &config_overrides, limit)
}
}
}
fn send_message(codex_bin: &Path, config_overrides: &[String], user_message: String) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
enum Endpoint {
SpawnCodex(PathBuf),
ConnectWs(String),
}
fn resolve_endpoint(codex_bin: Option<PathBuf>, url: Option<String>) -> Result<Endpoint> {
if codex_bin.is_some() && url.is_some() {
bail!("--codex-bin and --url are mutually exclusive");
}
if let Some(codex_bin) = codex_bin {
return Ok(Endpoint::SpawnCodex(codex_bin));
}
if let Some(url) = url {
return Ok(Endpoint::ConnectWs(url));
}
Ok(Endpoint::ConnectWs("ws://127.0.0.1:4222".to_string()))
}
fn serve(codex_bin: &Path, config_overrides: &[String], listen: &str, kill: bool) -> Result<()> {
let runtime_dir = PathBuf::from("/tmp/codex-app-server-test-client");
fs::create_dir_all(&runtime_dir)
.with_context(|| format!("failed to create runtime dir {}", runtime_dir.display()))?;
let log_path = runtime_dir.join("app-server.log");
if kill {
kill_listeners_on_same_port(listen)?;
}
let log_file = OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
.with_context(|| format!("failed to open log file {}", log_path.display()))?;
let log_file_stderr = log_file
.try_clone()
.with_context(|| format!("failed to clone log file handle {}", log_path.display()))?;
let mut cmdline = format!(
"tail -f /dev/null | RUST_BACKTRACE=full RUST_LOG=warn,codex_=trace {}",
shell_quote(&codex_bin.display().to_string())
);
for override_kv in config_overrides {
cmdline.push_str(&format!(" --config {}", shell_quote(override_kv)));
}
cmdline.push_str(&format!(" app-server --listen {}", shell_quote(listen)));
let child = Command::new("nohup")
.arg("sh")
.arg("-c")
.arg(cmdline)
.stdin(Stdio::null())
.stdout(Stdio::from(log_file))
.stderr(Stdio::from(log_file_stderr))
.spawn()
.with_context(|| format!("failed to start `{}` app-server", codex_bin.display()))?;
let pid = child.id();
println!("started codex app-server");
println!("listen: {listen}");
println!("pid: {pid} (launcher process)");
println!("log: {}", log_path.display());
Ok(())
}
fn kill_listeners_on_same_port(listen: &str) -> Result<()> {
let url = Url::parse(listen).with_context(|| format!("invalid --listen URL `{listen}`"))?;
let port = url
.port_or_known_default()
.with_context(|| format!("unable to infer port from --listen URL `{listen}`"))?;
let output = Command::new("lsof")
.arg("-nP")
.arg(format!("-tiTCP:{port}"))
.arg("-sTCP:LISTEN")
.output()
.with_context(|| format!("failed to run lsof for port {port}"))?;
if !output.status.success() {
return Ok(());
}
let pids: Vec<u32> = String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| line.trim().parse::<u32>().ok())
.collect();
if pids.is_empty() {
return Ok(());
}
for pid in pids {
println!("killing listener pid {pid} on port {port}");
let pid_str = pid.to_string();
let term_status = Command::new("kill")
.arg(&pid_str)
.status()
.with_context(|| format!("failed to send SIGTERM to pid {pid}"))?;
if !term_status.success() {
continue;
}
}
thread::sleep(Duration::from_millis(300));
let output = Command::new("lsof")
.arg("-nP")
.arg(format!("-tiTCP:{port}"))
.arg("-sTCP:LISTEN")
.output()
.with_context(|| format!("failed to re-check listeners on port {port}"))?;
if !output.status.success() {
return Ok(());
}
let remaining: Vec<u32> = String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| line.trim().parse::<u32>().ok())
.collect();
for pid in remaining {
println!("force killing remaining listener pid {pid} on port {port}");
let _ = Command::new("kill").arg("-9").arg(pid.to_string()).status();
}
Ok(())
}
fn shell_quote(input: &str) -> String {
format!("'{}'", input.replace('\'', "'\\''"))
}
fn send_message(
endpoint: &Endpoint,
config_overrides: &[String],
user_message: String,
) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -222,9 +449,19 @@ pub fn send_message_v2(
config_overrides: &[String],
user_message: String,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let endpoint = Endpoint::SpawnCodex(codex_bin.to_path_buf());
send_message_v2_endpoint(&endpoint, config_overrides, user_message, dynamic_tools)
}
fn send_message_v2_endpoint(
endpoint: &Endpoint,
config_overrides: &[String],
user_message: String,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
send_message_v2_with_policies(
codex_bin,
endpoint,
config_overrides,
user_message,
None,
@@ -233,8 +470,63 @@ pub fn send_message_v2(
)
}
fn resume_message_v2(
endpoint: &Endpoint,
config_overrides: &[String],
thread_id: String,
user_message: String,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
ensure_dynamic_tools_unused(dynamic_tools, "resume-message-v2")?;
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let resume_response = client.thread_resume(ThreadResumeParams {
thread_id,
..Default::default()
})?;
println!("< thread/resume response: {resume_response:?}");
let turn_response = client.turn_start(TurnStartParams {
thread_id: resume_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: user_message,
text_elements: Vec::new(),
}],
..Default::default()
})?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&resume_response.thread.id, &turn_response.turn.id)?;
Ok(())
}
fn thread_resume_follow(
endpoint: &Endpoint,
config_overrides: &[String],
thread_id: String,
) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let resume_response = client.thread_resume(ThreadResumeParams {
thread_id,
..Default::default()
})?;
println!("< thread/resume response: {resume_response:?}");
println!("< streaming notifications until process is terminated");
client.stream_notifications_forever()
}
fn trigger_cmd_approval(
codex_bin: &Path,
endpoint: &Endpoint,
config_overrides: &[String],
user_message: Option<String>,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
@@ -243,17 +535,19 @@ fn trigger_cmd_approval(
"Run `touch /tmp/should-trigger-approval` so I can confirm the file exists.";
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
send_message_v2_with_policies(
codex_bin,
endpoint,
config_overrides,
message,
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::ReadOnly),
Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
}),
dynamic_tools,
)
}
fn trigger_patch_approval(
codex_bin: &Path,
endpoint: &Endpoint,
config_overrides: &[String],
user_message: Option<String>,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
@@ -262,23 +556,25 @@ fn trigger_patch_approval(
"Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch.";
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
send_message_v2_with_policies(
codex_bin,
endpoint,
config_overrides,
message,
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::ReadOnly),
Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
}),
dynamic_tools,
)
}
fn no_trigger_cmd_approval(
codex_bin: &Path,
endpoint: &Endpoint,
config_overrides: &[String],
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let prompt = "Run `touch should_not_trigger_approval.txt`";
send_message_v2_with_policies(
codex_bin,
endpoint,
config_overrides,
prompt.to_string(),
None,
@@ -288,14 +584,14 @@ fn no_trigger_cmd_approval(
}
fn send_message_v2_with_policies(
codex_bin: &Path,
endpoint: &Endpoint,
config_overrides: &[String],
user_message: String,
approval_policy: Option<AskForApproval>,
sandbox_policy: Option<SandboxPolicy>,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -326,13 +622,13 @@ fn send_message_v2_with_policies(
}
fn send_follow_up_v2(
codex_bin: &Path,
endpoint: &Endpoint,
config_overrides: &[String],
first_message: String,
follow_up_message: String,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -372,8 +668,8 @@ fn send_follow_up_v2(
Ok(())
}
fn test_login(codex_bin: &Path, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -402,8 +698,8 @@ fn test_login(codex_bin: &Path, config_overrides: &[String]) -> Result<()> {
}
}
fn get_account_rate_limits(codex_bin: &Path, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
fn get_account_rate_limits(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -414,8 +710,8 @@ fn get_account_rate_limits(codex_bin: &Path, config_overrides: &[String]) -> Res
Ok(())
}
fn model_list(codex_bin: &Path, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::spawn(codex_bin, config_overrides)?;
fn model_list(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
@@ -426,6 +722,26 @@ fn model_list(codex_bin: &Path, config_overrides: &[String]) -> Result<()> {
Ok(())
}
fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u32) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.thread_list(ThreadListParams {
cursor: None,
limit: Some(limit),
sort_key: None,
model_providers: None,
source_kinds: None,
archived: None,
cwd: None,
})?;
println!("< thread/list response: {response:?}");
Ok(())
}
fn ensure_dynamic_tools_unused(
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
command: &str,
@@ -460,15 +776,32 @@ fn parse_dynamic_tools_arg(dynamic_tools: &Option<String>) -> Result<Option<Vec<
Ok(Some(tools))
}
enum ClientTransport {
Stdio {
child: Child,
stdin: Option<ChildStdin>,
stdout: BufReader<ChildStdout>,
},
WebSocket {
url: String,
socket: Box<WebSocket<MaybeTlsStream<TcpStream>>>,
},
}
struct CodexClient {
child: Child,
stdin: Option<ChildStdin>,
stdout: BufReader<ChildStdout>,
transport: ClientTransport,
pending_notifications: VecDeque<JSONRPCNotification>,
}
impl CodexClient {
fn spawn(codex_bin: &Path, config_overrides: &[String]) -> Result<Self> {
fn connect(endpoint: &Endpoint, config_overrides: &[String]) -> Result<Self> {
match endpoint {
Endpoint::SpawnCodex(codex_bin) => Self::spawn_stdio(codex_bin, config_overrides),
Endpoint::ConnectWs(url) => Self::connect_websocket(url),
}
}
fn spawn_stdio(codex_bin: &Path, config_overrides: &[String]) -> Result<Self> {
let codex_bin_display = codex_bin.display();
let mut cmd = Command::new(codex_bin);
for override_kv in config_overrides {
@@ -492,9 +825,27 @@ impl CodexClient {
.context("codex app-server stdout unavailable")?;
Ok(Self {
child: codex_app_server,
stdin: Some(stdin),
stdout: BufReader::new(stdout),
transport: ClientTransport::Stdio {
child: codex_app_server,
stdin: Some(stdin),
stdout: BufReader::new(stdout),
},
pending_notifications: VecDeque::new(),
})
}
fn connect_websocket(url: &str) -> Result<Self> {
let parsed = Url::parse(url).with_context(|| format!("invalid websocket URL `{url}`"))?;
let (socket, _response) = connect(parsed.as_str()).with_context(|| {
format!(
"failed to connect to websocket app-server at `{url}`; if no server is running, start one with `codex-app-server-test-client serve --listen {url}`"
)
})?;
Ok(Self {
transport: ClientTransport::WebSocket {
url: url.to_string(),
socket: Box::new(socket),
},
pending_notifications: VecDeque::new(),
})
}
@@ -516,7 +867,16 @@ impl CodexClient {
},
};
self.send_request(request, request_id, "initialize")
let response: InitializeResponse = self.send_request(request, request_id, "initialize")?;
// Complete the initialize handshake.
let initialized = JSONRPCMessage::Notification(JSONRPCNotification {
method: "initialized".to_string(),
params: None,
});
self.write_jsonrpc_message(initialized)?;
Ok(response)
}
fn start_thread(&mut self) -> Result<NewConversationResponse> {
@@ -592,6 +952,16 @@ impl CodexClient {
self.send_request(request, request_id, "thread/start")
}
fn thread_resume(&mut self, params: ThreadResumeParams) -> Result<ThreadResumeResponse> {
let request_id = self.request_id();
let request = ClientRequest::ThreadResume {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "thread/resume")
}
fn turn_start(&mut self, params: TurnStartParams) -> Result<TurnStartResponse> {
let request_id = self.request_id();
let request = ClientRequest::TurnStart {
@@ -632,6 +1002,16 @@ impl CodexClient {
self.send_request(request, request_id, "model/list")
}
fn thread_list(&mut self, params: ThreadListParams) -> Result<ThreadListResponse> {
let request_id = self.request_id();
let request = ClientRequest::ThreadList {
request_id: request_id.clone(),
params,
};
self.send_request(request, request_id, "thread/list")
}
fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> {
loop {
let notification = self.next_notification()?;
@@ -766,6 +1146,12 @@ impl CodexClient {
Ok(())
}
fn stream_notifications_forever(&mut self) -> Result<()> {
loop {
let _ = self.next_notification()?;
}
}
fn extract_event(
&self,
notification: JSONRPCNotification,
@@ -813,17 +1199,7 @@ impl CodexClient {
let request_json = serde_json::to_string(request)?;
let request_pretty = serde_json::to_string_pretty(request)?;
print_multiline_with_prefix("> ", &request_pretty);
if let Some(stdin) = self.stdin.as_mut() {
writeln!(stdin, "{request_json}")?;
stdin
.flush()
.context("failed to flush request to codex app-server")?;
} else {
bail!("codex app-server stdin closed");
}
Ok(())
self.write_payload(&request_json)
}
fn wait_for_response<T>(&mut self, request_id: RequestId, method: &str) -> Result<T>
@@ -878,17 +1254,8 @@ impl CodexClient {
fn read_jsonrpc_message(&mut self) -> Result<JSONRPCMessage> {
loop {
let mut response_line = String::new();
let bytes = self
.stdout
.read_line(&mut response_line)
.context("failed to read from codex app-server")?;
if bytes == 0 {
bail!("codex app-server closed stdout");
}
let trimmed = response_line.trim();
let raw = self.read_payload()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
continue;
}
@@ -1017,16 +1384,56 @@ impl CodexClient {
let payload = serde_json::to_string(&message)?;
let pretty = serde_json::to_string_pretty(&message)?;
print_multiline_with_prefix("> ", &pretty);
self.write_payload(&payload)
}
if let Some(stdin) = self.stdin.as_mut() {
writeln!(stdin, "{payload}")?;
stdin
.flush()
.context("failed to flush response to codex app-server")?;
return Ok(());
fn write_payload(&mut self, payload: &str) -> Result<()> {
match &mut self.transport {
ClientTransport::Stdio { stdin, .. } => {
if let Some(stdin) = stdin.as_mut() {
writeln!(stdin, "{payload}")?;
stdin
.flush()
.context("failed to flush payload to codex app-server")?;
return Ok(());
}
bail!("codex app-server stdin closed")
}
ClientTransport::WebSocket { socket, url } => {
socket
.send(Message::Text(payload.to_string().into()))
.with_context(|| format!("failed to write websocket message to `{url}`"))?;
Ok(())
}
}
}
bail!("codex app-server stdin closed")
fn read_payload(&mut self) -> Result<String> {
match &mut self.transport {
ClientTransport::Stdio { stdout, .. } => {
let mut response_line = String::new();
let bytes = stdout
.read_line(&mut response_line)
.context("failed to read from codex app-server")?;
if bytes == 0 {
bail!("codex app-server closed stdout");
}
Ok(response_line)
}
ClientTransport::WebSocket { socket, url } => loop {
let frame = socket
.read()
.with_context(|| format!("failed to read websocket message from `{url}`"))?;
match frame {
Message::Text(text) => return Ok(text.to_string()),
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) => continue,
Message::Close(_) => {
bail!("websocket app-server at `{url}` closed the connection")
}
Message::Frame(_) => continue,
}
},
}
}
}
@@ -1038,21 +1445,25 @@ fn print_multiline_with_prefix(prefix: &str, payload: &str) {
impl Drop for CodexClient {
fn drop(&mut self) {
let _ = self.stdin.take();
let ClientTransport::Stdio { child, stdin, .. } = &mut self.transport else {
return;
};
if let Ok(Some(status)) = self.child.try_wait() {
let _ = stdin.take();
if let Ok(Some(status)) = child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
}
thread::sleep(Duration::from_millis(100));
if let Ok(Some(status)) = self.child.try_wait() {
if let Ok(Some(status)) = child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
}
let _ = self.child.kill();
let _ = self.child.wait();
let _ = child.kill();
let _ = child.wait();
}
}
+8 -2
View File
@@ -20,8 +20,8 @@ anyhow = { workspace = true }
async-trait = { workspace = true }
codex-arg0 = { workspace = true }
codex-cloud-requirements = { workspace = true }
codex-common = { workspace = true, features = ["cli"] }
codex-core = { workspace = true }
codex-utils-cli = { workspace = true }
codex-backend-client = { workspace = true }
codex-file-search = { workspace = true }
codex-chatgpt = { workspace = true }
@@ -30,8 +30,12 @@ codex-protocol = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-feedback = { workspace = true }
codex-rmcp-client = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-json-to-toml = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive"] }
futures = { workspace = true }
owo-colors = { workspace = true, features = ["supports-colors"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tempfile = { workspace = true }
@@ -44,6 +48,7 @@ tokio = { workspace = true, features = [
"rt-multi-thread",
"signal",
] }
tokio-tungstenite = { workspace = true }
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
uuid = { workspace = true, features = ["serde", "v7"] }
@@ -57,8 +62,8 @@ axum = { workspace = true, default-features = false, features = [
] }
base64 = { workspace = true }
codex-execpolicy = { workspace = true }
codex-utils-absolute-path = { workspace = true }
core_test_support = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
os_info = { workspace = true }
pretty_assertions = { workspace = true }
rmcp = { workspace = true, default-features = false, features = [
@@ -66,5 +71,6 @@ rmcp = { workspace = true, default-features = false, features = [
"transport-streamable-http-server",
] }
serial_test = { workspace = true }
tokio-tungstenite = { workspace = true }
wiremock = { workspace = true }
shlex = { workspace = true }
+33 -8
View File
@@ -19,7 +19,20 @@
## Protocol
Similar to [MCP](https://modelcontextprotocol.io/), `codex app-server` supports bidirectional communication, streaming JSONL over stdio. The protocol is JSON-RPC 2.0, though the `"jsonrpc":"2.0"` header is omitted.
Similar to [MCP](https://modelcontextprotocol.io/), `codex app-server` supports bidirectional communication using JSON-RPC 2.0 messages (with the `"jsonrpc":"2.0"` header omitted on the wire).
Supported transports:
- stdio (`--listen stdio://`, default): newline-delimited JSON (JSONL)
- websocket (`--listen ws://IP:PORT`): one JSON-RPC message per websocket text frame (**experimental / unsupported**)
Websocket transport is currently experimental and unsupported. Do not rely on it for production workloads.
Backpressure behavior:
- The server uses bounded queues between transport ingress, request processing, and outbound writes.
- When request ingress is saturated, new requests are rejected with a JSON-RPC error code `-32001` and message `"Server overloaded; retry later."`.
- Clients should treat this as retryable and use exponential backoff with jitter.
## Message Schema
@@ -42,7 +55,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
## Lifecycle Overview
- Initialize once: Immediately after launching the codex app-server process, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request before this handshake gets rejected.
- Initialize once per connection: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request on that connection before this handshake gets rejected.
- Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and you’ll also get a `thread/started` notification. If you’re continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history.
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object and triggers a `turn/started` notification.
- Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. You’ll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
@@ -50,7 +63,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
## Initialization
Clients must send a single `initialize` request before invoking any other method, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls receive an `"Already initialized"` error.
Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error.
`initialize.params.capabilities` also supports per-connection notification opt-out via `optOutNotificationMethods`, which is a list of exact method names to suppress for that connection. Matching is exact (no wildcards/prefixes). Unknown method names are accepted and ignored.
@@ -104,7 +117,7 @@ Example with notification opt-out:
- `thread/start` — create a new thread; emits `thread/started` and auto-subscribes you to turn/item events for that thread.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; emits `thread/started` and auto-subscribes you to turn/item events for the new thread.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders` filtering.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, and `cwd` filters.
- `thread/loaded/list` — list the thread ids currently loaded in memory.
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`.
- `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success.
@@ -118,7 +131,7 @@ Example with notification opt-out:
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
- `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review.
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
- `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids).
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options and optional `upgrade` model ids.
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination).
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
@@ -196,6 +209,8 @@ To branch from a stored session, call `thread/fork` with the `thread.id`. This c
{ "method": "thread/started", "params": { "thread": { … } } }
```
Experimental API: `thread/start`, `thread/resume`, and `thread/fork` accept `persistExtendedHistory: true` to persist a richer subset of ThreadItems for non-lossy history when calling `thread/read`, `thread/resume`, and `thread/fork` later. This does not backfill events that were not persisted previously.
### Example: List threads (with pagination & filters)
`thread/list` lets you render a history UI. Results default to `createdAt` (newest first) descending. Pass any combination of:
@@ -206,6 +221,7 @@ To branch from a stored session, call `thread/fork` with the `thread.id`. This c
- `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers.
- `sourceKinds` — restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`).
- `archived` — when `true`, list archived threads only. When `false` or `null`, list non-archived threads (default).
- `cwd` — restrict results to threads whose session cwd exactly matches this path.
Example:
@@ -508,6 +524,13 @@ Examples:
- Opt out of legacy session setup event: `codex/event/session_configured`
- Opt out of streamed agent text deltas: `item/agentMessage/delta`
### Fuzzy file search events (experimental)
The fuzzy file search session API emits per-query notifications:
- `fuzzyFileSearch/sessionUpdated` — `{ sessionId, query, files }` with the current matching files for the active query.
- `fuzzyFileSearch/sessionCompleted` — `{ sessionId, query }` once indexing/matching for that query has completed.
### Turn events
The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` status). Token usage events stream separately via `thread/tokenUsage/updated`. Clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`.
@@ -737,7 +760,7 @@ To enable or disable a skill by path:
## Apps
Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, and whether it is currently accessible.
Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, whether it is currently accessible, and whether it is enabled in config.
```json
{ "method": "app/list", "id": 50, "params": {
@@ -756,7 +779,8 @@ Use `app/list` to fetch available apps (connectors). Each entry includes metadat
"logoUrlDark": null,
"distributionChannel": null,
"installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
"isAccessible": true
"isAccessible": true,
"isEnabled": true
}
],
"nextCursor": null
@@ -782,7 +806,8 @@ The server also emits `app/list/updated` notifications whenever either source (a
"logoUrlDark": null,
"distributionChannel": null,
"installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
"isAccessible": true
"isAccessible": true,
"isEnabled": true
}
]
}

Some files were not shown because too many files have changed in this diff Show More