.tar.gz (new)
-
- # 1. Produce a .tar.gz for every file in the directory *before* we
- # run `zstd --rm`, because that flag deletes the original files.
- 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" == *.tar.zst || "$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"
-
- # Also create .zst and remove the uncompressed binaries to keep
- # non-Windows artifact directories small.
- zstd -T0 -19 --rm "$dest/$base"
- done
-
- - name: Suffix Bazel artifact filenames
- if: ${{ matrix.build-system == 'bazel' && runner.os != 'macOS' }}
- shell: bash
- run: |
- set -euo pipefail
- for artifact in "dist/${{ matrix.target }}"/*; do
- name="${artifact##*/}"
- stem="${name%%.*}"
- mv "$artifact" "${artifact%/*}/${stem}-bazel${name#"$stem"}"
- done
-
- - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- if: ${{ runner.os != 'macOS' }}
- with:
- name: ${{ matrix.artifact_name }}${{ env.ARTIFACT_SUFFIX }}
- # Upload the per-binary .zst files, .tar.gz equivalents, and any
- # prebuilt archives staged above.
- path: |
- codex-rs/dist/${{ matrix.target }}/*
-
- build-macos-voice:
- needs: tag-check
- name: Build voice runtime - ${{ matrix.target }}
- runs-on: ${{ matrix.runner }}
- environment:
- name: bazel
- deployment: false
- timeout-minutes: 120
- permissions:
- contents: read
- strategy:
- fail-fast: false
- matrix:
- include:
- - target: aarch64-apple-darwin
- runner: macos-15
- prefix: macos_aarch64
- - target: x86_64-apple-darwin
- runner: macos-15-intel
- prefix: macos_x86_64
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: "3.12"
- - name: Prepare Bazel CI
- uses: ./.github/actions/prepare-bazel-ci
- with:
- target: ${{ matrix.target }}
- cache-scope: bazel-release-primary
- - name: Build matching helper and native runtime
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- PREFIX: ${{ matrix.prefix }}
- BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
- run: |
- set -euo pipefail
- # Release tags bump Cargo.toml without rewriting the workspace lockfile.
- # Refresh workspace versions before Bazel reads the Cargo dependency graph.
- (cd codex-rs && cargo update --workspace)
- python3 .github/scripts/run_bazel_with_buildbuddy.py build --config=release \
- --macos_minimum_os=14.0 //codex-rs/voice-host:codex-voice-host //third_party/voice:native_runtime
- runtime="bazel-bin/third_party/voice/native_runtime_${PREFIX}"
- PYTHONPATH=third_party/voice python3 - "$runtime" "$TARGET" <<'PY'
- from pathlib import Path
- import sys
- from package_runtime import runtime_files
- runtime_files(Path(sys.argv[1]).resolve(strict=True), sys.argv[2])
- PY
- mkdir -p "voice-unsigned/${TARGET}"
- cp -R "$runtime" "voice-unsigned/${TARGET}/runtime"
- # GNU tar extraction and signing need writable copies of Bazel outputs.
- chmod -R u+w "voice-unsigned/${TARGET}/runtime"
- cp bazel-bin/codex-rs/voice-host/codex-voice-host "voice-unsigned/${TARGET}/codex-voice-host"
- tar -C "voice-unsigned/${TARGET}" -czf "voice-unsigned-${TARGET}.tar.gz" runtime codex-voice-host
- - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: voice-${{ matrix.target }}-unsigned
- path: voice-unsigned-${{ matrix.target }}.tar.gz
- if-no-files-found: error
-
- sign-macos-binaries:
- needs: [build, build-macos-voice]
- if: ${{ always() && needs.build.result == 'success' && needs.build-macos-voice.result == 'success' }}
- name: Sign macOS binaries - ${{ matrix.target }} - ${{ matrix.bundle }} - ${{ matrix.build-system }}
- continue-on-error: ${{ matrix.build-system == 'bazel' }}
- runs-on: ubuntu-latest
- timeout-minutes: 45
- environment:
- name: codesigning
- deployment: false
- permissions:
- contents: read
- id-token: write
-
- env:
- ARTIFACT_SUFFIX: ${{ matrix.build-system == 'bazel' && '-bazel' || '' }}
-
- strategy:
- fail-fast: false
- matrix:
- build-system: [cargo, bazel]
- target:
- - aarch64-apple-darwin
- - x86_64-apple-darwin
- bundle: [primary, app-server]
- include:
- - target: aarch64-apple-darwin
- bundle: primary
- artifact_name: aarch64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- - target: aarch64-apple-darwin
- bundle: app-server
- artifact_name: aarch64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
- - target: x86_64-apple-darwin
- bundle: primary
- artifact_name: x86_64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- - target: x86_64-apple-darwin
- bundle: app-server
- artifact_name: x86_64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Download unsigned macOS binaries
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-unsigned${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/unsigned-macos
-
- - name: Download unsigned voice runtime
- if: ${{ matrix.bundle == 'primary' }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: voice-${{ matrix.target }}-unsigned
- path: ${{ runner.temp }}/unsigned-voice
-
- - name: Set up AKV PKCS11 macOS signing
- uses: ./.github/actions/setup-akv-pkcs11-codesigning
- with:
- rcodesign-blob-uri: ${{ secrets.AKV_CODESIGN_RCODESIGN_BLOB_URI }}
- rcodesign-sha256: ${{ secrets.AKV_CODESIGN_RCODESIGN_SHA256 }}
- akv-pkcs11-library-blob-uri: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_BLOB_URI }}
- akv-pkcs11-library-sha256: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_SHA256 }}
- azure-client-id: ${{ secrets.AKV_CODESIGN_AZURE_CLIENT_ID }}
- azure-tenant-id: ${{ secrets.AKV_CODESIGN_TENANT }}
- azure-subscription-id: ${{ secrets.AKV_CODESIGN_SUBSCRIPTION }}
- key-vault-name: ${{ secrets.AKV_CODESIGN_KEY_VAULT_NAME }}
- key-name: ${{ secrets.AKV_CODESIGN_KEY_NAME }}
- key-version: ${{ secrets.AKV_CODESIGN_KEY_VERSION || '' }}
- certificate-sha256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 || '' }}
-
- - name: Sign and notarize macOS binaries
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- BINARIES: ${{ matrix.binaries }}
- APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
- APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
- run: |
- set -euo pipefail
-
- input_dir="${RUNNER_TEMP}/unsigned-macos"
- output_dir="${GITHUB_WORKSPACE}/signed-macos/${TARGET}"
- report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}"
- mkdir -p "$output_dir" "$report_dir"
-
- for binary in ${BINARIES}; do
- unsigned_path="${input_dir}/${binary}-${TARGET}-unsigned.zst"
- signed_path="${output_dir}/${binary}"
- if [[ ! -f "$unsigned_path" ]]; then
- echo "Unsigned binary $unsigned_path not found"
- exit 1
- fi
-
- zstd -d --stdout "$unsigned_path" >"$signed_path"
- chmod 0755 "$signed_path"
-
- entitlements="${GITHUB_WORKSPACE}/.github/scripts/macos-signing/${binary}.entitlements.plist"
- if [[ ! -f "$entitlements" ]]; then
- echo "Entitlements file $entitlements not found"
- exit 1
- fi
-
- .github/scripts/macos-signing/sign_macos_code.sh \
- --target "$signed_path" \
- --identity unused \
- --deep false \
- --identifier "$binary" \
- --options runtime \
- --timestamp true \
- --entitlements "$entitlements"
-
- mkdir -p "${report_dir}/${binary}"
- rcodesign print-signature-info "$signed_path" \
- >"${report_dir}/${binary}/signature-info.yaml"
-
- .github/scripts/macos-signing/notarize_macos_binary_with_akv.sh \
- --binary "$signed_path" \
- --report-dir "${report_dir}/${binary}"
- done
-
- - name: Fetch, sign, and notarize pinned macOS helpers
- if: ${{ matrix.bundle == 'primary' }}
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
- APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
- run: |
- set -euo pipefail
-
- signed_root="${GITHUB_WORKSPACE}/signed-resources/${TARGET}"
- mkdir -p "$signed_root"
-
- PYTHONPATH="${GITHUB_WORKSPACE}/scripts" python3 - "$TARGET" "$signed_root" <<'PY'
- import shutil
- import sys
- from pathlib import Path
-
- from codex_package.ripgrep import fetch_rg
- from codex_package.targets import TARGET_SPECS
-
- spec = TARGET_SPECS[sys.argv[1]]
- signed_root = Path(sys.argv[2])
- shutil.copy2(fetch_rg(spec), signed_root / "rg")
- PY
-
- for resource in rg; do
- binary="${signed_root}/${resource}"
- report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/${resource}"
- mkdir -p "$report_dir"
- chmod 0755 "$binary"
- .github/scripts/macos-signing/sign_macos_code.sh \
- --target "$binary" \
- --identity unused \
- --deep false \
- --identifier "com.openai.codex.${resource}" \
- --options runtime \
- --timestamp true
-
- rcodesign print-signature-info "$binary" \
- >"${report_dir}/signature-info.yaml"
-
- .github/scripts/macos-signing/notarize_macos_binary_with_akv.sh \
- --binary "$binary" \
- --report-dir "$report_dir"
- done
-
- - name: Sign and notarize voice runtime
- if: ${{ matrix.bundle == 'primary' }}
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
- APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
- run: |
- set -euo pipefail
- unsigned="${RUNNER_TEMP}/unsigned-voice/extracted"
- signed="${GITHUB_WORKSPACE}/signed-voice/${TARGET}"
- mkdir -p "$unsigned" "$signed"
- tar -xzf "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${TARGET}.tar.gz" -C "$unsigned"
- python3 third_party/voice/release_runtime.py stage \
- --target "$TARGET" --source "$unsigned/runtime" --output "$signed/runtime"
- cp "$unsigned/codex-voice-host" "$signed/codex-voice-host"
- chmod 0755 "$signed/codex-voice-host"
-
- while IFS= read -r -d '' library; do
- name="$(basename "$library")"
- .github/scripts/macos-signing/sign_macos_code.sh \
- --target "$library" --identity unused --deep false \
- --identifier "com.openai.codex.voice.${name}" \
- --options runtime --timestamp true
- done < <(find "$signed/runtime" -name '*.dylib' -type f -print0)
- .github/scripts/macos-signing/sign_macos_code.sh \
- --target "$signed/codex-voice-host" --identity unused --deep false \
- --identifier com.openai.codex.voice-host --options runtime --timestamp true \
- --entitlements .github/scripts/macos-signing/codex-voice-host.entitlements.plist
-
- # Notarize the actual signed closure together, then hash those bytes.
- (cd "$signed" && zip -qr "${RUNNER_TEMP}/voice-${TARGET}.zip" runtime codex-voice-host)
- report_dir="${GITHUB_WORKSPACE}/macos-binary-signing-verification/${TARGET}/voice"
- mkdir -p "$report_dir"
- python3 .github/scripts/macos-signing/notarize_with_akv.py \
- --file "${RUNNER_TEMP}/voice-${TARGET}.zip" \
- --report-log "${report_dir}/notarization.json" \
- --max-wait-seconds 600
- python3 third_party/voice/release_runtime.py seal \
- --target "$TARGET" --output "$signed/runtime"
- tar -C "$signed" -czf "${GITHUB_WORKSPACE}/signed-voice-${TARGET}.tar.gz" \
- runtime codex-voice-host
-
- - name: Upload signed voice runtime
- if: ${{ matrix.bundle == 'primary' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: voice-${{ matrix.target }}-signed${{ env.ARTIFACT_SUFFIX }}
- path: signed-voice-${{ matrix.target }}.tar.gz
- if-no-files-found: error
-
- - name: Upload signed macOS binaries
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-signed-binaries${{ env.ARTIFACT_SUFFIX }}
- path: signed-macos/${{ matrix.target }}/*
- if-no-files-found: error
-
- - name: Upload signed macOS helpers
- if: ${{ matrix.bundle == 'primary' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.target }}-signed-resources${{ env.ARTIFACT_SUFFIX }}
- path: signed-resources/${{ matrix.target }}/*
- if-no-files-found: error
-
- - name: Upload binary signing verification
- if: ${{ always() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-binary-signing-verification${{ env.ARTIFACT_SUFFIX }}
- path: macos-binary-signing-verification/${{ matrix.target }}/
- if-no-files-found: warn
-
- package-macos:
- needs: sign-macos-binaries
- name: Package macOS artifacts - ${{ matrix.target }} - ${{ matrix.bundle }} - ${{ matrix.build-system }}
- continue-on-error: ${{ matrix.build-system == 'bazel' }}
- runs-on: macos-15-xlarge
- timeout-minutes: 45
- permissions:
- contents: read
- defaults:
- run:
- working-directory: codex-rs
-
- env:
- ARTIFACT_SUFFIX: ${{ matrix.build-system == 'bazel' && '-bazel' || '' }}
-
- strategy:
- fail-fast: false
- matrix:
- build-system: [cargo, bazel]
- target:
- - aarch64-apple-darwin
- - x86_64-apple-darwin
- bundle: [primary, app-server]
- include:
- - target: aarch64-apple-darwin
- bundle: primary
- artifact_name: aarch64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- build_dmg: "true"
- - target: aarch64-apple-darwin
- bundle: app-server
- artifact_name: aarch64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
- build_dmg: "false"
- - target: x86_64-apple-darwin
- bundle: primary
- artifact_name: x86_64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- build_dmg: "true"
- - target: x86_64-apple-darwin
- bundle: app-server
- artifact_name: x86_64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
- build_dmg: "false"
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Download signed macOS binaries
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-signed-binaries${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/target/${{ matrix.target }}/release
-
- - name: Download signed macOS helpers
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.target }}-signed-resources${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/signed-resources/${{ matrix.target }}
-
- - name: Download signed voice runtime
- if: ${{ matrix.bundle == 'primary' }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: voice-${{ matrix.target }}-signed${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/signed-voice
-
- - name: Verify signed voice runtime
- if: ${{ matrix.bundle == 'primary' }}
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- run: |
- set -euo pipefail
- root="${RUNNER_TEMP}/signed-voice/${TARGET}"
- mkdir -p "$root"
- tar -xzf "${RUNNER_TEMP}/signed-voice/signed-voice-${TARGET}.tar.gz" -C "$root"
- case "$TARGET" in
- aarch64-apple-darwin) arch=arm64 ;;
- x86_64-apple-darwin) arch=x86_64 ;;
- *) exit 1 ;;
- esac
- PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$root/runtime" "$TARGET" <<'PY'
- from pathlib import Path
- import sys
- from package_runtime import runtime_files
- runtime_files(Path(sys.argv[1]).resolve(strict=True), sys.argv[2], public_release=True)
- PY
- while IFS= read -r -d '' binary; do
- lipo "$binary" -verify_arch "$arch"
- codesign --verify --strict --verbose=2 "$binary"
- done < <(find "$root/runtime" -name '*.dylib' -type f -print0)
- lipo "$root/codex-voice-host" -verify_arch "$arch"
- codesign --verify --strict --verbose=2 "$root/codex-voice-host"
-
- - name: Verify signed macOS binaries
- shell: bash
- run: |
- set -euo pipefail
- for binary in ${{ matrix.binaries }}; do
- binary_path="target/${{ matrix.target }}/release/${binary}"
- chmod 0755 "$binary_path"
- codesign --verify --strict --verbose=2 "$binary_path"
- done
-
- for resource in rg; do
- resource_path="signed-resources/${{ matrix.target }}/${resource}"
- chmod 0755 "$resource_path"
- codesign --verify --strict --verbose=2 "$resource_path"
- done
-
- - name: Stage macOS artifacts
- shell: bash
- run: |
- set -euo pipefail
- dest="dist/${{ matrix.target }}"
- mkdir -p "$dest"
-
- for binary in ${{ matrix.binaries }}; do
- # Both variants package the host, but only the primary bundle publishes
- # standalone binary archives to avoid duplicate release asset names.
- if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
- continue
- fi
- cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
- done
-
- - name: Build Codex package archive
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- BUNDLE: ${{ matrix.bundle }}
- run: |
- set -euo pipefail
- voice_args=()
- if [[ "$BUNDLE" == "primary" ]]; then
- voice_args+=(--voice-release-dir "${RUNNER_TEMP}/signed-voice/${TARGET}")
- voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}")
- fi
- bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
- --target "$TARGET" \
- --bundle "$BUNDLE" \
- --entrypoint-dir "target/${TARGET}/release" \
- --archive-dir "dist/${TARGET}" \
- --rg-bin "signed-resources/${TARGET}/rg" \
- ${voice_args[@]+"${voice_args[@]}"}
-
- - name: Build unsigned macOS DMG
- if: ${{ matrix.build_dmg == 'true' }}
- shell: bash
- run: |
- set -euo pipefail
-
- target="${{ matrix.target }}"
- release_dir="target/${target}/release"
- dmg_root="${RUNNER_TEMP}/codex-dmg-root-${target}"
- volname="Codex (${target})"
- dmg_path="${release_dir}/codex-${target}.dmg"
-
- rm -rf "$dmg_root"
- mkdir -p "$dmg_root"
-
- for binary in ${{ matrix.binaries }}; do
- binary_path="${release_dir}/${binary}"
- if [[ ! -f "$binary_path" ]]; then
- echo "Binary $binary_path not found"
- exit 1
- fi
- ditto "$binary_path" "${dmg_root}/${binary}"
- done
-
- if [[ "${{ matrix.bundle }}" == "primary" ]]; then
- # Keep the root-level invocation, but resolve it into the canonical
- # package layout so InstallContext finds its adjacent voice runtime.
- package="${RUNNER_TEMP}/codex-package-voice-${target}"
- ditto "$package" "$dmg_root"
- rm "${dmg_root}/codex"
- ln -s bin/codex "${dmg_root}/codex"
- fi
-
- rm -f "$dmg_path"
- hdiutil create \
- -volname "$volname" \
- -srcfolder "$dmg_root" \
- -format UDZO \
- -ov \
- "$dmg_path"
-
- if [[ ! -f "$dmg_path" ]]; then
- echo "DMG $dmg_path not found after build"
- exit 1
- fi
-
- - name: Upload unsigned macOS DMG
- if: ${{ matrix.build_dmg == 'true' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-unsigned-dmg${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg
- if-no-files-found: error
-
- - name: Set up Python release tools
- if: ${{ matrix.build-system == 'cargo' && matrix.bundle == 'primary' }}
- uses: ./.github/actions/setup-python-release-tools
- with:
- group: runtime
-
- - name: Build Python runtime wheel
- if: ${{ matrix.build-system == 'cargo' && matrix.bundle == 'primary' }}
- shell: bash
- run: |
- set -euo pipefail
-
- case "${{ matrix.target }}" in
- aarch64-apple-darwin)
- platform_tag="macosx_11_0_arm64"
- ;;
- x86_64-apple-darwin)
- platform_tag="macosx_10_9_x86_64"
- ;;
- *)
- echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
- exit 1
- ;;
- esac
-
- # The wheel keeps its macOS 10.9/11 tags. The native voice build
- # currently targets macOS 14, so keep it in the release archives and
- # DMG but leave this older-OS-compatible wheel unchanged.
- wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}"
- bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
- --target "${{ matrix.target }}" \
- --bundle primary \
- --entrypoint-dir "target/${{ matrix.target }}/release" \
- --archive-dir "$wheel_archives" \
- --rg-bin "signed-resources/${{ matrix.target }}/rg"
- wheel_archive="${wheel_archives}/codex-package-${{ matrix.target }}.tar.gz"
- python3 - "$wheel_archive" <<'PY'
- import sys
- import tarfile
- with tarfile.open(sys.argv[1]) as archive:
- assert not any("codex-resources/voice/" in item.name for item in archive)
- PY
-
- stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
- wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
- python3 \
- "${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
- stage-runtime \
- "$stage_dir" \
- "$wheel_archive" \
- --codex-version "${GITHUB_REF_NAME}" \
- --platform-tag "$platform_tag"
- uv build --offline --no-build-isolation --no-create-gitignore \
- --python "${RUNNER_TEMP}/python-runtime-build-venv" --wheel --out-dir "$wheel_dir" "$stage_dir"
-
- - name: Upload Python runtime wheel
- if: ${{ matrix.build-system == 'cargo' && matrix.bundle == 'primary' }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: python-runtime-wheel-${{ matrix.target }}
- path: python-runtime-dist/${{ matrix.target }}/*.whl
- if-no-files-found: error
-
- - name: Compress artifacts
- shell: bash
- run: |
- set -euo pipefail
- dest="dist/${{ matrix.target }}"
- for f in "$dest"/*; do
- base="$(basename "$f")"
- if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
- continue
- fi
-
- tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
- zstd -T0 -19 --rm "$dest/$base"
- done
-
- - name: Upload packaged macOS artifacts
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-packaged${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/dist/${{ matrix.target }}/*
- if-no-files-found: error
-
- sign-macos-dmg:
- needs: package-macos
- name: Sign macOS DMG - ${{ matrix.target }} - ${{ matrix.build-system }}
- continue-on-error: ${{ matrix.build-system == 'bazel' }}
- runs-on: ubuntu-latest
- timeout-minutes: 45
- environment:
- name: codesigning
- deployment: false
- permissions:
- contents: read
- id-token: write
-
- env:
- ARTIFACT_SUFFIX: ${{ matrix.build-system == 'bazel' && '-bazel' || '' }}
-
- strategy:
- fail-fast: false
- matrix:
- build-system: [cargo, bazel]
- target:
- - aarch64-apple-darwin
- - x86_64-apple-darwin
- include:
- - target: aarch64-apple-darwin
- artifact_name: aarch64-apple-darwin
- - target: x86_64-apple-darwin
- artifact_name: x86_64-apple-darwin
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Download unsigned macOS DMG
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-unsigned-dmg${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/unsigned-dmg
-
- - name: Set up AKV PKCS11 macOS signing
- uses: ./.github/actions/setup-akv-pkcs11-codesigning
- with:
- rcodesign-blob-uri: ${{ secrets.AKV_CODESIGN_RCODESIGN_BLOB_URI }}
- rcodesign-sha256: ${{ secrets.AKV_CODESIGN_RCODESIGN_SHA256 }}
- akv-pkcs11-library-blob-uri: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_BLOB_URI }}
- akv-pkcs11-library-sha256: ${{ secrets.AKV_CODESIGN_PKCS11_LIBRARY_SHA256 }}
- azure-client-id: ${{ secrets.AKV_CODESIGN_AZURE_CLIENT_ID }}
- azure-tenant-id: ${{ secrets.AKV_CODESIGN_TENANT }}
- azure-subscription-id: ${{ secrets.AKV_CODESIGN_SUBSCRIPTION }}
- key-vault-name: ${{ secrets.AKV_CODESIGN_KEY_VAULT_NAME }}
- key-name: ${{ secrets.AKV_CODESIGN_KEY_NAME }}
- key-version: ${{ secrets.AKV_CODESIGN_KEY_VERSION || '' }}
- certificate-sha256: ${{ secrets.AKV_CODESIGN_CERTIFICATE_SHA256 || '' }}
-
- - name: Sign, notarize, and staple macOS DMG
- shell: bash
- env:
- TARGET: ${{ matrix.target }}
- APPLE_NOTARIZATION_AKV_KEY_NAME: ${{ secrets.AKV_NOTARIZATION_KEY_NAME }}
- APPLE_NOTARIZATION_AKV_KEY_VERSION: ${{ secrets.AKV_NOTARIZATION_KEY_VERSION }}
- run: |
- set -euo pipefail
-
- dmg_path="${RUNNER_TEMP}/unsigned-dmg/codex-${TARGET}.dmg"
- report_dir="${GITHUB_WORKSPACE}/macos-dmg-signing-verification/${TARGET}"
- if [[ ! -f "$dmg_path" ]]; then
- echo "Unsigned DMG $dmg_path not found"
- exit 1
- fi
-
- .github/scripts/macos-signing/sign_macos_code.sh \
- --target "$dmg_path" \
- --identity unused \
- --deep false \
- --timestamp true
-
- mkdir -p "$report_dir"
- rcodesign print-signature-info "$dmg_path" \
- >"${report_dir}/signature-info-before-notarization.yaml"
-
- .github/scripts/macos-signing/notarize_macos_dmg_with_akv.sh \
- --dmg "$dmg_path" \
- --report-dir "$report_dir"
-
- rcodesign print-signature-info "$dmg_path" \
- >"${report_dir}/signature-info.yaml"
-
- - name: Upload signed macOS DMG
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-signed-dmg${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/unsigned-dmg/codex-${{ matrix.target }}.dmg
- if-no-files-found: error
-
- - name: Upload DMG signing verification
- if: ${{ always() }}
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}-dmg-signing-verification${{ env.ARTIFACT_SUFFIX }}
- path: macos-dmg-signing-verification/${{ matrix.target }}/
- if-no-files-found: warn
-
- finalize-macos:
- needs:
- - package-macos
- - sign-macos-dmg
- name: Verify macOS artifacts - ${{ matrix.target }} - ${{ matrix.bundle }} - ${{ matrix.build-system }}
- continue-on-error: ${{ matrix.build-system == 'bazel' }}
- runs-on: macos-15-xlarge
- timeout-minutes: 30
- permissions:
- contents: read
- defaults:
- run:
- working-directory: codex-rs
-
- env:
- ARTIFACT_SUFFIX: ${{ matrix.build-system == 'bazel' && '-bazel' || '' }}
-
- strategy:
- fail-fast: false
- matrix:
- build-system: [cargo, bazel]
- target:
- - aarch64-apple-darwin
- - x86_64-apple-darwin
- bundle: [primary, app-server]
- include:
- - target: aarch64-apple-darwin
- bundle: primary
- artifact_name: aarch64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- verify_dmg: "true"
- - target: aarch64-apple-darwin
- bundle: app-server
- artifact_name: aarch64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
- verify_dmg: "false"
- - target: x86_64-apple-darwin
- bundle: primary
- artifact_name: x86_64-apple-darwin
- binaries: "codex codex-code-mode-host codex-responses-api-proxy"
- verify_dmg: "true"
- - target: x86_64-apple-darwin
- bundle: app-server
- artifact_name: x86_64-apple-darwin-app-server
- binaries: "codex-app-server codex-code-mode-host"
- verify_dmg: "false"
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Download packaged macOS artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-packaged${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/dist/${{ matrix.target }}
-
- - name: Download signed macOS binaries
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-signed-binaries${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/signed-binaries
-
- - name: Download signed macOS DMG
- if: ${{ matrix.verify_dmg == 'true' }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: ${{ matrix.artifact_name }}-signed-dmg${{ env.ARTIFACT_SUFFIX }}
- path: ${{ runner.temp }}/signed-dmg
-
- - name: Verify signed macOS artifacts
- shell: bash
- run: |
- set -euo pipefail
-
- target="${{ matrix.target }}"
- packaged_dir="dist/${target}"
- case "$target" in
- aarch64-apple-darwin) expected_arch="arm64" ;;
- x86_64-apple-darwin) expected_arch="x86_64" ;;
- *)
- echo "Unexpected macOS target: $target"
- exit 1
- ;;
- esac
-
- verify_signed_binary() {
- local path="$1"
- local binary="$2"
- local actual_entitlements expected_entitlements normalized_actual normalized_expected
-
- chmod 0755 "$path"
- lipo "$path" -verify_arch "$expected_arch"
- codesign --verify --strict --verbose=2 "$path"
-
- expected_entitlements="${GITHUB_WORKSPACE}/.github/scripts/macos-signing/${binary}.entitlements.plist"
- if [[ ! -f "$expected_entitlements" ]]; then
- echo "Expected entitlements file $expected_entitlements not found"
- exit 1
- fi
- actual_entitlements="$(mktemp)"
- normalized_actual="$(mktemp)"
- normalized_expected="$(mktemp)"
- codesign -d --entitlements :- "$path" >"$actual_entitlements"
- plutil -convert xml1 -o "$normalized_actual" "$actual_entitlements"
- plutil -convert xml1 -o "$normalized_expected" "$expected_entitlements"
- diff -u "$normalized_expected" "$normalized_actual"
- rm -f "$actual_entitlements" "$normalized_actual" "$normalized_expected"
- }
-
- for binary in ${{ matrix.binaries }}; do
- binary_path="${RUNNER_TEMP}/signed-binaries/${binary}"
- verify_signed_binary "$binary_path" "$binary"
-
- # The app-server package contains the host, but its standalone archives
- # are omitted above to avoid duplicate release asset names.
- if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
- continue
- fi
-
- direct_archive_dir="${RUNNER_TEMP}/direct-archive-${binary}-${target}"
- rm -rf "$direct_archive_dir"
- mkdir -p "$direct_archive_dir"
- tar -xzf "${packaged_dir}/${binary}-${target}.tar.gz" -C "$direct_archive_dir"
- verify_signed_binary "${direct_archive_dir}/${binary}-${target}" "$binary"
-
- direct_zstd_path="${RUNNER_TEMP}/${binary}-${target}-from-zstd"
- zstd -d --stdout "${packaged_dir}/${binary}-${target}.zst" >"$direct_zstd_path"
- verify_signed_binary "$direct_zstd_path" "$binary"
- done
-
- case "${{ matrix.bundle }}" in
- primary)
- package_stem="codex-package"
- package_entrypoint="codex"
- ;;
- app-server)
- package_stem="codex-app-server-package"
- package_entrypoint="codex-app-server"
- ;;
- *)
- echo "Unexpected macOS bundle: ${{ matrix.bundle }}"
- exit 1
- ;;
- esac
-
- package_dir="${RUNNER_TEMP}/${package_stem}-${target}"
- rm -rf "$package_dir"
- mkdir -p "$package_dir"
- tar -xzf "${packaged_dir}/${package_stem}-${target}.tar.gz" -C "$package_dir"
- verify_signed_binary "${package_dir}/bin/${package_entrypoint}" "$package_entrypoint"
- verify_signed_binary "${package_dir}/bin/codex-code-mode-host" "codex-code-mode-host"
-
- for resource in \
- "${package_dir}/codex-path/rg"
- do
- chmod 0755 "$resource"
- lipo "$resource" -verify_arch "$expected_arch"
- codesign --verify --strict --verbose=2 "$resource"
- entitlements="$(mktemp)"
- codesign -d --entitlements :- "$resource" >"$entitlements"
- if [[ -s "$entitlements" ]]; then
- echo "Bundled helper $resource must not have code-signing entitlements." >&2
- plutil -p "$entitlements" >&2
- exit 1
- fi
- rm -f "$entitlements"
- done
-
- if [[ "${{ matrix.bundle }}" == "primary" ]]; then
- voice="${package_dir}/codex-resources/voice"
- [[ -f "${voice}/lib/libgstreamer-1.0.0.dylib" ]]
- export VOICE_BUILD_COMMIT="$(git rev-parse HEAD)"
- PYTHONPATH="${GITHUB_WORKSPACE}/third_party/voice" python3 - "$voice" "$target" "$package_dir" <<'PY'
- import json
- import sys
- from pathlib import Path
- from package_runtime import runtime_files
- from runtime import digest
-
- voice, target, package = map(Path, sys.argv[1:])
- runtime_files(voice.resolve(strict=True), str(target), public_release=True)
- manifest = json.loads((voice / "manifest.json").read_text())
- assert manifest["buildCommit"] == __import__("os").environ["VOICE_BUILD_COMMIT"]
- for relative, expected in manifest["sha256"].items():
- assert digest(package / relative) == expected, relative
- PY
- helper="${voice}/bin/codex-voice-host"
- [[ "$("$helper" --build-commit)" == "$VOICE_BUILD_COMMIT" ]]
- while IFS= read -r -d '' library; do
- lipo "$library" -verify_arch "$expected_arch"
- codesign --verify --strict --verbose=2 "$library"
- done < <(find "$voice" -name '*.dylib' -type f -print0)
- verify_signed_binary "$helper" "codex-voice-host"
- fi
-
- if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then
- exit 0
- fi
-
- dmg_path="${RUNNER_TEMP}/signed-dmg/codex-${target}.dmg"
- mount_dir="${RUNNER_TEMP}/codex-dmg-mount-${target}"
- if [[ ! -f "$dmg_path" ]]; then
- echo "Signed DMG $dmg_path not found"
- exit 1
- fi
-
- hdiutil verify "$dmg_path"
- codesign --verify --strict --verbose=2 "$dmg_path"
- xcrun stapler validate "$dmg_path"
-
- rm -rf "$mount_dir"
- mkdir -p "$mount_dir"
- hdiutil attach "$dmg_path" -nobrowse -readonly -mountpoint "$mount_dir"
- cleanup_mount() {
- hdiutil detach "$mount_dir" >/dev/null
- }
- trap cleanup_mount EXIT
-
- for binary in ${{ matrix.binaries }}; do
- verify_signed_binary "${mount_dir}/${binary}" "$binary"
- done
-
- if [[ "${{ matrix.bundle }}" == "primary" ]]; then
- [[ "$(readlink "${mount_dir}/codex")" == "bin/codex" ]]
- cmp "${mount_dir}/codex-package.json" "${package_dir}/codex-package.json"
- cmp "${mount_dir}/codex-resources/voice/manifest.json" \
- "${package_dir}/codex-resources/voice/manifest.json"
- cmp "${mount_dir}/codex-resources/voice/bin/codex-voice-host" \
- "${package_dir}/codex-resources/voice/bin/codex-voice-host"
- fi
-
- cleanup_mount
- trap - EXIT
- cp "$dmg_path" "dist/${target}/codex-${target}.dmg"
-
- - name: Suffix Bazel artifact filenames
- if: ${{ matrix.build-system == 'bazel' }}
- shell: bash
- run: |
- set -euo pipefail
- for artifact in "dist/${{ matrix.target }}"/*; do
- name="${artifact##*/}"
- stem="${name%%.*}"
- mv "$artifact" "${artifact%/*}/${stem}-bazel${name#"$stem"}"
- done
-
- - name: Upload verified macOS artifacts
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: ${{ matrix.artifact_name }}${{ env.ARTIFACT_SUFFIX }}
- path: codex-rs/dist/${{ matrix.target }}/*
- if-no-files-found: error
-
- # Keep provisioned packages separate from the legacy CLI/npm/installer layout.
- # Desktop consumes the versioned codex-provisioned manifest from this release.
- provisioned-macos-candidate:
- needs: finalize-macos
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/rust-v') && vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
- permissions:
- contents: read
- id-token: write
- uses: ./.github/workflows/rust-release-provisioned-macos.yml
- secrets: inherit
-
- build-windows:
- needs: tag-check
- uses: ./.github/workflows/rust-release-windows.yml
- secrets: inherit
-
- argument-comment-lint-release-assets:
- name: argument-comment-lint release assets
- needs: tag-check
- uses: ./.github/workflows/rust-release-argument-comment-lint.yml
- with:
- publish: true
-
- stage-npm-packages:
- name: stage npm packages
- needs:
- - build
- - finalize-macos
- - build-windows
- runs-on: ubuntu-latest
- permissions:
- contents: read
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Download target artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- path: dist
- pattern: "{aarch64,x86_64}-{apple-darwin{,-app-server},unknown-linux-musl{,-app-server},pc-windows-msvc}"
-
- - name: Setup pnpm
- uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
- with:
- run_install: false
-
- - name: Setup Node.js for npm packaging
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- with:
- node-version: 22
-
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
-
- - name: Stage npm packages
- run: |
- release_version="${GITHUB_REF_NAME#rust-v}"
- ./scripts/stage_npm_packages.py \
- --release-version "$release_version" \
- --artifacts-dir "${GITHUB_WORKSPACE}/dist" \
- --package codex \
- --package codex-responses-api-proxy \
- --package codex-sdk
-
- - name: Upload staged npm packages
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: npm-packages
- path: dist/npm/*.tgz
- if-no-files-found: error
-
- release:
- # Each job checks its channel under this lock; out-of-order builds cannot
- # roll back GitHub, npm, R2, or the canary branch.
- concurrency: &release-publication
- group: rust-release
- queue: max
- cancel-in-progress: false
- needs:
- - tag-check
- - build
- - finalize-macos
- - build-windows
- - argument-comment-lint-release-assets
- - stage-npm-packages
- - provisioned-macos-candidate
- if: >-
- ${{
- always() &&
- needs.tag-check.result == 'success' &&
- needs.build.result == 'success' &&
- needs.finalize-macos.result == 'success' &&
- needs.build-windows.result == 'success' &&
- needs.argument-comment-lint-release-assets.result == 'success' &&
- needs.stage-npm-packages.result == 'success' &&
- (needs.provisioned-macos-candidate.result == 'success' ||
- (vars.CODEX_PROVISIONED_MACOS_CANDIDATE != 'true' &&
- needs.provisioned-macos-candidate.result == 'skipped'))
- }}
- name: release
- runs-on: ubuntu-latest
- permissions:
- contents: write
- outputs:
- version: ${{ steps.release_name.outputs.name }}
- tag: ${{ github.ref_name }}
- make_latest: ${{ steps.release_name.outputs.make_latest }}
- prerelease: ${{ steps.release_name.outputs.prerelease }}
- should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
- npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Generate release notes from tag commit message
- id: release_notes
- shell: bash
- run: |
- set -euo pipefail
-
- # On tag pushes, GITHUB_SHA may be a tag object for annotated tags;
- # peel it to the underlying commit.
- commit="$(git rev-parse "${GITHUB_SHA}^{commit}")"
- notes_path="${RUNNER_TEMP}/release-notes.md"
-
- # Use the commit message for the commit the tag points at (not the
- # annotated tag message).
- # Preserve the previous publisher's 124,999-character cap, reserving
- # the last character for a trailing newline for GitHub's renderer.
- git log -1 --format=%B "${commit}" |
- python3 -c 'import sys; print(sys.stdin.read()[:124998])' > "${notes_path}"
-
- echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
-
- - name: Download release artifacts
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- path: dist
- pattern: "{{aarch64,x86_64}-{apple-darwin{,-app-server},unknown-linux-musl{,-app-server},pc-windows-msvc}{,-bazel},*-symbols{,-bazel},argument-comment-lint-*,python-runtime-wheel-*,npm-packages}"
-
- - name: Download verified provisioned macOS packages
- if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- path: dist
- pattern: "provisioned-macos-verified-candidate-*"
-
- - name: Remove per-architecture checksum filenames before release upload
- if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
- run: rm dist/provisioned-macos-verified-candidate-*/SHA256SUMS
-
- - name: List
- run: ls -R dist/
-
- - name: Add Codex package checksum manifest
- run: |
- set -euo pipefail
-
- manifest="dist/codex-package_SHA256SUMS"
- tmp_manifest="$(mktemp)"
- find dist -type f \
- \( -name 'codex-package-*.tar.gz' -o -name 'codex-app-server-package-*.tar.gz' -o -name 'codex-provisioned-package-*.tar.gz' \) \
- -print |
- sort |
- while IFS= read -r archive; do
- sha256sum "$archive" |
- awk -v name="$(basename "$archive")" '{ print $1 " " name }'
- done > "$tmp_manifest"
-
- if [[ ! -s "$tmp_manifest" ]]; then
- echo "No Codex package archives found for checksum manifest"
- exit 1
- fi
-
- mv "$tmp_manifest" "$manifest"
- cat "$manifest"
-
- - name: Add config schema release asset
- run: |
- cp codex-rs/core/config.schema.json dist/config-schema.json
-
- - name: Define release name
- id: release_name
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- set -euo pipefail
- # Extract the version from the tag name, which is in the format
- # "rust-v0.1.0".
- version="${GITHUB_REF_NAME#rust-v}"
- echo "name=${version}" >> $GITHUB_OUTPUT
- if [[ "${version}" == *-* ]]; then
- echo "make_latest=false" >> $GITHUB_OUTPUT
- echo "prerelease=true" >> $GITHUB_OUTPUT
- else
- make_latest="$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" \
- | python3 .github/scripts/releases.py "$version" --check-github latest)"
- echo "make_latest=${make_latest}" >> $GITHUB_OUTPUT
- echo "prerelease=false" >> $GITHUB_OUTPUT
- fi
-
- - name: Determine npm publish settings
- id: npm_publish_settings
- env:
- VERSION: ${{ steps.release_name.outputs.name }}
- run: |
- set -euo pipefail
- version="${VERSION}"
-
- if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
- echo "should_publish=true" >> "$GITHUB_OUTPUT"
- echo "npm_tag=" >> "$GITHUB_OUTPUT"
- elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+(\.[0-9]+)?$ ]]; then
- echo "should_publish=true" >> "$GITHUB_OUTPUT"
- echo "npm_tag=alpha" >> "$GITHUB_OUTPUT"
- else
- echo "should_publish=false" >> "$GITHUB_OUTPUT"
- echo "npm_tag=" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Stage installer scripts
- run: |
- cp scripts/install/install.sh dist/install.sh
- mv dist/x86_64-pc-windows-msvc/install.ps1 dist/install.ps1
-
- - name: Create GitHub Release
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- RELEASE_NAME: ${{ steps.release_name.outputs.name }}
- RELEASE_NOTES: ${{ steps.release_notes.outputs.path }}
- MAKE_LATEST: ${{ steps.release_name.outputs.make_latest }}
- PRERELEASE: ${{ steps.release_name.outputs.prerelease }}
- run: |
- set -euo pipefail
-
- # Look up this tag, including drafts, without listing release history.
- # API errors must fail the step rather than look like an absent release.
- # shellcheck disable=SC2016
- exists="$(gh api graphql \
- -f query='query($owner:String!,$repo:String!,$tag:String!){repository(owner:$owner,name:$repo){release(tagName:$tag){databaseId}}}' \
- -f owner="$GITHUB_REPOSITORY_OWNER" \
- -f repo="${GITHUB_REPOSITORY#*/}" \
- -f tag="$GITHUB_REF_NAME" \
- --jq '.data.repository.release != null')"
- release_args=(
- --repo "$GITHUB_REPOSITORY"
- --title "$RELEASE_NAME"
- --notes-file "$RELEASE_NOTES"
- --prerelease="$PRERELEASE"
- )
- if [[ "$exists" == false ]]; then
- # Prereleases must be created directly to keep prereleased events.
- draft=true
- if [[ "$PRERELEASE" == true ]]; then
- draft=false
- fi
- gh release create "$GITHUB_REF_NAME" "${release_args[@]}" \
- --draft="$draft" --verify-tag --latest=false
- fi
-
- mapfile -d '' assets < <(find dist -type f -print0 | sort -z)
- gh release upload "$GITHUB_REF_NAME" "${assets[@]}" \
- --repo "$GITHUB_REPOSITORY" --clobber
- gh release edit "$GITHUB_REF_NAME" "${release_args[@]}" \
- --draft=false --latest="$MAKE_LATEST"
-
- publish-r2-assets:
- name: publish-r2-assets
- needs: release
- if: ${{ !cancelled() && needs.release.result == 'success' }}
- permissions:
- contents: read
- uses: ./.github/workflows/r2-release.yml
- secrets: inherit
- with:
- tag: ${{ needs.release.outputs.tag }}
- make_latest: ${{ fromJSON(needs.release.outputs.make_latest) }}
- prerelease: ${{ fromJSON(needs.release.outputs.prerelease) }}
- stage: assets
-
- publish-r2:
- concurrency: *release-publication
- name: publish-r2
- needs: [release, publish-dotslash, publish-r2-assets]
- if: >-
- ${{
- !cancelled() &&
- needs.release.result == 'success' &&
- needs.publish-dotslash.result == 'success' &&
- needs.publish-r2-assets.result == 'success'
- }}
- permissions:
- contents: read
- uses: ./.github/workflows/r2-release.yml
- secrets: inherit
- with:
- tag: ${{ needs.release.outputs.tag }}
- make_latest: ${{ fromJSON(needs.release.outputs.make_latest) }}
- prerelease: ${{ fromJSON(needs.release.outputs.prerelease) }}
- stage: finalize
-
- publish-dotslash:
- name: publish-dotslash
- needs: release
- if: ${{ !cancelled() && needs.release.result == 'success' }}
- runs-on: ubuntu-latest
- permissions:
- contents: write
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - uses: ./.github/actions/publish-dotslash
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag: ${{ github.ref_name }}
- configs: |
- .github/dotslash-config.json
- .github/dotslash-argument-comment-lint-config.json
-
- - name: Publish the provisioned macOS package manifest
- if: vars.CODEX_PROVISIONED_MACOS_CANDIDATE == 'true'
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag: ${{ github.ref_name }}
- config: .github/dotslash-provisioned-config.json
-
- # Publish to npm using OIDC authentication.
- # July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/
- # npm docs: https://docs.npmjs.com/trusted-publishers
- publish-npm:
- concurrency: *release-publication
- # Publish to npm for stable releases, numbered alphas, and alpha hotfixes.
- if: >-
- ${{
- !cancelled() &&
- needs.release.result == 'success' &&
- needs.release.outputs.should_publish_npm == 'true'
- }}
- name: publish-npm
- needs: release
- runs-on: ubuntu-latest
- permissions:
- id-token: write # Required for OIDC
- contents: read
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Setup Node.js
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- with:
- # Node 24 bundles npm >= 11.5.1, which trusted publishing requires.
- node-version: 24
- registry-url: "https://registry.npmjs.org"
- scope: "@openai"
-
- - name: Download npm tarballs from release
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- RELEASE_TAG: ${{ needs.release.outputs.tag }}
- RELEASE_VERSION: ${{ needs.release.outputs.version }}
- run: |
- set -euo pipefail
- version="$RELEASE_VERSION"
- tag="$RELEASE_TAG"
- mkdir -p dist/npm
- patterns=(
- "codex-npm-${version}.tgz"
- "codex-npm-linux-*-${version}.tgz"
- "codex-npm-darwin-*-${version}.tgz"
- "codex-npm-win32-*-${version}.tgz"
- "codex-responses-api-proxy-npm-${version}.tgz"
- "codex-sdk-npm-${version}.tgz"
- )
- for pattern in "${patterns[@]}"; do
- gh release download "$tag" \
- --repo "${GITHUB_REPOSITORY}" \
- --pattern "$pattern" \
- --dir dist/npm
- done
-
- # No NODE_AUTH_TOKEN needed because we use OIDC.
- - name: Publish to npm
- env:
- VERSION: ${{ needs.release.outputs.version }}
- NPM_TAG: ${{ needs.release.outputs.npm_tag }}
- NPM_CONFIG_LOGLEVEL: verbose
- NPM_CONFIG_LOGS_DIR: ${{ runner.temp }}/npm-publish-logs
- NPM_CONFIG_LOGS_MAX: "100"
- run: |
- set -euo pipefail
- prefix=""
- if [[ -n "${NPM_TAG}" ]]; then
- prefix="${NPM_TAG}-"
- fi
-
- root_tarball="dist/npm/codex-npm-${VERSION}.tgz"
- sdk_tarball="dist/npm/codex-sdk-npm-${VERSION}.tgz"
- # Keep this list in sync with CODEX_PLATFORM_PACKAGES in
- # codex-cli/scripts/build_npm_package.py. The root wrapper advances
- # @openai/codex@latest as soon as it publishes, so every platform
- # package it aliases must already exist in the registry first.
- platform_tarballs=(
- "dist/npm/codex-npm-linux-x64-${VERSION}.tgz"
- "dist/npm/codex-npm-linux-arm64-${VERSION}.tgz"
- "dist/npm/codex-npm-darwin-x64-${VERSION}.tgz"
- "dist/npm/codex-npm-darwin-arm64-${VERSION}.tgz"
- "dist/npm/codex-npm-win32-x64-${VERSION}.tgz"
- "dist/npm/codex-npm-win32-arm64-${VERSION}.tgz"
- )
-
- for required_tarball in "${platform_tarballs[@]}" "${root_tarball}"; do
- if [[ ! -f "${required_tarball}" ]]; then
- echo "Missing npm tarball: ${required_tarball}"
- exit 1
- fi
- done
-
- shopt -s nullglob
- other_tarballs=()
- for tarball in dist/npm/*-"${VERSION}".tgz; do
- if [[ "${tarball}" == "${root_tarball}" || "${tarball}" == "${sdk_tarball}" ]]; then
- continue
- fi
-
- is_platform_tarball=false
- for platform_tarball in "${platform_tarballs[@]}"; do
- if [[ "${tarball}" == "${platform_tarball}" ]]; then
- is_platform_tarball=true
- break
- fi
- done
- if [[ "${is_platform_tarball}" == true ]]; then
- continue
- fi
-
- other_tarballs+=("${tarball}")
- done
-
- # npm returns HTTP 409 when concurrent publishes update the same
- # packument. Every platform tarball is a version of @openai/codex,
- # so publish all tarballs serially.
- tarballs=(
- "${platform_tarballs[@]}"
- "${other_tarballs[@]}"
- "${root_tarball}"
- )
- # The SDK depends on this exact root package version.
- if [[ -f "${sdk_tarball}" ]]; then
- tarballs+=("${sdk_tarball}")
- fi
-
- for tarball in "${tarballs[@]}"; do
- filename="$(basename "${tarball}")"
- tag=""
-
- case "${filename}" in
- codex-npm-linux-*-"${VERSION}".tgz|codex-npm-darwin-*-"${VERSION}".tgz|codex-npm-win32-*-"${VERSION}".tgz)
- platform="${filename#codex-npm-}"
- platform="${platform%-${VERSION}.tgz}"
- tag="${prefix}${platform}"
- ;;
- codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz)
- tag="${NPM_TAG}"
- ;;
- *)
- echo "Unexpected npm tarball: ${filename}"
- exit 1
- ;;
- esac
-
- # The publication workflow holds the lock from this lookup through
- # publish, including concurrent releases from other version lines.
- tag="$(python3 .github/scripts/npm_alpha_tag.py "${tarball}" "${VERSION}" "${tag:-latest}")"
-
- publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}")
- if [[ -n "${tag}" ]]; then
- publish_cmd+=(--tag "${tag}")
- fi
-
- # A fresh npm process obtains new OIDC credentials on each attempt.
- for attempt in 1 2 3; do
- echo "+ ${publish_cmd[*]} (attempt ${attempt}/3)"
- set +e
- publish_output="$("${publish_cmd[@]}" 2>&1)"
- publish_status=$?
- set -e
-
- echo "${publish_output}"
- if [[ ${publish_status} -eq 0 ]]; then
- break
- fi
-
- if grep -qiE "previously published|cannot publish over|version already exists" <<< "${publish_output}"; then
- echo "Skipping already-published package version for ${filename}"
- break
- fi
-
- if [[ ${attempt} -eq 3 ]]; then
- exit "${publish_status}"
- fi
- delay=$((attempt * 10))
- echo "Publishing ${filename} failed; retrying in ${delay} seconds."
- sleep "${delay}"
- done
- done
-
- - name: Retain npm debug logs
- if: always()
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: npm-publish-logs-${{ github.run_attempt }}
- path: ${{ runner.temp }}/npm-publish-logs/
- if-no-files-found: ignore
- retention-days: 7
-
- deploy-dev-website:
- name: Trigger developers.openai.com deploy
- needs: release
- # Only trigger the deploy for a stable release.
- # The deploy updates developers.openai.com with the new config schema json file.
- if: >-
- ${{
- !cancelled() &&
- needs.release.result == 'success' &&
- !contains(needs.release.outputs.version, '-')
- }}
- runs-on: ubuntu-latest
- continue-on-error: true
- permissions: {}
- environment:
- name: dev-website-vercel-deploy
- deployment: false
-
- steps:
- - name: Trigger developers.openai.com deploy
- continue-on-error: true
- env:
- DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL: ${{ secrets.DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL }}
- run: |
- if ! curl -sS -f -o /dev/null -X POST "$DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL"; then
- echo "::warning title=developers.openai.com deploy hook failed::Vercel deploy hook POST failed for ${GITHUB_REF_NAME}"
- exit 1
- fi
-
- winget:
- concurrency: *release-publication
- name: winget
- needs: release
- # Only publish stable/mainline releases to WinGet; pre-releases include a
- # '-' in the semver string (e.g., 1.2.3-alpha.1).
- if: >-
- ${{
- !cancelled() &&
- needs.release.result == 'success' &&
- !contains(needs.release.outputs.version, '-')
- }}
- # komac opens the winget-pkgs PR using the published Windows archives;
- # it runs on Linux and does not execute the Windows binaries.
- runs-on: ubuntu-latest
- permissions:
- contents: read
- environment:
- name: mainline-release-winget
- deployment: false
-
- steps:
- # Adapted from the WinGet Releaser version previously used by this job:
- # https://github.com/vedantmgoyal9/winget-releaser/blob/7bd472be23763def6e16bd06cc8b1cdfab0e2fd5/action.yml
- # Keep dependencies explicit because its cargo-binstall@main reference
- # violates the policy requiring full commit SHA action pins.
- - name: Install cargo-binstall
- uses: cargo-bins/cargo-binstall@b874e25ea559687bec77e281e9b271aa1367b624 # v1.23.0
- with:
- version: "1.23.0"
- env:
- GITHUB_TOKEN: ${{ github.token }}
-
- - name: Install komac
- env:
- GITHUB_TOKEN: ${{ github.token }}
- run: cargo-binstall komac --version 2.16.0 --no-confirm
-
- - name: Publish to WinGet
- shell: bash
- env:
- GH_TOKEN: ${{ github.token }}
- GITHUB_TOKEN: ${{ secrets.WINGET_PUBLISH_PAT }}
- RELEASE_VERSION: ${{ needs.release.outputs.version }}
- RELEASE_TAG: ${{ needs.release.outputs.tag }}
- KOMAC_FORK_OWNER: openai-oss-forks
- KOMAC_CREATED_WITH: Codex release workflow
- KOMAC_CREATED_WITH_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- run: |
- set -euo pipefail
- installer_urls="$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \
- --json assets \
- --jq '.assets[] | select(.name | test("^codex-(x86_64|aarch64)-pc-windows-msvc\\.exe\\.zip$")) | .url')"
- mapfile -t urls <<< "$installer_urls"
- if [[ ${#urls[@]} -ne 2 ]]; then
- echo "::error::Expected x86_64 and aarch64 Windows archives for $RELEASE_TAG"
- exit 1
- fi
- komac sync-fork
- komac update OpenAI.Codex --version "$RELEASE_VERSION" --submit --urls "${urls[@]}"
- komac cleanup --only-merged
-
- update-branch:
- concurrency: *release-publication
- name: Update latest-alpha-cli branch
- if: >-
- ${{
- !cancelled() &&
- needs.release.result == 'success' &&
- needs.release.outputs.npm_tag == 'alpha' &&
- needs.publish-r2.result == 'success' &&
- needs.publish-npm.result == 'success'
- }}
- permissions:
- contents: write
- # R2 waits for the GitHub release and DotSlash release-asset uploads.
- needs:
- - release
- - publish-r2
- - publish-npm
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Determine whether to update the GitHub canary branch
- id: github_canary
- shell: bash
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- RELEASE_VERSION: ${{ needs.release.outputs.version }}
- PYTHONPATH: .github/scripts
- run: |
- set -euo pipefail
- # Despite its name, raw+json returns raw TOML. The default response
- # is JSON metadata with the file contents encoded as base64.
- update="$(gh api "repos/$GITHUB_REPOSITORY/contents/codex-rs/Cargo.toml?ref=latest-alpha-cli" \
- -H 'Accept: application/vnd.github.raw+json' \
- | python3 .github/scripts/releases.py "$RELEASE_VERSION" --check-github canary)"
- echo "update=$update" >> "$GITHUB_OUTPUT"
- - name: Update latest-alpha-cli branch
- if: success() && steps.github_canary.outputs.update == 'true'
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- set -euo pipefail
- gh api \
- repos/${GITHUB_REPOSITORY}/git/refs/heads/latest-alpha-cli \
- -X PATCH \
- -f sha="${GITHUB_SHA}" \
- -F force=true
+ echo "::error::Upstream Rust release including macOS is disabled in Codefx. Use codefx-rust-v0.166-release.yml."
+ exit 1
diff --git a/.github/workflows/rusty-v8-release.yml b/.github/workflows/rusty-v8-release.yml
index 3dcdbe09859d..68a759c6c02a 100644
--- a/.github/workflows/rusty-v8-release.yml
+++ b/.github/workflows/rusty-v8-release.yml
@@ -1,495 +1,13 @@
-name: rusty-v8-release
-
+name: Disabled upstream V8 publisher (Codefx Linux/Windows only)
on:
- push:
- tags:
- - "rusty-v8-v*.*.*"
-
-# Cargo's libgit2 transport has been flaky when fetching git dependencies with
-# nested submodules. Prefer the system git CLI for Cargo smoke tests.
-env:
- CARGO_NET_GIT_FETCH_WITH_CLI: "true"
-
-concurrency:
- group: ${{ github.workflow }}::${{ github.ref_name }}
- cancel-in-progress: false
-
+ workflow_dispatch:
+permissions:
+ contents: read
jobs:
- metadata:
- runs-on: ubuntu-latest
- outputs:
- release_tag: ${{ steps.release_tag.outputs.release_tag }}
- v8_version: ${{ steps.v8_version.outputs.version }}
-
+ disabled:
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: "3.12"
-
- - name: Resolve exact v8 crate version
- id: v8_version
- shell: bash
+ - name: Block upstream multi-OS publishing
run: |
- set -euo pipefail
- version="$(python3 .github/scripts/rusty_v8_bazel.py resolved-v8-crate-version)"
- echo "version=${version}" >> "$GITHUB_OUTPUT"
-
- - name: Resolve release tag
- id: release_tag
- env:
- GITHUB_REF_NAME: ${{ github.ref_name }}
- V8_VERSION: ${{ steps.v8_version.outputs.version }}
- shell: bash
- run: |
- set -euo pipefail
-
- expected_release_tag="rusty-v8-v${V8_VERSION}"
- release_tag="${GITHUB_REF_NAME}"
- if [[ "${release_tag}" != "${expected_release_tag}" ]]; then
- echo "Tag ${release_tag} does not match expected release tag ${expected_release_tag}." >&2
- exit 1
- fi
-
- echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
-
- build:
- name: Build ${{ matrix.variant }} ${{ matrix.target }}
- needs: metadata
- runs-on: ${{ matrix.runner }}
- permissions:
- contents: read
- actions: read
- environment:
- name: bazel
- deployment: false
- strategy:
- fail-fast: false
- matrix:
- include:
- - runner: ubuntu-24.04
- bazel_config: ci-v8
- platform: linux_amd64
- sandbox: false
- target: x86_64-unknown-linux-gnu
- v8_cpu: x64
- variant: release
- - runner: ubuntu-24.04
- bazel_config: ci-v8
- platform: linux_amd64
- sandbox: true
- target: x86_64-unknown-linux-gnu
- v8_cpu: x64
- variant: ptrcomp-sandbox
- - runner: ubuntu-24.04-arm
- bazel_config: ci-v8
- platform: linux_arm64
- sandbox: false
- target: aarch64-unknown-linux-gnu
- v8_cpu: arm64
- variant: release
- - runner: ubuntu-24.04-arm
- bazel_config: ci-v8
- platform: linux_arm64
- sandbox: true
- target: aarch64-unknown-linux-gnu
- v8_cpu: arm64
- variant: ptrcomp-sandbox
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_amd64
- sandbox: false
- target: x86_64-apple-darwin
- v8_cpu: x64
- variant: release
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_amd64
- sandbox: true
- target: x86_64-apple-darwin
- v8_cpu: x64
- variant: ptrcomp-sandbox
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_arm64
- sandbox: false
- target: aarch64-apple-darwin
- v8_cpu: arm64
- variant: release
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_arm64
- sandbox: true
- target: aarch64-apple-darwin
- v8_cpu: arm64
- variant: ptrcomp-sandbox
- - runner: ubuntu-24.04
- bazel_config: ci-v8
- platform: linux_amd64_musl
- sandbox: false
- target: x86_64-unknown-linux-musl
- v8_cpu: x64
- variant: release
- - runner: ubuntu-24.04-arm
- bazel_config: ci-v8
- platform: linux_arm64_musl
- sandbox: false
- target: aarch64-unknown-linux-musl
- v8_cpu: arm64
- variant: release
- - runner: ubuntu-24.04
- bazel_config: ci-v8
- platform: linux_amd64_musl
- sandbox: true
- target: x86_64-unknown-linux-musl
- v8_cpu: x64
- variant: ptrcomp-sandbox
- - runner: ubuntu-24.04-arm
- bazel_config: ci-v8
- platform: linux_arm64_musl
- sandbox: true
- target: aarch64-unknown-linux-musl
- v8_cpu: arm64
- variant: ptrcomp-sandbox
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Prepare Bazel CI
- uses: ./.github/actions/prepare-bazel-ci
- with:
- target: ${{ matrix.target }}
- cache-scope: bazel-v8
-
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: "3.12"
-
- - name: Set up Rust toolchain for Cargo smoke
- uses: dtolnay/rust-toolchain@d124f86f28ff42855784ff3319f48ac182c248f8 # 1.99.1
- with:
- toolchain: "1.99.1"
-
- - name: Build Bazel V8 release pair
- env:
- BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
- PLATFORM: ${{ matrix.platform }}
- SANDBOX: ${{ matrix.sandbox }}
- TARGET: ${{ matrix.target }}
- V8_CPU: ${{ matrix.v8_cpu }}
- shell: bash
- run: |
- set -euo pipefail
-
- target_suffix="${TARGET//-/_}"
- pair_kind="release_pair"
- if [[ "${SANDBOX}" == "true" ]]; then
- pair_kind="sandbox_release_pair"
- fi
- pair_target="//third_party/v8:rusty_v8_${pair_kind}_${target_suffix}"
-
- bazel_args=(
- build
- -c
- opt
- "--platforms=@llvm//platforms:${PLATFORM}"
- --config=rusty-v8-upstream-libcxx
- "--config=v8-target-${V8_CPU}"
- "${pair_target}"
- --build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
- )
- if [[ "${SANDBOX}" != "true" ]]; then
- bazel_args+=(--config=v8-release-compat)
- fi
-
- ./.github/scripts/run_bazel_with_buildbuddy.py \
- --noexperimental_remote_repo_contents_cache \
- "${bazel_args[@]}" \
- "--config=${{ matrix.bazel_config }}"
-
- - name: Stage release pair
- env:
- BAZEL_CONFIG: ${{ matrix.bazel_config }}
- BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
- PLATFORM: ${{ matrix.platform }}
- SANDBOX: ${{ matrix.sandbox }}
- TARGET: ${{ matrix.target }}
- V8_CPU: ${{ matrix.v8_cpu }}
- shell: bash
- run: |
- set -euo pipefail
-
- stage_args=(
- --platform "${PLATFORM}"
- --target "${TARGET}"
- --compilation-mode opt
- --output-dir "dist/${TARGET}"
- --bazel-config "${BAZEL_CONFIG}"
- --bazel-config "v8-target-${V8_CPU}"
- )
- if [[ "${SANDBOX}" == "true" ]]; then
- stage_args+=(--sandbox)
- else
- stage_args+=(--bazel-config v8-release-compat)
- fi
-
- python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
-
- - name: Smoke test staged artifact with Cargo
- env:
- SANDBOX: ${{ matrix.sandbox }}
- TARGET: ${{ matrix.target }}
- shell: bash
- run: |
- set -euo pipefail
-
- host_arch="$(uname -m)"
- case "${TARGET}:${host_arch}" in
- x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
- ;;
- *)
- echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
- exit 0
- ;;
- esac
-
- archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
- binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
- if [[ -z "${archive}" || -z "${binding}" ]]; then
- echo "Missing staged archive or binding for ${TARGET}." >&2
- exit 1
- fi
-
- cargo_args=(test -p codex-v8-poc)
- if [[ "${SANDBOX}" == "true" ]]; then
- cargo_args+=(--features sandbox)
- fi
-
- (
- cd codex-rs
- CARGO_TARGET_DIR="${RUNNER_TEMP}/rusty-v8-cargo-smoke-${TARGET}-${SANDBOX}" \
- RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
- RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
- cargo "${cargo_args[@]}"
- )
-
- - name: Upload staged artifacts
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.variant }}-${{ matrix.target }}
- path: dist/${{ matrix.target }}/*
-
- build-windows-source:
- name: Build ptrcomp-sandbox ${{ matrix.target }} from source
- needs: metadata
- runs-on: ${{ matrix.runner }}
- permissions:
- contents: read
- strategy:
- fail-fast: false
- matrix:
- include:
- - runner: windows-2022
- target: x86_64-pc-windows-msvc
- - runner: windows-2022
- target: aarch64-pc-windows-msvc
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- - name: Configure git for upstream checkout
- shell: bash
- run: git config --global core.symlinks true
-
- - name: Check out upstream rusty_v8
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: denoland/rusty_v8
- ref: v${{ needs.metadata.outputs.v8_version }}
- path: upstream-rusty-v8
- submodules: recursive
- persist-credentials: false
-
- - name: Download upstream Windows build tools
- shell: bash
- working-directory: upstream-rusty-v8
- run: |
- set -euo pipefail
- # Upstream skips this submodule and downloads the pinned archive in CI.
- mkdir -p tools/win
- curl -fL https://chromium.googlesource.com/chromium/src/tools/win/+archive/faefd1b6fa9eeb033ad6fe60368ccb9bf908cbd0.tar.gz |
- tar -xz -C tools/win
-
- - name: Set up Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: "3.11"
- architecture: x64
-
- - name: Set up Codex Rust toolchain for Cargo smoke
- uses: dtolnay/rust-toolchain@d124f86f28ff42855784ff3319f48ac182c248f8 # 1.99.1
- with:
- toolchain: "1.99.1"
- targets: ${{ matrix.target }}
-
- - name: Install rusty_v8 Rust toolchain
- env:
- TARGET: ${{ matrix.target }}
- shell: bash
- run: |
- set -euo pipefail
- rustup toolchain install 1.91.0 --profile minimal --no-self-update
- rustup target add --toolchain 1.91.0 "${TARGET}"
-
- - name: Write upstream submodule status
- shell: bash
- working-directory: upstream-rusty-v8
- run: git submodule status --recursive > git_submodule_status.txt
-
- - name: Restore upstream source-build cache
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
- with:
- path: |
- upstream-rusty-v8/target/sccache
- upstream-rusty-v8/target/${{ matrix.target }}/release/gn_out
- key: rusty-v8-source-${{ matrix.target }}-sandbox-${{ hashFiles('upstream-rusty-v8/Cargo.lock', 'upstream-rusty-v8/build.rs', 'upstream-rusty-v8/git_submodule_status.txt', 'patches/v8_array_sort_elements_kind.patch') }}
- restore-keys: |
- rusty-v8-source-${{ matrix.target }}-sandbox-
-
- - name: Apply V8 Array.sort backport
- shell: bash
- working-directory: upstream-rusty-v8/v8
- run: git apply -p3 "${GITHUB_WORKSPACE}/patches/v8_array_sort_elements_kind.patch"
-
- - name: Install sccache
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
- with:
- tool: sccache@0.8.2
-
- - name: Start sccache
- shell: pwsh
- env:
- SCCACHE_CACHE_SIZE: 256M
- SCCACHE_DIR: ${{ github.workspace }}/upstream-rusty-v8/target/sccache
- SCCACHE_IDLE_TIMEOUT: 0
- run: sccache --start-server
-
- - name: Install Chromium clang for ARM64 MSVC cross build
- if: matrix.target == 'aarch64-pc-windows-msvc'
- shell: bash
- working-directory: upstream-rusty-v8
- run: python3 tools/clang/scripts/update.py
-
- - name: Build upstream rusty_v8 sandbox release pair
- env:
- SCCACHE_IDLE_TIMEOUT: 0
- TARGET: ${{ matrix.target }}
- V8_FROM_SOURCE: "1"
- shell: bash
- working-directory: upstream-rusty-v8
- run: cargo +1.91.0 build --locked --release --target "${TARGET}" --features v8_enable_sandbox
-
- - name: Stage upstream sandbox release pair
- env:
- TARGET: ${{ matrix.target }}
- shell: bash
- run: |
- set -euo pipefail
- python3 .github/scripts/rusty_v8_bazel.py stage-upstream-release-pair \
- --target-dir upstream-rusty-v8/target \
- --target "${TARGET}" \
- --output-dir "dist/${TARGET}" \
- --sandbox
-
- - name: Smoke link staged artifact with Cargo
- env:
- TARGET: ${{ matrix.target }}
- shell: bash
- run: |
- set -euo pipefail
-
- archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'rusty_v8_*.lib.gz' -print -quit)"
- binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
- if [[ -z "${archive}" || -z "${binding}" ]]; then
- echo "Missing staged archive or binding for ${TARGET}." >&2
- exit 1
- fi
-
- (
- cd codex-rs
- RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
- RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
- cargo +1.99.1 test -p codex-v8-poc --target "${TARGET}" --features sandbox --no-run
- )
-
- - name: Upload staged artifacts
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- with:
- name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-ptrcomp-sandbox-${{ matrix.target }}
- path: dist/${{ matrix.target }}/*
-
- publish-release:
- needs:
- - metadata
- - build
- - build-windows-source
- runs-on: ubuntu-latest
- permissions:
- contents: write
- actions: read
-
- steps:
- - name: Check whether release already exists
- env:
- GH_TOKEN: ${{ github.token }}
- RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
- shell: bash
- run: |
- set -euo pipefail
-
- if is_draft="$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json isDraft --jq '.isDraft' 2>/dev/null)"; then
- if [[ "${is_draft}" != "true" ]]; then
- echo "Refusing to replace existing published release ${RELEASE_TAG}." >&2
- exit 1
- fi
-
- echo "Removing unfinished draft release for ${RELEASE_TAG}."
- gh release delete "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --yes
- fi
-
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- path: dist
-
- - name: Create GitHub Release
- env:
- GH_TOKEN: ${{ github.token }}
- RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
- shell: bash
- run: |
- set -euo pipefail
- assets=()
- while IFS= read -r -d '' asset; do
- assets+=("${asset}")
- done < <(find dist -type f -print0)
- if [[ "${#assets[@]}" -eq 0 ]]; then
- echo "No V8 release artifacts were found." >&2
- exit 1
- fi
-
- # GitHub CLI keeps asset-bearing releases in draft until all uploads finish.
- gh release create "${RELEASE_TAG}" \
- --repo "${GITHUB_REPOSITORY}" \
- --title "${RELEASE_TAG}" \
- --verify-tag \
- --prerelease \
- "${assets[@]}"
+ echo "::error::Upstream V8 release workflow contains macOS release builds and is disabled in Codefx."
+ exit 1
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
index 4cb081eb94f8..ea82ac692021 100644
--- a/.github/workflows/sdk.yml
+++ b/.github/workflows/sdk.yml
@@ -4,11 +4,15 @@ on:
workflow_call:
jobs:
- python-sdk-installation:
- runs-on:
- group: ${{ github.event.repository.name }}-runners
- labels: ${{ github.event.repository.name }}-linux-x64
- timeout-minutes: 10
+ sdks:
+ # Keep both SDK suites on one runner. They share checkout, Docker, and
+ # Bazel setup; running them in one job preserves every test while avoiding
+ # a second runner allocation and a second repository checkout.
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ environment:
+ name: bazel
+ deployment: false
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -35,20 +39,6 @@ jobs:
env -u PYTHONPATH -u CODEX_EXEC_PATH /tmp/installed/bin/python tests/installed_sdk_smoke.py
'
- sdks:
- runs-on:
- group: ${{ github.event.repository.name }}-runners
- labels: ${{ github.event.repository.name }}-linux-x64
- timeout-minutes: 20
- environment:
- name: bazel
- deployment: false
- steps:
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
-
- name: Install Linux bwrap build dependencies
shell: bash
run: |
@@ -64,7 +54,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 22
+ node-version-file: .nvmrc
cache: pnpm
- name: Prepare Bazel CI
@@ -72,7 +62,7 @@ jobs:
uses: ./.github/actions/prepare-bazel-ci
with:
target: x86_64-unknown-linux-gnu
- cache-scope: sdk
+ cache-scope: bazel-target
- name: Build codex and the code-mode host with Bazel
id: build_bazel
diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml
index ae155133ed9c..36c4c3ccea11 100644
--- a/.github/workflows/v8-canary.yml
+++ b/.github/workflows/v8-canary.yml
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
concurrency:
- group: ${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
+ group: ${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}::scheduler-v4
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
@@ -83,6 +83,7 @@ jobs:
deployment: false
strategy:
fail-fast: false
+ max-parallel: 1
matrix:
include:
- runner: ubuntu-24.04
@@ -90,84 +91,48 @@ jobs:
platform: linux_amd64
sandbox: false
target: x86_64-unknown-linux-gnu
- v8_cpu: x64
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64
sandbox: true
target: x86_64-unknown-linux-gnu
- v8_cpu: x64
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: false
target: aarch64-unknown-linux-gnu
- v8_cpu: arm64
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: true
target: aarch64-unknown-linux-gnu
- v8_cpu: arm64
- variant: ptrcomp-sandbox
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_amd64
- sandbox: false
- target: x86_64-apple-darwin
- v8_cpu: x64
- variant: release
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_amd64
- sandbox: true
- target: x86_64-apple-darwin
- v8_cpu: x64
- variant: ptrcomp-sandbox
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_arm64
- sandbox: false
- target: aarch64-apple-darwin
- v8_cpu: arm64
- variant: release
- - runner: macos-15-xlarge
- bazel_config: ci-macos
- platform: macos_arm64
- sandbox: true
- target: aarch64-apple-darwin
- v8_cpu: arm64
variant: ptrcomp-sandbox
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: false
target: x86_64-unknown-linux-musl
- v8_cpu: x64
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: true
target: x86_64-unknown-linux-musl
- v8_cpu: x64
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: false
target: aarch64-unknown-linux-musl
- v8_cpu: arm64
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: true
target: aarch64-unknown-linux-musl
- v8_cpu: arm64
variant: ptrcomp-sandbox
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -186,9 +151,10 @@ jobs:
python-version: "3.12"
- name: Set up Rust toolchain for Cargo smoke
- uses: dtolnay/rust-toolchain@d124f86f28ff42855784ff3319f48ac182c248f8 # 1.99.1
+ uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
- toolchain: "1.99.1"
+ toolchain: "1.95.0"
+ targets: ${{ matrix.target }}
- name: Build Bazel V8 release pair
env:
@@ -196,7 +162,6 @@ jobs:
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
- V8_CPU: ${{ matrix.v8_cpu }}
shell: bash
run: |
set -euo pipefail
@@ -211,14 +176,9 @@ jobs:
bazel_args=(
build
"--platforms=@llvm//platforms:${PLATFORM}"
- --config=rusty-v8-upstream-libcxx
- "--config=v8-target-${V8_CPU}"
"${pair_target}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
)
- if [[ "${SANDBOX}" != "true" ]]; then
- bazel_args+=(--config=v8-release-compat)
- fi
./.github/scripts/run_bazel_with_buildbuddy.py \
--noexperimental_remote_repo_contents_cache \
@@ -232,7 +192,6 @@ jobs:
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
- V8_CPU: ${{ matrix.v8_cpu }}
shell: bash
run: |
set -euo pipefail
@@ -242,16 +201,33 @@ jobs:
--target "${TARGET}"
--output-dir "dist/${TARGET}"
--bazel-config "${BAZEL_CONFIG}"
- --bazel-config "v8-target-${V8_CPU}"
)
if [[ "${SANDBOX}" == "true" ]]; then
stage_args+=(--sandbox)
- else
- stage_args+=(--bazel-config v8-release-compat)
fi
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
+ # Larger Linux runner images may omit the host linker used by Cargo build scripts.
+ - name: Install native Linux Cargo smoke dependencies
+ if: ${{ runner.os == 'Linux' }}
+ env:
+ TARGET: ${{ matrix.target }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ packages=(build-essential)
+ if [[ "${TARGET}" == *-musl ]]; then
+ packages+=(musl-tools)
+ fi
+ sudo apt-get update -y
+ sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
+ if [[ "${TARGET}" == *-musl ]]; then
+ # Use musl only for target links; Cargo build scripts still link on the glibc host.
+ linker_var="CARGO_TARGET_${TARGET^^}_LINKER"
+ echo "${linker_var//-/_}=$(command -v musl-gcc)" >> "${GITHUB_ENV}"
+ fi
+
- name: Smoke test staged artifact with Cargo
env:
SANDBOX: ${{ matrix.sandbox }}
@@ -260,16 +236,6 @@ jobs:
run: |
set -euo pipefail
- host_arch="$(uname -m)"
- case "${TARGET}:${host_arch}" in
- x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
- ;;
- *)
- echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
- exit 0
- ;;
- esac
-
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
if [[ -z "${archive}" || -z "${binding}" ]]; then
@@ -277,10 +243,11 @@ jobs:
exit 1
fi
- cargo_args=(test -p codex-v8-poc)
+ cargo_args=(test -p codex-v8-poc --target "${TARGET}")
if [[ "${SANDBOX}" == "true" ]]; then
cargo_args+=(--features sandbox)
fi
+ cargo_args+=(-- --include-ignored)
(
cd codex-rs
@@ -302,13 +269,14 @@ jobs:
build-windows-source:
name: Build ptrcomp-sandbox ${{ matrix.target }} from source
- needs: metadata
- if: ${{ needs.metadata.outputs.windows_source_required == 'true' }}
+ needs: [metadata, build]
+ if: ${{ always() && !cancelled() && needs.metadata.outputs.windows_source_required == 'true' }}
runs-on: ${{ matrix.runner }}
permissions:
contents: read
strategy:
fail-fast: false
+ max-parallel: 1
matrix:
include:
- runner: windows-2025
@@ -354,9 +322,9 @@ jobs:
architecture: x64
- name: Set up Codex Rust toolchain for Cargo smoke
- uses: dtolnay/rust-toolchain@d124f86f28ff42855784ff3319f48ac182c248f8 # 1.99.1
+ uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
- toolchain: "1.99.1"
+ toolchain: "1.95.0"
targets: ${{ matrix.target }}
- name: Install rusty_v8 Rust toolchain
@@ -446,7 +414,7 @@ jobs:
cd codex-rs
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
- cargo +1.99.1 test -p codex-v8-poc --target "${TARGET}" --features sandbox --no-run
+ cargo +1.95.0 test -p codex-v8-poc --target "${TARGET}" --features sandbox --no-run
)
- name: Upload staged artifacts
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000000..ce62013c39ae
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+24.22.1
diff --git a/BUILD.bazel b/BUILD.bazel
index 9a6e5fb5c0e6..10d64abbaa0b 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,12 +1,4 @@
load("@apple_support//xcode:xcode_config.bzl", "xcode_config")
-load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
-
-# Set for builds that need custom V8 C++ flags or source instrumentation.
-bool_flag(
- name = "rusty_v8_from_source",
- build_setting_default = False,
- visibility = ["//visibility:public"],
-)
xcode_config(name = "disable_xcode")
diff --git a/MODULE.bazel b/MODULE.bazel
index b4bcc6f99435..66f6054c1219 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -151,6 +151,14 @@ bazel_dep(name = "rules_cc", version = "0.2.18")
cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
use_repo(cc_configure, "local_config_cc")
+single_version_override(
+ module_name = "rules_cc",
+ patch_strip = 1,
+ patches = [
+ "//patches:rules_cc_windows_native_llvm.patch",
+ ],
+)
+
bazel_dep(name = "rules_platform", version = "0.1.0")
bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "aws-lc", version = "5.1.0.bcr.1")
@@ -317,7 +325,7 @@ toolchains.toolchain(
edition = "2024",
extra_exec_rustc_flags = MSVC_RUSTC_FLAGS,
extra_rustc_flags = MSVC_RUSTC_FLAGS,
- version = "1.99.1",
+ version = "1.95.0",
)
use_repo(toolchains, "default_rust_toolchains")
@@ -393,6 +401,14 @@ crate.annotation(
},
rustc_flags = ["-Copt-level=3"],
)
+
+# Match the CodeModeHost response-compression benchmark's zstd level.
+crate.annotation(
+ crate = "tonic",
+ patch_args = ["-p1"],
+ patches = ["//patches:tonic-zstd-level1.patch"],
+ version = "0.14.3",
+)
crate.annotation(
crate = "zstd-sys",
gen_build_script = "on",
@@ -807,25 +823,47 @@ inject_repo(crate, "alsa_lib")
]
]
-bazel_dep(name = "v8", version = "15.2.124.1")
bazel_dep(name = "rules_license", version = "1.0.0")
-archive_override(
- module_name = "v8",
+engine_repository = use_repo_rule("//third_party/v8:engine_repository.bzl", "engine_repository")
+
+engine_repository(
+ name = "v8",
integrity = "sha256-aNN5G427qQbP/5o+NXONgXR8ZvS6mjf6mfAI6jtxyzI=",
patch_strip = 3,
patches = [
- "//patches:v8_module_deps.patch",
- "//patches:v8_bazel_rules.patch",
"//patches:v8_source_portability.patch",
"//patches:v8_array_sort_elements_kind.patch",
+ "//third_party/v8:v8_defs.patch",
],
strip_prefix = "v8-15.2.124.1",
urls = ["https://github.com/v8/v8/archive/refs/tags/15.2.124.1.tar.gz"],
)
-# The dependency repos and their BUILD files belong to Codex. V8 only needs
-# its original apparent repo names to resolve to these same imports.
+# All V8 inputs are owned here, including the pinned Python generator inputs.
+pip.parse(
+ extra_pip_args = ["--require-hashes"],
+ hub_name = "v8_python_deps",
+ python_version = "3.11",
+ requirements_lock = "@v8//:bazel/requirements.txt",
+ simpleapi_skip = [
+ "jinja2",
+ "markupsafe",
+ ],
+)
+use_repo(pip, "v8_python_deps")
+
+native_windows_llvm = use_repo_rule("//third_party/v8:native_windows.bzl", "native_windows_llvm")
+
+native_windows_llvm(name = "v8_native_windows_llvm")
+
+native_windows_cc = use_repo_rule("//third_party/v8:native_windows.bzl", "native_windows_cc")
+
+native_windows_cc(
+ name = "v8_native_windows_cc",
+ native_llvm = "@v8_native_windows_llvm//:BUILD.bazel",
+)
+
v8_dependencies = use_extension("//third_party/v8:dependencies.bzl", "dependencies")
use_repo(
v8_dependencies,
@@ -838,26 +876,13 @@ use_repo(
"v8_simdutf",
)
-v8_dependency_bridge = use_extension("@v8//:bazel/codex_deps.bzl", "codex_deps")
-
-inject_repo(
- v8_dependency_bridge,
- dragonbox = "v8_dragonbox",
- fast_float = "v8_fast_float",
- fp16 = "v8_fp16",
- highway = "v8_highway",
- icu = "v8_icu",
- simdutf = "v8_simdutf",
- v8_abseil = "v8_abseil",
-)
-
http_archive(
name = "v8_crate_152_2_0",
build_file = "//third_party/v8:v8_crate.BUILD.bazel",
- sha256 = "a10fe1a92da5c32c7c7f838ce36c0ccfcfd5edf0865b58bdde820aa64cea9888",
- strip_prefix = "v8-152.2.0",
+ sha256 = "e2e7f9961af59dbb749a60ac0a3c34c9680f6b86dd04277f2715356b5a52feab",
+ strip_prefix = "rusty_v8-350c5a859a9b3c84ab8701b3ad51e3b7699b30c0",
type = "tar.gz",
- urls = ["https://static.crates.io/crates/v8/v8-152.2.0.crate"],
+ urls = ["https://github.com/seanh-oai/rusty_v8/archive/350c5a859a9b3c84ab8701b3ad51e3b7699b30c0.tar.gz"],
)
git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 9ac9cc6ab1e1..834f2c1f34e9 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -1273,10 +1273,10 @@
"dunce_1.0.5": "{\"dependencies\":[],\"features\":{}}",
"dupe_0.9.1": "{\"dependencies\":[{\"name\":\"dupe_derive\",\"req\":\"=0.9.1\"}],\"features\":{}}",
"dupe_derive_0.9.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0.3\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}",
- "dylint_6.1.0": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0\"},{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"cargo-util\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"cargo-util-schemas\",\"optional\":true,\"req\":\"^0.14\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"default_features\":false,\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"dunce\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"config\",\"git\",\"link\",\"packaging\",\"rustup\"],\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"features\":[\"cargo\"],\"kind\":\"build\",\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"fs_extra\",\"optional\":true,\"req\":\"^1.3\"},{\"name\":\"glob\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"heck\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"hex\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"rewriter\",\"optional\":true,\"req\":\"^2.1\"},{\"name\":\"rustc-stable-hash\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"semver\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde-untagged\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"default-syntaxes\",\"regex-fancy\",\"parsing\"],\"name\":\"syntect\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^1.1\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.5\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"__cargo_cli\":[\"cargo-util\",\"cargo-util-schemas\",\"dunce\",\"dylint_internal/home\",\"fs_extra\",\"glob\",\"hex\",\"rustc-stable-hash\",\"serde-untagged\",\"toml\",\"url\"],\"__driver_from_crates_io\":[],\"default\":[],\"library_packages\":[\"__cargo_cli\"],\"package_options\":[\"chrono\",\"dylint_internal/clippy_utils\",\"dylint_internal/git\",\"heck\",\"rewriter\",\"syntect\",\"walkdir\"]}}",
- "dylint_internal_6.1.0": "{\"dependencies\":[{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.2\"},{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.13\"},{\"name\":\"cargo-util\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"cargo_metadata\",\"optional\":true,\"req\":\"^0.23\"},{\"name\":\"ctor\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.11\"},{\"name\":\"git2\",\"optional\":true,\"req\":\"^0.21\"},{\"name\":\"home\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"predicates\",\"req\":\"^3.1\"},{\"name\":\"regex\",\"req\":\"^1.13\"},{\"name\":\"semver\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"tar\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.27\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^1.1\"},{\"name\":\"toml_edit\",\"optional\":true,\"req\":\"^0.25\"},{\"kind\":\"dev\",\"name\":\"toml_edit\",\"req\":\"^0.25\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"cargo\":[\"anstyle\",\"bitflags\",\"cargo_metadata\",\"home\"],\"clippy_utils\":[\"git\",\"semver\",\"tempfile\",\"toml\",\"toml_edit\"],\"config\":[\"cargo_metadata\",\"serde\",\"thiserror\",\"toml\"],\"examples\":[\"cargo\",\"cargo-util\",\"rustup\",\"walkdir\"],\"git\":[\"git2\"],\"home\":[\"dep:home\"],\"link\":[\"cargo\",\"rustup\",\"serde_json\"],\"match_def_path\":[],\"packaging\":[\"cargo\",\"tar\"],\"rustup\":[\"cargo_metadata\"],\"testing\":[\"ctor\",\"env_logger\",\"packaging\"]}}",
- "dylint_linting_6.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.2\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"features\":[\"config\"],\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"features\":[\"clippy_utils\"],\"kind\":\"build\",\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"name\":\"paste\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustc_version\",\"req\":\"^0.4\"},{\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"name\":\"toml\",\"req\":\"^1.1\"},{\"kind\":\"build\",\"name\":\"toml\",\"req\":\"^1.1\"}],\"features\":{\"constituent\":[]}}",
- "dylint_testing_6.1.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"name\":\"compiletest_rs\",\"req\":\"^0.11\"},{\"name\":\"dylint\",\"req\":\"=6.1.0\"},{\"features\":[\"link\"],\"name\":\"dylint_internal\",\"req\":\"=6.1.0\"},{\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"regex\",\"req\":\"^1.13\"},{\"name\":\"tempfile\",\"req\":\"^3.27\"}],\"features\":{\"default\":[],\"deny_warnings\":[]}}",
+ "dylint_6.3.2": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0\"},{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"cargo-util\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"cargo-util-schemas\",\"optional\":true,\"req\":\"^0.14\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"default_features\":false,\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"dunce\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"config\",\"git\",\"link\",\"packaging\",\"rustup\"],\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"features\":[\"cargo\"],\"kind\":\"build\",\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"fs_extra\",\"optional\":true,\"req\":\"^1.3\"},{\"name\":\"glob\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"heck\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"hex\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"rewriter\",\"optional\":true,\"req\":\"^2.1\"},{\"name\":\"rustc-stable-hash\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"semver\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde-untagged\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"default-syntaxes\",\"regex-fancy\",\"parsing\"],\"name\":\"syntect\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^1.1\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.5\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"__cargo_cli\":[\"cargo-util\",\"cargo-util-schemas\",\"dunce\",\"dylint_internal/home\",\"fs_extra\",\"glob\",\"hex\",\"rustc-stable-hash\",\"serde-untagged\",\"toml\",\"url\"],\"__driver_from_crates_io\":[],\"default\":[],\"library_packages\":[\"__cargo_cli\"],\"package_options\":[\"chrono\",\"dylint_internal/clippy_utils\",\"dylint_internal/git\",\"heck\",\"rewriter\",\"syntect\",\"walkdir\"]}}",
+ "dylint_internal_6.3.2": "{\"dependencies\":[{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.2\"},{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.13\"},{\"name\":\"cargo-util\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"cargo_metadata\",\"optional\":true,\"req\":\"^0.23\"},{\"name\":\"ctor\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.11\"},{\"name\":\"git2\",\"optional\":true,\"req\":\"^0.21\"},{\"name\":\"home\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"pastey\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"predicates\",\"req\":\"^3.1\"},{\"name\":\"regex\",\"req\":\"^1.13\"},{\"name\":\"semver\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"tar\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^1.1\"},{\"name\":\"toml_edit\",\"optional\":true,\"req\":\"^0.25\"},{\"kind\":\"dev\",\"name\":\"toml_edit\",\"req\":\"^0.25\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"cargo\":[\"anstyle\",\"bitflags\",\"cargo_metadata\",\"home\"],\"clippy_utils\":[\"git\",\"semver\",\"toml\",\"toml_edit\"],\"config\":[\"cargo_metadata\",\"serde\",\"thiserror\",\"toml\"],\"examples\":[\"cargo\",\"cargo-util\",\"rustup\",\"walkdir\"],\"git\":[\"git2\"],\"home\":[\"dep:home\"],\"link\":[\"cargo\",\"rustup\",\"serde_json\"],\"match_def_path\":[],\"packaging\":[\"cargo\",\"tar\"],\"rustup\":[\"cargo_metadata\"],\"testing\":[\"ctor\",\"env_logger\",\"packaging\",\"rustup\"]}}",
+ "dylint_linting_6.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.2\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"features\":[\"config\"],\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"features\":[\"clippy_utils\"],\"kind\":\"build\",\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"name\":\"paste\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustc_version\",\"req\":\"^0.4\"},{\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.27\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"name\":\"toml\",\"req\":\"^1.1\"},{\"kind\":\"build\",\"name\":\"toml\",\"req\":\"^1.1\"}],\"features\":{\"constituent\":[]}}",
+ "dylint_testing_6.3.2": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"name\":\"compiletest_rs\",\"req\":\"^0.11\"},{\"name\":\"dylint\",\"req\":\"=6.3.2\"},{\"features\":[\"link\"],\"name\":\"dylint_internal\",\"req\":\"=6.3.2\"},{\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"regex\",\"req\":\"^1.13\"},{\"name\":\"tempfile\",\"req\":\"^3.27\"}],\"features\":{\"default\":[],\"deny_warnings\":[]}}",
"dyn-clone_1.0.20": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.66\"}],\"features\":{}}",
"ecdsa_0.16.9": "{\"dependencies\":[{\"name\":\"der\",\"optional\":true,\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"digest\",\"optional\":true,\"req\":\"^0.10.7\"},{\"default_features\":false,\"features\":[\"digest\",\"sec1\"],\"name\":\"elliptic-curve\",\"req\":\"^0.13.6\"},{\"default_features\":false,\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"elliptic-curve\",\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"name\":\"rfc6979\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serdect\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"signature\",\"req\":\"^2.0, <2.3\"},{\"default_features\":false,\"name\":\"spki\",\"optional\":true,\"req\":\"^0.7.2\"}],\"features\":{\"alloc\":[\"elliptic-curve/alloc\",\"signature/alloc\",\"spki/alloc\"],\"arithmetic\":[\"elliptic-curve/arithmetic\"],\"default\":[\"digest\"],\"dev\":[\"arithmetic\",\"digest\",\"elliptic-curve/dev\",\"hazmat\"],\"digest\":[\"dep:digest\",\"signature/digest\"],\"hazmat\":[],\"pem\":[\"elliptic-curve/pem\",\"pkcs8\"],\"pkcs8\":[\"digest\",\"elliptic-curve/pkcs8\",\"der\"],\"serde\":[\"elliptic-curve/serde\",\"serdect\"],\"signing\":[\"arithmetic\",\"digest\",\"hazmat\",\"rfc6979\"],\"std\":[\"alloc\",\"elliptic-curve/std\",\"signature/std\"],\"verifying\":[\"arithmetic\",\"digest\",\"hazmat\"]}}",
"ed25519-dalek_2.2.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"blake2\",\"req\":\"^0.10\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"features\":[\"digest\"],\"name\":\"curve25519-dalek\",\"req\":\"^4\"},{\"default_features\":false,\"features\":[\"digest\",\"rand_core\"],\"kind\":\"dev\",\"name\":\"curve25519-dalek\",\"req\":\"^4\"},{\"default_features\":false,\"name\":\"ed25519\",\"req\":\">=2.2, <2.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"merlin\",\"optional\":true,\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6.4\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha3\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"signature\",\"optional\":true,\"req\":\">=2.0, <2.3\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.3.0\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"static_secrets\"],\"kind\":\"dev\",\"name\":\"x25519-dalek\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.5\"}],\"features\":{\"alloc\":[\"curve25519-dalek/alloc\",\"ed25519/alloc\",\"serde?/alloc\",\"zeroize/alloc\"],\"asm\":[\"sha2/asm\"],\"batch\":[\"alloc\",\"merlin\",\"rand_core\"],\"default\":[\"fast\",\"std\",\"zeroize\"],\"digest\":[\"signature/digest\"],\"fast\":[\"curve25519-dalek/precomputed-tables\"],\"hazmat\":[],\"legacy_compatibility\":[\"curve25519-dalek/legacy_compatibility\"],\"pem\":[\"alloc\",\"ed25519/pem\",\"pkcs8\"],\"pkcs8\":[\"ed25519/pkcs8\"],\"rand_core\":[\"dep:rand_core\"],\"serde\":[\"dep:serde\",\"ed25519/serde\"],\"std\":[\"alloc\",\"ed25519/std\",\"serde?/std\",\"sha2/std\"],\"zeroize\":[\"dep:zeroize\",\"curve25519-dalek/zeroize\"]}}",
@@ -1394,6 +1394,7 @@
"git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186#0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.28\"},{\"name\":\"log\",\"req\":\"0.4.17\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"0.3.1\"},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"0.26.0\"},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26.0\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"tokio/time\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}",
"git+https://github.com/openai-oss-forks/tungstenite-rs?rev=4fffad30fe373adbdcffab9545e9e9bf4f2fc19f#4fffad30fe373adbdcffab9545e9e9bf4f2fc19f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"1.9.0\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true,\"req\":\"2\"},{\"default_features\":false,\"features\":[\"zlib-rs\"],\"name\":\"flate2\",\"optional\":true,\"req\":\"1.1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true,\"req\":\"0.4.0\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true,\"req\":\"1.3.4\"},{\"name\":\"log\",\"req\":\"0.4.8\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.3\"},{\"name\":\"rand\",\"req\":\"0.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true,\"req\":\"0.10\"},{\"name\":\"thiserror\",\"req\":\"2.0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.1.0\"},{\"name\":\"utf-8\",\"req\":\"0.7.5\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}",
"git+https://github.com/rust-lang/rust-clippy?rev=21baba9c10c5d179915cae82e6247bc2337872fb#21baba9c10c5d179915cae82e6247bc2337872fb_clippy_utils": "{\"dependencies\":[{\"default_features\":false,\"features\":[],\"name\":\"arrayvec\",\"optional\":false,\"req\":\"0.7\"},{\"name\":\"itertools\",\"req\":\"0.15\"},{\"name\":\"rustc_apfloat\",\"req\":\"0.2.0\"}],\"features\":{},\"strip_prefix\":\"clippy_utils\"}",
+ "git+https://github.com/seanh-oai/rusty_v8.git?rev=350c5a859a9b3c84ab8701b3ad51e3b7699b30c0#350c5a859a9b3c84ab8701b3ad51e3b7699b30c0_v8": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"2.5\"},{\"name\":\"paste\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"zoneinfo64\"],\"name\":\"temporal_capi\",\"optional\":false,\"req\":\"0.2.3\"},{\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"0.72\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"0.2\"},{\"kind\":\"build\",\"name\":\"gzip-header\",\"req\":\"1.0.0\"},{\"kind\":\"build\",\"name\":\"home\",\"req\":\"0\"},{\"kind\":\"build\",\"name\":\"miniz_oxide\",\"req\":\"0.8.8\"},{\"kind\":\"build\",\"name\":\"which\",\"req\":\"6\"}],\"features\":{\"default\":[\"use_custom_libcxx\"],\"simdutf\":[],\"use_custom_libcxx\":[],\"v8_enable_pointer_compression\":[],\"v8_enable_sandbox\":[\"v8_enable_pointer_compression\"],\"v8_enable_v8_checks\":[]},\"strip_prefix\":\"\"}",
"git2_0.21.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.13\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"libgit2-sys\",\"req\":\"^0.18.4\"},{\"name\":\"log\",\"req\":\"^0.4.8\"},{\"name\":\"openssl-probe\",\"optional\":true,\"req\":\"^0.1\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.45\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"features\":[\"formatting\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.47\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.5.4\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2.5.4\"}],\"features\":{\"cred\":[\"dep:url\"],\"default\":[],\"https\":[\"libgit2-sys/https\",\"openssl-sys\",\"openssl-probe\",\"cred\"],\"ssh\":[\"libgit2-sys/ssh\",\"cred\"],\"unstable\":[],\"unstable-sha256\":[\"libgit2-sys/unstable-sha256\"],\"vendored-libgit2\":[\"libgit2-sys/vendored\"],\"vendored-openssl\":[\"openssl-sys/vendored\",\"libgit2-sys/vendored-openssl\"],\"zlib-ng-compat\":[\"libgit2-sys/zlib-ng-compat\"]}}",
"gix-actor_0.44.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-date\",\"req\":\"^0.18.0\"},{\"features\":[\"bstr\"],\"name\":\"gix-error\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-date/serde\"]}}",
"gix-attributes_0.37.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.1\"},{\"features\":[\"bstr\"],\"name\":\"gix-error\",\"req\":\"^0.5.0\"},{\"name\":\"gix-glob\",\"req\":\"^0.29.0\"},{\"name\":\"gix-parallel\",\"req\":\"^0.50.0\"},{\"name\":\"gix-path\",\"req\":\"^0.14.0\"},{\"name\":\"gix-quote\",\"req\":\"^0.10.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"name\":\"unicode-bom\",\"req\":\"^2.0.3\"}],\"features\":{\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-glob/serde\"]}}",
@@ -1598,7 +1599,7 @@
"libredox_0.1.14": "{\"dependencies\":[{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"ioslice\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"plain\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"redox_syscall\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"base\":[\"libc\"],\"call\":[\"base\"],\"default\":[\"base\",\"call\",\"std\",\"redox_syscall\",\"protocol\"],\"mkns\":[\"ioslice\"],\"protocol\":[\"plain\",\"bitflags\",\"redox_syscall\"],\"std\":[\"base\"]}}",
"libsqlite3-sys_0.37.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72\"},{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.2.27\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.103\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3.19\"},{\"kind\":\"build\",\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2.20\"},{\"default_features\":false,\"kind\":\"build\",\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.36\"},{\"features\":[\"full\",\"extra-traits\",\"visit-mut\"],\"kind\":\"build\",\"name\":\"syn\",\"optional\":true,\"req\":\"^2.0.89\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"optional\":true,\"req\":\"^0.2.15\"}],\"features\":{\"buildtime_bindgen\":[\"bindgen\",\"pkg-config\",\"vcpkg\"],\"bundled\":[\"cc\",\"bundled_bindings\"],\"bundled-sqlcipher\":[\"bundled\"],\"bundled-sqlcipher-vendored-openssl\":[\"bundled-sqlcipher\",\"openssl-sys/vendored\"],\"bundled-windows\":[\"cc\",\"bundled_bindings\"],\"bundled_bindings\":[],\"column_metadata\":[],\"default\":[\"min_sqlite_version_3_34_1\"],\"in_gecko\":[],\"loadable_extension\":[\"prettyplease\",\"quote\",\"syn\"],\"min_sqlite_version_3_34_1\":[\"pkg-config\",\"vcpkg\"],\"preupdate_hook\":[\"buildtime_bindgen\"],\"session\":[\"preupdate_hook\",\"buildtime_bindgen\"],\"sqlcipher\":[],\"unlock_notify\":[],\"wasm32-wasi-vfs\":[],\"with-asan\":[]}}",
"libz-sys_1.1.25": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.98\"},{\"kind\":\"build\",\"name\":\"cmake\",\"optional\":true,\"req\":\"^0.1.50\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.43\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.11\"}],\"features\":{\"asm\":[],\"default\":[\"libc\",\"stock-zlib\"],\"static\":[],\"stock-zlib\":[],\"zlib-ng\":[\"libc\",\"cmake\"],\"zlib-ng-no-cmake-experimental-community-maintained\":[\"libc\"]}}",
- "libz-sys_1.1.29": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.98\"},{\"kind\":\"build\",\"name\":\"cmake\",\"optional\":true,\"req\":\"^0.1.50\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.43\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.11\"}],\"features\":{\"asm\":[],\"default\":[\"libc\",\"stock-zlib\"],\"static\":[],\"stock-zlib\":[],\"zlib-ng\":[\"libc\",\"cmake\"],\"zlib-ng-no-cmake-experimental-community-maintained\":[\"libc\"]}}",
+ "libz-sys_1.1.31": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.98\"},{\"kind\":\"build\",\"name\":\"cmake\",\"optional\":true,\"req\":\"^0.1.50\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.43\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.11\"}],\"features\":{\"asm\":[],\"default\":[\"libc\",\"stock-zlib\"],\"static\":[],\"stock-zlib\":[],\"zlib-ng\":[\"libc\",\"cmake\"],\"zlib-ng-no-cmake-experimental-community-maintained\":[\"libc\"]}}",
"line-clipping_0.3.7": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.11\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.26\"}],\"features\":{}}",
"link-section_0.17.2": "{\"dependencies\":[{\"features\":[\"link_section\"],\"name\":\"linktime-proc-macro\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"macrotest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2\"}],\"features\":{\"default\":[\"proc_macro\",\"std\"],\"proc_macro\":[\"dep:linktime-proc-macro\"],\"std\":[]}}",
"linkify_0.11.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"name\":\"memchr\",\"req\":\"^2.0.1\"}],\"features\":{}}",
@@ -1746,6 +1747,7 @@
"parking_lot_core_0.9.12": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.60\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.95\",\"target\":\"cfg(unix)\"},{\"name\":\"petgraph\",\"optional\":true,\"req\":\"^0.6.0\"},{\"name\":\"redox_syscall\",\"req\":\"^0.5\",\"target\":\"cfg(target_os = \\\"redox\\\")\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"deadlock_detection\":[\"petgraph\",\"backtrace\"],\"nightly\":[]}}",
"paste_1.0.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}",
"pastey_0.2.1": "{\"dependencies\":[],\"features\":{}}",
+ "pastey_0.2.3": "{\"dependencies\":[],\"features\":{}}",
"pathdiff_0.2.3": "{\"dependencies\":[{\"name\":\"camino\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1.0.0\"}],\"features\":{}}",
"pbkdf2_0.12.2": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"hmac\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}",
"pbkdf2_0.13.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"belt-hash\",\"req\":\"^0.2\"},{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.13\"},{\"name\":\"kdf\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"base64\"],\"name\":\"mcf\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.11\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.11\"}],\"features\":{\"alloc\":[\"mcf?/alloc\",\"password-hash?/alloc\"],\"default\":[\"hmac\"],\"getrandom\":[\"password-hash/getrandom\"],\"kdf\":[\"sha2\",\"dep:kdf\"],\"mcf\":[\"sha2\",\"password-hash\",\"dep:mcf\"],\"phc\":[\"password-hash/phc\",\"sha2\"],\"rand_core\":[\"password-hash/rand_core\"],\"sha2\":[\"hmac\",\"dep:sha2\"]}}",
@@ -2230,7 +2232,6 @@
"utf8_iter_1.0.4": "{\"dependencies\":[],\"features\":{}}",
"utf8parse_0.2.2": "{\"dependencies\":[],\"features\":{\"default\":[],\"nightly\":[]}}",
"uuid_1.20.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"atomic\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"borsh-derive\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.20.0\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"md-5\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.56\"},{\"default_features\":false,\"name\":\"sha1_smol\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"slog\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.52\"},{\"name\":\"uuid-rng-internal-lib\",\"optional\":true,\"package\":\"uuid-rng-internal\",\"req\":\"^1.20.0\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"default_features\":false,\"features\":[\"msrv\"],\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"features\":[\"derive\"],\"name\":\"zerocopy\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"atomic\":[\"dep:atomic\"],\"borsh\":[\"dep:borsh\",\"dep:borsh-derive\"],\"default\":[\"std\"],\"fast-rng\":[\"rng\",\"dep:rand\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"macro-diagnostics\":[],\"md5\":[\"dep:md-5\"],\"rng\":[\"dep:getrandom\"],\"rng-getrandom\":[\"rng\",\"dep:getrandom\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/getrandom\"],\"rng-rand\":[\"rng\",\"dep:rand\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/rand\"],\"serde\":[\"dep:serde_core\"],\"sha1\":[\"dep:sha1_smol\"],\"std\":[\"wasm-bindgen?/std\",\"js-sys?/std\"],\"v1\":[\"atomic\"],\"v3\":[\"md5\"],\"v4\":[\"rng\"],\"v5\":[\"sha1\"],\"v6\":[\"atomic\"],\"v7\":[\"rng\"],\"v8\":[]}}",
- "v8_152.2.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"align-data\",\"req\":\"^0.1.0\"},{\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"^0.72\"},{\"kind\":\"dev\",\"name\":\"bindgen\",\"req\":\"^0.72\"},{\"name\":\"bitflags\",\"req\":\"^2.5\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"fslock\",\"req\":\"^0.2\"},{\"kind\":\"build\",\"name\":\"gzip-header\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"gzip-header\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"home\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"home\",\"req\":\"^0\"},{\"kind\":\"build\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.8\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.8\"},{\"name\":\"paste\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"features\":[\"zoneinfo64\"],\"name\":\"temporal_capi\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.96\"},{\"kind\":\"build\",\"name\":\"which\",\"req\":\"^6\"},{\"kind\":\"dev\",\"name\":\"which\",\"req\":\"^6\"}],\"features\":{\"default\":[\"use_custom_libcxx\"],\"simdutf\":[],\"use_custom_libcxx\":[],\"v8_enable_pointer_compression\":[],\"v8_enable_sandbox\":[\"v8_enable_pointer_compression\"],\"v8_enable_v8_checks\":[]}}",
"valuable_0.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"valuable-derive\",\"optional\":true,\"req\":\"=0.1.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"derive\":[\"valuable-derive\"],\"std\":[\"alloc\"]}}",
"vcpkg_0.2.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3.7\"}],\"features\":{}}",
"version-compare_0.2.1": "{\"dependencies\":[],\"features\":{}}",
@@ -2421,107 +2422,107 @@
"zvariant_utils_2.1.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.81\"},{\"name\":\"quote\",\"req\":\"^1.0.36\"},{\"features\":[\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.64\"}],\"features\":{}}"
},
"@@rules_rs+//rs/toolchains:module_extension.bzl%toolchains": {
- "cargo-1.99.1-aarch64-apple-darwin.tar.xz": "685e563d94db1f003ffa5f5831439eec02569b9950b69867953c8213269c1eaf",
- "cargo-1.99.1-aarch64-pc-windows-msvc.tar.xz": "28ae5b9b72ae43aa01d2f6c1b285be59f8aef6eafc96f9e16b912c3c1363a638",
- "cargo-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "5156832e0c1254e9b49a79edabdc6d4c35f32f8fc1cf3ca700f268ff96087ab0",
- "cargo-1.99.1-x86_64-apple-darwin.tar.xz": "6636dabcec164909d1c6eb9e2c596148787c84f7d3a02ff68a141569077f80b5",
- "cargo-1.99.1-x86_64-pc-windows-msvc.tar.xz": "4adb46c3707dee790682f7769d86983841148111695f561c843c33daa2af0c9b",
- "cargo-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "53f3521949404d595efb06a55c12c5658d4ed1c75566f33d3d514eaebe70bd8a",
- "clippy-1.99.1-aarch64-apple-darwin.tar.xz": "c463f31831196702fc544900a0dc4c1837905327507fbec751fe46e59ca098bf",
- "clippy-1.99.1-aarch64-pc-windows-msvc.tar.xz": "6324b172cdda7ca117df406564a6c1f6634f983c06afc71c7a9fd46a4e34bfa8",
- "clippy-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "3bea76c26dcf30c176c0a8a8cc283eb59fabd754e24a68b4ca0701e5f3245f6b",
- "clippy-1.99.1-x86_64-apple-darwin.tar.xz": "b5810174b696eaa05dd4ffd5b24c40a33c6d4bccc05fb72264006b7bfefb8c5d",
- "clippy-1.99.1-x86_64-pc-windows-msvc.tar.xz": "eb20d3f60731450acecd7c6b108bec2eb4abd18321b383e6663a3f072fe9c0b3",
- "clippy-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "458339712a058e52cc4522ff9ef8b59cd1ea1340312216cd94194c464a3f6dc5",
- "rust-analyzer-1.99.1-aarch64-apple-darwin.tar.xz": "c1ee8a54e685e13414e53fc46029d46db12613bc45c081f1a49020f1674ad6b4",
- "rust-analyzer-1.99.1-aarch64-pc-windows-msvc.tar.xz": "a4df9d29b15a2ab6f9b6fefe1198f3bdb03e2b7e91899cdc0f0fdf8b68b5c44b",
- "rust-analyzer-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "b626351402eb9ea16af2ebc2b2cd02ec6f6ca6f0c18fa74b41243eb42996e2b1",
- "rust-analyzer-1.99.1-x86_64-apple-darwin.tar.xz": "fe414afba5aa69e7994e29dddd9b166496900e70acc51fd3a55c82bfac86fe97",
- "rust-analyzer-1.99.1-x86_64-pc-windows-msvc.tar.xz": "e8d29f16781a71468dbff5c602dd48d6c544696c957752be783ed5a1edd954e4",
- "rust-analyzer-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "5f2613d62003fc8cbe788d4ae409359c91c241ba248adbefe032df8bb77357bf",
- "rust-src-1.99.1.tar.xz": "e485a88c45db546b73571df7bebfcb4454ba4fb552c492e25fc5b00eeb5bf22d",
- "rust-std-1.99.1-aarch64-apple-darwin.tar.xz": "fe79d6ab5a0321de26463999d8c112b8ad3eafbcbcf822d66a2300b45dde1031",
- "rust-std-1.99.1-aarch64-apple-ios-macabi.tar.xz": "0d5ec58e0b034fab7decd4e72f21cfa6428421585ca98cf6ab65d3d5369b3f8b",
- "rust-std-1.99.1-aarch64-apple-ios-sim.tar.xz": "9780e3b33bb4aad552b2288de1aa65dff4feba66f51b77a4a6b2c52bfd455a28",
- "rust-std-1.99.1-aarch64-apple-ios.tar.xz": "d62c2e84717c6fa56ef0d8e5a11821850fb717c40879075a8e465a1e15af247c",
- "rust-std-1.99.1-aarch64-linux-android.tar.xz": "7463b06bb5f734c02d3bac967538b4de4da910fb90668398bc21b10b5620e35d",
- "rust-std-1.99.1-aarch64-pc-windows-gnullvm.tar.xz": "cf14a1b1fb7f32b90369d91f97fa157fe1ca43edb64235df951006baab677a95",
- "rust-std-1.99.1-aarch64-pc-windows-msvc.tar.xz": "044748841f47007a222dd5b3f5e9dacec1dae7386d7d308c5172d07ea7112fa0",
- "rust-std-1.99.1-aarch64-unknown-fuchsia.tar.xz": "c28c7143f598af3cdaa8d62e120a2348473e7a7cb580b0958c99ec1b1d764fbc",
- "rust-std-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "e25668477083d6a9ddb87b9beb9c00742225f38233ccb4bfba3afa7a07427f52",
- "rust-std-1.99.1-aarch64-unknown-linux-musl.tar.xz": "2f3eb0d0610e2b7f6b2121d106e2490ac648decfa0c09b30873bdfc60de6c1ab",
- "rust-std-1.99.1-aarch64-unknown-none-softfloat.tar.xz": "6ff3ecba5ae285c5286ad6f5024994d818cee569c59d980aa2edc9e5fa3bcd4e",
- "rust-std-1.99.1-aarch64-unknown-none.tar.xz": "fa76b51dba64b20f6ca0593ed9bfb582a91fb0060fa803c54b68f1f6a3774eb9",
- "rust-std-1.99.1-aarch64-unknown-uefi.tar.xz": "e48e00681ea2c989d39274e3ed50ffdacb3bccea0950ea1789ab5075c8f36dae",
- "rust-std-1.99.1-arm-linux-androideabi.tar.xz": "6973466090a51ab5d5fadde4449cb2e521371ede85b35f95252ba2119aceb9b6",
- "rust-std-1.99.1-arm-unknown-linux-gnueabi.tar.xz": "fb93e9715ef14ed17fd6922dd200d14cfe855b46c4fa1e8a03ee85ca75646022",
- "rust-std-1.99.1-arm-unknown-linux-gnueabihf.tar.xz": "32278298365b7b78cf5b67ec34bf5e11821679dfea8a6d867725af860c4cbe50",
- "rust-std-1.99.1-arm-unknown-linux-musleabi.tar.xz": "f66cb84ada451344f46757c72d1bfa5a34e8f174affe67b7fa9c3b2d8a033e26",
- "rust-std-1.99.1-arm-unknown-linux-musleabihf.tar.xz": "74adeec515565c17242c8fafedbda40a30a49f85e9510fc60013cbc539f83782",
- "rust-std-1.99.1-armv7-linux-androideabi.tar.xz": "8c5c8d2bb1faf551ab21de3c9f9f930bfc7e610222e08bfae43b9438413fe300",
- "rust-std-1.99.1-armv7-unknown-linux-gnueabi.tar.xz": "d9f8155889cd3f4fc36ac35dd1ec357c46ae9cade0b106aca03e272b005e0823",
- "rust-std-1.99.1-armv7-unknown-linux-gnueabihf.tar.xz": "53fdf6581a24f510def5173813a0d23518a8cde547b9fbf4876499f19feda18c",
- "rust-std-1.99.1-armv7-unknown-linux-musleabi.tar.xz": "eaf5ee2b0948e18e586af42aa9aeb0e8e5aba515bc2d862bf0d23dc59569a8a9",
- "rust-std-1.99.1-armv7-unknown-linux-musleabihf.tar.xz": "edccfaef9195cb74b5c33541e587629f92433f59fb5e4bbb7b8293f966ecd60a",
- "rust-std-1.99.1-i686-linux-android.tar.xz": "76494f24073c8c3f0f98649580bf4ae8d3e91831c763017c2d120ade90a5a108",
- "rust-std-1.99.1-i686-pc-windows-gnu.tar.xz": "bf9668700154fcce55eca02d785da580eea561c1b6f70e723fd2f8a4296fd93f",
- "rust-std-1.99.1-i686-pc-windows-gnullvm.tar.xz": "28ffcbd7ff0f7cf1d4104fce7ef72d186a3e1c583319e8e32b69957d17ce498a",
- "rust-std-1.99.1-i686-pc-windows-msvc.tar.xz": "3a086606ba2581d43e905f65466d122c257ef0329ae3fbc3ceb067286d0668b0",
- "rust-std-1.99.1-i686-unknown-freebsd.tar.xz": "90342aa51874648ca441913f8df923a915dcbc229694b8bf4620b3d53389350f",
- "rust-std-1.99.1-i686-unknown-linux-gnu.tar.xz": "8abb74feac06cfb0d3de4e27301adb05f740661c7d6fb154528906202157ae15",
- "rust-std-1.99.1-i686-unknown-linux-musl.tar.xz": "25df5dae9ead02745f709ed133f09700abdec5eb7a8027e08bb41aefb1a451a1",
- "rust-std-1.99.1-i686-unknown-uefi.tar.xz": "c1297ae654a1a695056aa3c5113c67a9e68616bf225ba3da78873cc3cc538792",
- "rust-std-1.99.1-loongarch64-unknown-linux-gnu.tar.xz": "6400e6880a1f8183bba1a8b939cb1c6d189ab64e4aff4f246c00161941ee76e8",
- "rust-std-1.99.1-loongarch64-unknown-linux-musl.tar.xz": "3053ebe881b3434879a934012c4a61b1fa94a0aec922115a574cc81e8e3ca15e",
- "rust-std-1.99.1-loongarch64-unknown-none.tar.xz": "01dd53deb6fe0d62b0dffa0e172935c1cf6ab1f59bf6287306df0937db47db9b",
- "rust-std-1.99.1-powerpc-unknown-linux-gnu.tar.xz": "25d05cf16db111ecfe7dd8e1a3d61d144f2348e87dafeb07ff3cf402e3196cfd",
- "rust-std-1.99.1-powerpc64-unknown-linux-gnu.tar.xz": "58f8526a50eb47eb106130a29e77c97e3c5fd60af41e8a16a63d39673f9aa33d",
- "rust-std-1.99.1-powerpc64le-unknown-linux-gnu.tar.xz": "4bfb8ca55cc13f11addc5782a946b360b78888a18c8e315635b822f961f553c6",
- "rust-std-1.99.1-powerpc64le-unknown-linux-musl.tar.xz": "6db9c7e403354b0f52321d286a032565a84aa925624daed33a87c02f956e302f",
- "rust-std-1.99.1-riscv32imc-unknown-none-elf.tar.xz": "1c92e40aca279ee001a3a36d816140d49997f4b3791d81ae67175cfcbdb1d817",
- "rust-std-1.99.1-riscv64gc-unknown-linux-gnu.tar.xz": "0fe2aa8294281e16886a101466160ec460ed99e51c8af11e1c76f88f8bd0de6b",
- "rust-std-1.99.1-riscv64gc-unknown-linux-musl.tar.xz": "59797f5c7c87ef38c1d202576cb6fb9c6423730d8efcaf2fab7c367d2d878a39",
- "rust-std-1.99.1-riscv64gc-unknown-none-elf.tar.xz": "bf5b831d8df3ad039770dffaf59ad3492ff0b239a5c4bb225a1961831c117b3e",
- "rust-std-1.99.1-s390x-unknown-linux-gnu.tar.xz": "a5e394a3d85c6888f016abc9ac02dceadc6eb8a7216b8a5ba863d6cf649766ae",
- "rust-std-1.99.1-sparc64-unknown-linux-gnu.tar.xz": "c73df3e84bba70fa87151ead94aac7848fee8334f16526bc2f97d20372b327ae",
- "rust-std-1.99.1-thumbv6m-none-eabi.tar.xz": "2097eeae25f0d9a76a9fab9d78e5b8eed6f5c262946c9c0418f23aa6273e9b2f",
- "rust-std-1.99.1-thumbv7em-none-eabi.tar.xz": "dbe6561e6a655e9866a8502961fe7089bffd438195945d03a190ee59a9568a02",
- "rust-std-1.99.1-thumbv7em-none-eabihf.tar.xz": "dff135161000157504513c459a9646e52125d8b825395559c35ce78d54d226c8",
- "rust-std-1.99.1-thumbv7m-none-eabi.tar.xz": "04c171e0c59fee9ea8b4b27f0195eadbf2f47495e06e14ff09d19eb6c6946d38",
- "rust-std-1.99.1-thumbv8m.main-none-eabi.tar.xz": "59251d3c7d1804242c4a1ade2a08b0e133ee83537deff860408b08d1a239f938",
- "rust-std-1.99.1-thumbv8m.main-none-eabihf.tar.xz": "a24ca64600d6ff0d7f765ff4d60cb697b1535b6f4fdb3fe3054918b75c702e0f",
- "rust-std-1.99.1-wasm32-unknown-emscripten.tar.xz": "1797fc9e45d1a8fe819573e9d002fd9f3d4e680d3cfc4bb49af43cebd3096e90",
- "rust-std-1.99.1-wasm32-unknown-unknown.tar.xz": "5f628f857dd00a8aa3fcac2e275665d2782066a5795274ed124c5b560f46423c",
- "rust-std-1.99.1-wasm32-wasip1-threads.tar.xz": "5d44556bbae559fd029f207c8c2140b63ed70f0aa29f08be1bf42bed1bf7f716",
- "rust-std-1.99.1-wasm32-wasip1.tar.xz": "d498fbff3fd032f2f5fabc299e5cbf7bc9d64abac6e389fc1a315b69821b48c4",
- "rust-std-1.99.1-wasm32-wasip2.tar.xz": "15d54cff9ce112ab273cf7e214600108754d51100ce7d6bd62a354f16c93b4f3",
- "rust-std-1.99.1-x86_64-apple-darwin.tar.xz": "484d5b88b424ddd29df196e1eb9420c8335bb5fac90a3c37140253345a8c65c4",
- "rust-std-1.99.1-x86_64-apple-ios-macabi.tar.xz": "a85f3aacdc89a737b384f9676c4cfbc9e1f802c8ae4e10ba298bd07e06f2501e",
- "rust-std-1.99.1-x86_64-apple-ios.tar.xz": "cfadc69ce94f86a7517510389893cc10a4df88e619536bd7bb5f4db57d76bd94",
- "rust-std-1.99.1-x86_64-linux-android.tar.xz": "48132cd1f40a8112345ead45e8483495ab48285f152100f3ae1b1190bed4d8d4",
- "rust-std-1.99.1-x86_64-pc-windows-gnu.tar.xz": "b17e3bc962ff24b3bc47a750d838defed85b280746250e3a3ad029e6c1a29dd7",
- "rust-std-1.99.1-x86_64-pc-windows-gnullvm.tar.xz": "8ddc26705e13c46530c898524b0d2657b7d416a25e8d9ffe369a97b23a619bf6",
- "rust-std-1.99.1-x86_64-pc-windows-msvc.tar.xz": "4fe09430ded40b8b3092b49f0a651178eb1f8154ef4d1f1a73f2a3595b2d956f",
- "rust-std-1.99.1-x86_64-unknown-freebsd.tar.xz": "cd11b574be47094026f0fdb12ab3ae7fdb16acea71143d87c3ccf0208e6c9852",
- "rust-std-1.99.1-x86_64-unknown-fuchsia.tar.xz": "00e4d731dd46e0879bd25a280178627b2bc53b4143d7ec3fac05b746ea8f5537",
- "rust-std-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "109b074225bf5b9ea3820890876bfdf4ed161adbfaf94b2b4b5984c85ee5d958",
- "rust-std-1.99.1-x86_64-unknown-linux-musl.tar.xz": "1dd241eaeb19ff3b5180461c77bdee93d6cafc669eda9de834e7c06b0ec3ca95",
- "rust-std-1.99.1-x86_64-unknown-netbsd.tar.xz": "97569760ad6e042d56ccdac2fd295df8a10351403b9bbca2a19567f8ed6ae46b",
- "rust-std-1.99.1-x86_64-unknown-none.tar.xz": "ae62c0c5ea47af55eb432dabc8f4a92c1815965633fc3ecf4056c02948f302cf",
- "rust-std-1.99.1-x86_64-unknown-uefi.tar.xz": "436c3de8edf19b2bea8f877f495927d2bdef4f3f6e6c67eae3eb5d31b0820616",
- "rustc-1.99.1-aarch64-apple-darwin.tar.xz": "23948a0a9c162fd2952defb3fb0462c44e8fcfd1a4ab8a274a1e1bdb2cf810a3",
- "rustc-1.99.1-aarch64-pc-windows-msvc.tar.xz": "4d89c7f3f9d8637c667332adf77b55f43c322ae0ff038e75493bb28e16369af2",
- "rustc-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "acdcdfe09ae1f103aa9a2b7f88903a9588bc2e99288af10e6fbf5514e2e49f25",
- "rustc-1.99.1-src.tar.xz": "6451598a99e77f07571960f6b21d577399d3a30f33e80cbd2e3a4aae6535d265",
- "rustc-1.99.1-x86_64-apple-darwin.tar.xz": "203feef2c2165d7dda1f30ccf75590f72a6abc52ce259ed1c418de84a6887698",
- "rustc-1.99.1-x86_64-pc-windows-msvc.tar.xz": "7da83c2af1f209606b0160f25174d2867c7c7b73667c32c9e5fbcef4f5f45348",
- "rustc-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "2ff05ff9c08a90861e7f6e4065b40a8b87463b4576186fd613871c3e37f2091f",
- "rustfmt-1.99.1-aarch64-apple-darwin.tar.xz": "8b80dda570f140ee9ced8799294e812bba847a025f185bae4cba85a2ba235570",
- "rustfmt-1.99.1-aarch64-pc-windows-msvc.tar.xz": "2ad35c2fd069066955c40eaa48fe8c5f14c55c86de73fda35ab53e7decef49a8",
- "rustfmt-1.99.1-aarch64-unknown-linux-gnu.tar.xz": "26b3200b1461921415927abf7fe410202a6eda2db3e419abde59e0abfcd93fc2",
- "rustfmt-1.99.1-x86_64-apple-darwin.tar.xz": "06491b425e02612479c30ad9a49f72f88bc8e033310ec8cc238a15eafecf91b7",
- "rustfmt-1.99.1-x86_64-pc-windows-msvc.tar.xz": "1a4d309df5ecacbefe5798c092a03f9d00aa9e1cbc65673c4ace1367c7a25989",
- "rustfmt-1.99.1-x86_64-unknown-linux-gnu.tar.xz": "e3b36c4c7ec3486c059d8067ff48787676612007123aad7a4e8ec12c48ee9ba2"
+ "cargo-1.95.0-aarch64-apple-darwin.tar.xz": "6c2ffed8e1ac9cf4dc9e80f282a869a6b237a153e7c55cca039d33de29d80aaf",
+ "cargo-1.95.0-aarch64-pc-windows-msvc.tar.xz": "e645b30fa035a18aa12d28b699052014c7efa9dd4a33dabd223f0d16b5fa28e8",
+ "cargo-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "7c070aeba9bbf12073646995a03f36c346bb5f541d0078ba6d9dc2a7adaaf6af",
+ "cargo-1.95.0-x86_64-apple-darwin.tar.xz": "e2e1131ade2dddc0d779e0ab3a6a990085c7a654951235742823c3a1ce0f190f",
+ "cargo-1.95.0-x86_64-pc-windows-msvc.tar.xz": "cab2606cb2d0aa31c55d50512fe07a9f15e893227566fbeb448306760cd0d2bf",
+ "cargo-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "e74edd2cf7d0f1f1383b4f00eb90c843750bc489e2ccf7214e6476678a907425",
+ "clippy-1.95.0-aarch64-apple-darwin.tar.xz": "fd183baa023d0c4e0c5b8184226e2d4c85126adf156cb1f3a726ec593bba8d62",
+ "clippy-1.95.0-aarch64-pc-windows-msvc.tar.xz": "44c1b7ada72aa8f3fcaceb37a3899665bc9b160c2fea77879c8ecb65a9e97eba",
+ "clippy-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "fb021e0c0fc2238be9266d7614f4a26bc372544c4cba3528d729ab24ad229fc9",
+ "clippy-1.95.0-x86_64-apple-darwin.tar.xz": "e47367f6b1489d74cbba93b387310adcb82e27a51e44b2c6ff543eb4f199fe32",
+ "clippy-1.95.0-x86_64-pc-windows-msvc.tar.xz": "ddc151d6f58c6658b7380292ecaef36e62d063bbdbf7f5802669810575bb5b75",
+ "clippy-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "ac779bc9839dd47180806b133e4e2563c4a34716284cd5b8fede8ef289f452ca",
+ "rust-analyzer-1.95.0-aarch64-apple-darwin.tar.xz": "11231fc6574301b94bd379af4ef409caef7c65b877bcecf2b227dc0d74aa0ec7",
+ "rust-analyzer-1.95.0-aarch64-pc-windows-msvc.tar.xz": "92958624f23d4b0980748ac9e6d67f6f67a868f8224e8c6240e3f84145e2d805",
+ "rust-analyzer-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "b37e5b9aad624e54228254f98a710ee19ad464fe7ada93ef12e20c87886a0047",
+ "rust-analyzer-1.95.0-x86_64-apple-darwin.tar.xz": "6cd111900e13fd19b188c5d8844b34136af3967066c0ea2914ce5c3508296c85",
+ "rust-analyzer-1.95.0-x86_64-pc-windows-msvc.tar.xz": "ba58e349f5e8b0ef13735c48d4ad8d8c7664472f8403f3c9d97b291bd54a7638",
+ "rust-analyzer-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "a9d71c6e7427c45afcd846a8b34a3e3301ae7a0e91a2bcf929326af77a7dc68e",
+ "rust-src-1.95.0.tar.xz": "67b09138c8db96afc4bbfc69ea771ac9a091fd777698acb43f6dfd9fb7dea363",
+ "rust-std-1.95.0-aarch64-apple-darwin.tar.xz": "9b30089b0f767cb91b2190ffec55a9beeb2a21a1405d8da0f664d7e09d08e6d8",
+ "rust-std-1.95.0-aarch64-apple-ios-macabi.tar.xz": "0e1760828f4e0fa1cde0061ba5680619dcc1cdcafec9242cc18dc4547c73b1cd",
+ "rust-std-1.95.0-aarch64-apple-ios-sim.tar.xz": "4bfe5b0c74c10d121a8ac60f1833c7714b963f9130f6256ca313d94405267deb",
+ "rust-std-1.95.0-aarch64-apple-ios.tar.xz": "6fcc42d8dbba4910a128ffa32d62a730339a7e3882a90341a881f2edf66ff55a",
+ "rust-std-1.95.0-aarch64-linux-android.tar.xz": "de5e8fa5d955809891eea77682811fc90be705f78883bd94071e98f5a738d05b",
+ "rust-std-1.95.0-aarch64-pc-windows-gnullvm.tar.xz": "93d810b8872771afe04f66aa30a4eee48736aca693186e4c7cc2766e1a82e340",
+ "rust-std-1.95.0-aarch64-pc-windows-msvc.tar.xz": "be21b5a8a71c49b4dcbc19956233b0de7bfda3ee3c8a199148299f867e95cb42",
+ "rust-std-1.95.0-aarch64-unknown-fuchsia.tar.xz": "b311a0523f75e031d683d983515edb9baaf22a843dbd44ce2a30a3204752f592",
+ "rust-std-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "3a21b271b1ff973b94d69b25e7a39992f9fbcae1ab6d9475844a23e6ad3908ac",
+ "rust-std-1.95.0-aarch64-unknown-linux-musl.tar.xz": "f6710416ed9a7d5cf2a15efa761eb79a1deeb43f9961bbe05cc97bec4ef9064a",
+ "rust-std-1.95.0-aarch64-unknown-none-softfloat.tar.xz": "54d691468e25e7989b022a171337beadf78b5202877b312b75182b7f93efbb8b",
+ "rust-std-1.95.0-aarch64-unknown-none.tar.xz": "2b0c986dc9902866311f1fe2d44bc2bd84479d2ac84ed7ada76a5eb7ba37080f",
+ "rust-std-1.95.0-aarch64-unknown-uefi.tar.xz": "ca657564103024d345ca32e8e4ade7ebb395a51163d2897d9e5f8373c025e49e",
+ "rust-std-1.95.0-arm-linux-androideabi.tar.xz": "12a9c5fa24608159c2b2bd50abc0c6d0add407c0258cee894c2f61c07051a9c4",
+ "rust-std-1.95.0-arm-unknown-linux-gnueabi.tar.xz": "f6cd592dacdf41f724ee90a2f34db028e37ca2a7fb26fa86e93e8fd68e24d066",
+ "rust-std-1.95.0-arm-unknown-linux-gnueabihf.tar.xz": "fda8408ea17881c6529e27e58672d6c628f786cad557fac92856077e7a610239",
+ "rust-std-1.95.0-arm-unknown-linux-musleabi.tar.xz": "62f21fabc209fd0de53156764fc426da74c59525bb60cb4c1c3ffd1be0bbe00b",
+ "rust-std-1.95.0-arm-unknown-linux-musleabihf.tar.xz": "2842ce67f7a4c68c6e8b30ad3bb36484fb745edcc2694b2a36bf0609cf758044",
+ "rust-std-1.95.0-armv7-linux-androideabi.tar.xz": "61e95e144986c52ff9fa2fe3c249a68b2bf268adb2a2eeb81d80c180e43027f1",
+ "rust-std-1.95.0-armv7-unknown-linux-gnueabi.tar.xz": "e23454d6ca7fc3f5eb7cf9241572765176d86e9f45d4f394de31a5fd794e523f",
+ "rust-std-1.95.0-armv7-unknown-linux-gnueabihf.tar.xz": "bd319e18ca2dad0450f76277874d56356330da536be8cf271509f8e6f28ac6de",
+ "rust-std-1.95.0-armv7-unknown-linux-musleabi.tar.xz": "a49bc987e0531800f92825ce61402b5734ea747caa8970d3373506742ed7daa6",
+ "rust-std-1.95.0-armv7-unknown-linux-musleabihf.tar.xz": "77f9aaff4669c076edc96cdb99dc21749d2c692c862232b52176572c289fe671",
+ "rust-std-1.95.0-i686-linux-android.tar.xz": "e7b5e18d1d4119c7c1454ee8427933f27aeff9e81a22248c2829f5f299d3a937",
+ "rust-std-1.95.0-i686-pc-windows-gnu.tar.xz": "2e98fb94fc500690d7e72e071ce29dd98790ff518163963f5c82c32afba9231d",
+ "rust-std-1.95.0-i686-pc-windows-gnullvm.tar.xz": "b429a8c456d6a1815ece064102bd6a25f67d46db4f6d7b79dbe7d1b9d1f78e0f",
+ "rust-std-1.95.0-i686-pc-windows-msvc.tar.xz": "6206fd7e8bd119e9d1ba1c425ad25c282512eb0d5659f2dcb4be224b84715706",
+ "rust-std-1.95.0-i686-unknown-freebsd.tar.xz": "3b13b3ccecf482c0da3e94f19e0aa5e8e375622f4e09b30e6dae2b7638bee63f",
+ "rust-std-1.95.0-i686-unknown-linux-gnu.tar.xz": "527c5d5249a7f77b48d3c9da3ac512d27b47f43d08dbe3c6f82a3d5b35d8aa27",
+ "rust-std-1.95.0-i686-unknown-linux-musl.tar.xz": "af4d3e7aabb63d39a7a2ff5435cc993b65ff38a2d2e23f1967e519037a1b0455",
+ "rust-std-1.95.0-i686-unknown-uefi.tar.xz": "3233985273616ec36861f2d50b4a025c903b2bb8c45b171c0ae9e2de8342125b",
+ "rust-std-1.95.0-loongarch64-unknown-linux-gnu.tar.xz": "eaf2c37c3293eea742e7ab20f25718ab19c93bd381df8823113fce70460c19c3",
+ "rust-std-1.95.0-loongarch64-unknown-linux-musl.tar.xz": "959b1bf99bc724c87bc4f2c0d184eb0554c134885c05c90d060eb572838924fd",
+ "rust-std-1.95.0-loongarch64-unknown-none.tar.xz": "139e8bdc86cbc21e149a2229c092f74741c907ef6424fa8ce8d435db47895cb6",
+ "rust-std-1.95.0-powerpc-unknown-linux-gnu.tar.xz": "59e0abbaa246502521e37c55b8d6cf88d5b8a697b0c70c61ec189937308f7246",
+ "rust-std-1.95.0-powerpc64-unknown-linux-gnu.tar.xz": "cc7fb9aa289ff1756502ae16a05e2885289165f01ed94a7c2db6576b3dae74a6",
+ "rust-std-1.95.0-powerpc64le-unknown-linux-gnu.tar.xz": "2370d9266051a0b23346d42e43a00f91b2daff22a963fb03e28ae50cb0b76c50",
+ "rust-std-1.95.0-powerpc64le-unknown-linux-musl.tar.xz": "0362ba4ecfe0bb508f0d2b064c6fbbfe72604d5ae1989d6a8a7b4fe5ff1889f1",
+ "rust-std-1.95.0-riscv32imc-unknown-none-elf.tar.xz": "04befebf3b15372dacb7e6c0fcdab842c17a63ea58bf2f4cdccd7182ae9195c6",
+ "rust-std-1.95.0-riscv64gc-unknown-linux-gnu.tar.xz": "50fe7869e166bb4c990a0e1664366b1ffdbe669664b7663cd03c079bd0efdcac",
+ "rust-std-1.95.0-riscv64gc-unknown-linux-musl.tar.xz": "e01bdbf5d6fa3e529671d49e87ba81dc9612101144f3ee5a0e1de3c48f27b47c",
+ "rust-std-1.95.0-riscv64gc-unknown-none-elf.tar.xz": "a4cb7a1527f3b56a39464e5ca2b174a27b708b26d78e604735eb5ffd9ee4d20b",
+ "rust-std-1.95.0-s390x-unknown-linux-gnu.tar.xz": "31978c1286afff9a0bb7f01c2ae4a39f40727b6100a82b6d934f146b06cde510",
+ "rust-std-1.95.0-sparc64-unknown-linux-gnu.tar.xz": "88619b2413d218c119a2060e583a9e835fa5f9cf6ac038070eec10b02c191056",
+ "rust-std-1.95.0-thumbv6m-none-eabi.tar.xz": "602ec023c4615fc1c2d78b688554d42fa525e07e861c052f406fd7a607e5d5ee",
+ "rust-std-1.95.0-thumbv7em-none-eabi.tar.xz": "fb671966ba9aede333956ed43fcfe114ec890ca6e70369c9f3219871ee3ae8ae",
+ "rust-std-1.95.0-thumbv7em-none-eabihf.tar.xz": "fa3d189c09b64d818ad65a3fec1ce1c7d7b3908aea6fc4607a3fcc05067cad81",
+ "rust-std-1.95.0-thumbv7m-none-eabi.tar.xz": "e9e39f483ad4c1ce55fa4f508009e8d7d7ef25efe6592f7bfbabc159a24658ff",
+ "rust-std-1.95.0-thumbv8m.main-none-eabi.tar.xz": "3b64bffb193b37e83dc7b169add55f0ef3298220f8475468be4bb87276a68105",
+ "rust-std-1.95.0-thumbv8m.main-none-eabihf.tar.xz": "25ec92187d3a45fb1e397b0ba6000341d872523925a99a48b1978bdf1e6038cb",
+ "rust-std-1.95.0-wasm32-unknown-emscripten.tar.xz": "967b92a8682e8b8a1a459d776b36e41c906cdcac1008fef70e4800fec40d6864",
+ "rust-std-1.95.0-wasm32-unknown-unknown.tar.xz": "5587b89ff69623d09e476439d44a24453b4e4ea3d5e0b53a5c0a935151ff3fd1",
+ "rust-std-1.95.0-wasm32-wasip1-threads.tar.xz": "9079935a00a3c3aaf284957bbe82972983ce2708019687cec1f4988c30c1e0f3",
+ "rust-std-1.95.0-wasm32-wasip1.tar.xz": "86e5b6d98c7520bb9c3ad4f8cbbbf14beaf230b0f06b437db398e3c4f7dae43e",
+ "rust-std-1.95.0-wasm32-wasip2.tar.xz": "68146eb4c887431379966efa21d75dc957f18bd1166239c1deaa53fe38cb9ab4",
+ "rust-std-1.95.0-x86_64-apple-darwin.tar.xz": "2be13c14122b8d4d09b7f7c434fca9ae7215ec72049944189c88c4d9128ce504",
+ "rust-std-1.95.0-x86_64-apple-ios-macabi.tar.xz": "60b92e51e87f84046e0b19ccedc88c45b4b62c3ab10351f8473453f341c894f1",
+ "rust-std-1.95.0-x86_64-apple-ios.tar.xz": "11abb7b1c92b5a88b8c3ba21ee596a1be7dee5e817b336f26b4968d8ed5513ad",
+ "rust-std-1.95.0-x86_64-linux-android.tar.xz": "77b8e2be4a6e784a63cd77de944864c8044ddf4d5c7d56f663ada8a38a8319c4",
+ "rust-std-1.95.0-x86_64-pc-windows-gnu.tar.xz": "f57e045016a04130125fb43295d95f9ad2bebc296150eadb031dbf5167ad12bd",
+ "rust-std-1.95.0-x86_64-pc-windows-gnullvm.tar.xz": "3c52a0e34e0b4abe4439cabd77383408f5f9c80b6e249fbc0855df424b45008d",
+ "rust-std-1.95.0-x86_64-pc-windows-msvc.tar.xz": "7c659bdc88646e7e1befa370881bd311be87b26f006933a28b40dcab2f7cc832",
+ "rust-std-1.95.0-x86_64-unknown-freebsd.tar.xz": "dfe913c2f477172db10d3723d9f5c536d8b6f42776c979cc855820d8249adec3",
+ "rust-std-1.95.0-x86_64-unknown-fuchsia.tar.xz": "faa2bc09a3992f1d81b538121d64a3a396f4ec666e665c79d2ab47461c2d3206",
+ "rust-std-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "047ea7098803d3500fa1072e9cee5392697e21525559e4458128a2bf874aa382",
+ "rust-std-1.95.0-x86_64-unknown-linux-musl.tar.xz": "aee540abf132920f791ef781489851a078d69dff493fb628d49c1d573f92bb3a",
+ "rust-std-1.95.0-x86_64-unknown-netbsd.tar.xz": "7a82b71c53f20cb147a340819fcab645da220b312a96194531e631ee99783a7d",
+ "rust-std-1.95.0-x86_64-unknown-none.tar.xz": "7c151c0e7bf3b0b4d7136774cd3686e5f691b761b648b17e83af58e7669d3e01",
+ "rust-std-1.95.0-x86_64-unknown-uefi.tar.xz": "4cc55629480aa8ab5b39eb6b7458433b48461d6626fdea0330fb88e23af818ea",
+ "rustc-1.95.0-aarch64-apple-darwin.tar.xz": "149e85a285b6eba58eb6c8bdf7deb1b93763890598e62cb635a712e3a8454f04",
+ "rustc-1.95.0-aarch64-pc-windows-msvc.tar.xz": "0dbec9739b93427ccdd3948c3b1f83cec42e4c9545d930a8d1e1464ff4092c5f",
+ "rustc-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "0fe3689eeaed603e5ef24572d11597d3edadaefd2cb181674ad621260f2501d2",
+ "rustc-1.95.0-src.tar.xz": "62b67230754da642a264ca0cb9fc08820c54e2ed7b3baba0289876d4cdb48c08",
+ "rustc-1.95.0-x86_64-apple-darwin.tar.xz": "33db457715446a69ed6f69f78f5fbb9ca8e17a16585d1d7a0060479bfe4c7afc",
+ "rustc-1.95.0-x86_64-pc-windows-msvc.tar.xz": "4cb1f3b578adc6541cbe13a6f85f1fd8c0ce643d90b506a36dee24c680864c67",
+ "rustc-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "8426a3d170a5879f5682f5fbdd024a1779b3951e7baba685af2d6dc32a6dfc15",
+ "rustfmt-1.95.0-aarch64-apple-darwin.tar.xz": "c54af79adfdc790d27fc56e24407e370c80be5a89ad537eef9fbd45d3c3e28e8",
+ "rustfmt-1.95.0-aarch64-pc-windows-msvc.tar.xz": "a873c048743e6da29e09a8b55c774ee113f8f6ae4fd57d988d304a6453801b34",
+ "rustfmt-1.95.0-aarch64-unknown-linux-gnu.tar.xz": "64cce868f0f3d29f1524e11e9bab01ac9d538a31665fea1cd6b78af46a1c0a41",
+ "rustfmt-1.95.0-x86_64-apple-darwin.tar.xz": "5f7228f40a160e80d260e74e068d6fec8627aa02f1f5ae29d2019b9347076401",
+ "rustfmt-1.95.0-x86_64-pc-windows-msvc.tar.xz": "8bcf91606e36b8a0164efafde50709cd7a3c02143a2edaa81dbf3dccd6ed8f4c",
+ "rustfmt-1.95.0-x86_64-unknown-linux-gnu.tar.xz": "f1b2a7301513ffdd95ebf22ebbdd932e4d17fc806f748d93924740d0297b1396"
},
"@@windows_support+//windows:extensions.bzl%msvc_runtime": {
"visual_studio_installer_manifest_v1": {
diff --git a/README.md b/README.md
index 43c66619a986..f028d7ba626c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
Codefx CLI is a fork of OpenAI Codex CLI with resilient retries and Codefx release updates.
-
+
If you want Codex in your code editor (VS Code, Cursor, Windsurf), install in your IDE.
@@ -13,17 +13,17 @@ If you want Codex in your code editor (VS Code, Cursor, Windsurf), {});
+ process.execve(binaryPath, [binaryPath, ...process.argv.slice(2)], env);
+}
+
+// Older Node versions and Windows need a child process. Spawn asynchronously
+// so Node can forward signals while the native binary is executing.
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
env,
@@ -280,8 +286,9 @@ const childResult = await new Promise((resolve) => {
});
if (childResult.type === "signal") {
- // Re-emit the same signal so that the parent terminates with the expected
- // semantics (this also sets the correct exit code of 128 + n).
+ // Preserve a failure exit code even if Node ignores the re-emitted signal.
+ process.exitCode = 128 + constants.signals[childResult.signal];
+ process.removeAllListeners(childResult.signal);
process.kill(process.pid, childResult.signal);
} else {
process.exit(childResult.exitCode);
diff --git a/codex-cli/scripts/README.md b/codex-cli/scripts/README.md
index 4877781c36b9..e60657a387c3 100644
--- a/codex-cli/scripts/README.md
+++ b/codex-cli/scripts/README.md
@@ -18,6 +18,12 @@ When `--package codex` is provided, the staging helper builds the lightweight
`@openai/codex` meta package plus all platform-native `@openai/codex` variants
that are later published under platform-specific dist-tags.
+The npm variants omit the standalone `codex-exec-server` executable and its
+`execServer` manifest entry to keep uploads within npm's package size limit.
+They retain `codex exec-server` through the CLI, the code-mode host, and the
+platform sandbox helpers. Canonical release archives retain the standalone
+executor; npm staging changes only the package copy.
+
Direct `build_npm_package.py` invocations are still useful for package-specific
debugging, but native packages expect `--vendor-src` to point at a prehydrated
`vendor/` tree. Release packaging should use `scripts/stage_npm_packages.py`
diff --git a/codex-cli/scripts/build_npm_package.py b/codex-cli/scripts/build_npm_package.py
index 12f4f9dc8993..7b7cd5d95547 100755
--- a/codex-cli/scripts/build_npm_package.py
+++ b/codex-cli/scripts/build_npm_package.py
@@ -391,6 +391,16 @@ def copy_native_binaries(
if dest_target_dir.exists():
shutil.rmtree(dest_target_dir)
shutil.copytree(target_dir, dest_target_dir)
+ # standalone exec-server is a binary that is equal to codex exec-server, do not publish it to npm
+ for name in ("codex-exec-server", "codex-exec-server.exe"):
+ (dest_target_dir / "bin" / name).unlink(missing_ok=True)
+ metadata_path = dest_target_dir / "codex-package.json"
+ if metadata_path.is_file():
+ metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
+ metadata.pop("execServer", None)
+ metadata_path.write_text(
+ json.dumps(metadata, indent=2) + "\n", encoding="utf-8"
+ )
else:
dest_target_dir.mkdir(parents=True, exist_ok=True)
diff --git a/codex-cli/tests/exit-status.test.mjs b/codex-cli/tests/exit-status.test.mjs
new file mode 100644
index 000000000000..6eb265dc71cd
--- /dev/null
+++ b/codex-cli/tests/exit-status.test.mjs
@@ -0,0 +1,121 @@
+// Run with CODEX_TEST_BINARY=/absolute/path/to/codex node --test codex-cli/tests/exit-status.test.mjs.
+import assert from "node:assert/strict";
+import { execFileSync, spawn, spawnSync } from "node:child_process";
+import { once } from "node:events";
+import {
+ copyFile,
+ mkdir,
+ mkdtemp,
+ rm,
+ symlink,
+ writeFile,
+} from "node:fs/promises";
+import { constants, tmpdir } from "node:os";
+import path from "node:path";
+import { createInterface } from "node:readline";
+import test from "node:test";
+
+test(
+ "the npm wrapper preserves numeric exits and child signals",
+ {
+ skip: process.platform === "win32",
+ },
+ async () => {
+ assert.ok(
+ process.env.CODEX_TEST_BINARY,
+ "Set CODEX_TEST_BINARY to a built Codex binary",
+ );
+ const directory = await mkdtemp(path.join(tmpdir(), "codex-npm-exit-"));
+ let child;
+ let killTimer;
+ try {
+ const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
+ const target = `${arch}-${process.platform === "darwin" ? "apple-darwin" : "unknown-linux-musl"}`;
+ const bin = path.join(directory, "vendor", target, "bin");
+ const home = path.join(directory, "home");
+ await mkdir(bin, { recursive: true });
+ await mkdir(home);
+ await mkdir(path.join(directory, "bin"));
+ await writeFile(
+ path.join(directory, "package.json"),
+ '{"type":"module"}',
+ );
+ const wrapper = path.join(directory, "bin", "codex.js");
+ await copyFile(new URL("../bin/codex.js", import.meta.url), wrapper);
+ await symlink(
+ path.resolve(process.env.CODEX_TEST_BINARY),
+ path.join(bin, "codex"),
+ );
+ const env = { ...process.env, CODEX_HOME: home, CODEX_SQLITE_HOME: home };
+
+ // A normal CLI argument error must retain its numeric exit status.
+ const invalid = spawnSync(
+ process.execPath,
+ [wrapper, "--invalid-codex-option"],
+ { env, timeout: 30_000, killSignal: "SIGKILL" },
+ );
+ assert.equal(invalid.error, undefined);
+ assert.deepEqual([invalid.status, invalid.signal], [2, null]);
+
+ for (const signal of ["SIGHUP", "SIGXFSZ"]) {
+ // Wait for the real migration prompt before terminating a process.
+ child = spawn(process.execPath, [wrapper, "migrate-nfs-db"], {
+ env,
+ detached: true,
+ });
+ const processGroup = child.pid;
+ killTimer = setTimeout(() => {
+ try {
+ process.kill(-processGroup, "SIGKILL");
+ } catch (error) {
+ if (error.code !== "ESRCH") throw error;
+ }
+ }, 30_000);
+ const exited = once(child, "exit");
+ const lines = createInterface({ input: child.stderr });
+ let prompted = false;
+ for await (const line of lines) {
+ if (line.endsWith("[y/N]:")) {
+ prompted = true;
+ break;
+ }
+ }
+ assert.ok(prompted, "migration must reach its confirmation prompt");
+ const processes = execFileSync("ps", ["-axo", "pid=,ppid="], {
+ encoding: "utf8",
+ });
+ const children = processes
+ .trim()
+ .split("\n")
+ .map((line) => line.trim().split(/\s+/).map(Number))
+ .filter(([, parent]) => parent === child.pid);
+ const usesExecve = typeof process.execve === "function";
+ assert.equal(
+ children.length,
+ usesExecve ? 0 : 1,
+ "execve must replace the wrapper; spawn must create one native child",
+ );
+ if (usesExecve || signal === "SIGHUP") {
+ // Signal the native process directly, or let the wrapper forward it.
+ child.kill(signal);
+ assert.deepEqual(await exited, [null, signal]);
+ } else {
+ // A native child can die from a signal that Node ignores by default.
+ process.kill(children[0][0], signal);
+ assert.deepEqual(await exited, [
+ 128 + constants.signals[signal],
+ null,
+ ]);
+ }
+ clearTimeout(killTimer);
+ }
+ } finally {
+ clearTimeout(killTimer);
+ if (child && child.exitCode === null && child.signalCode === null) {
+ process.kill(-child.pid, "SIGKILL");
+ await once(child, "exit");
+ }
+ await rm(directory, { recursive: true, force: true });
+ }
+ },
+);
diff --git a/codex-cli/tests/test_build_npm_package.py b/codex-cli/tests/test_build_npm_package.py
new file mode 100644
index 000000000000..1424abe15db0
--- /dev/null
+++ b/codex-cli/tests/test_build_npm_package.py
@@ -0,0 +1,112 @@
+"""Check the contents of npm archives staged from canonical release packages."""
+
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+import unittest
+
+REPO_ROOT = Path(__file__).resolve().parents[2]
+sys.path.insert(0, str(REPO_ROOT / "codex-cli" / "scripts"))
+
+from build_npm_package import CODEX_PLATFORM_PACKAGES
+
+
+class NpmPackageTest(unittest.TestCase):
+ def test_npm_archives_preserve_cli_runtime_without_standalone_executor(
+ self,
+ ) -> None:
+ # Use an installed executable as prebuilt input: packaging must preserve its
+ # bytes even when staging a package for another operating system.
+ executable = shutil.which("whoami") or sys.executable
+ for package, platform in CODEX_PLATFORM_PACKAGES.items():
+ with self.subTest(package=package), tempfile.TemporaryDirectory() as temp:
+ root = Path(temp)
+ target = platform["target_triple"]
+ source = root / "vendor" / target
+ suffix = ".exe" if platform["os"] == "win32" else ""
+ metadata = {
+ "layoutVersion": 1,
+ "variant": "codex",
+ "version": "1.2.3",
+ "target": target,
+ "entrypoint": f"bin/codex{suffix}",
+ "execServer": f"bin/codex-exec-server{suffix}",
+ "resourcesDir": "codex-resources",
+ "pathDir": "codex-path",
+ }
+ files = [
+ metadata["entrypoint"],
+ metadata["execServer"],
+ f"bin/codex-code-mode-host{suffix}",
+ f"codex-path/rg{suffix}",
+ ]
+ if platform["os"] == "linux":
+ files.append("codex-resources/bwrap")
+ elif platform["os"] == "win32":
+ files.extend(
+ [
+ "codex-resources/codex-command-runner.exe",
+ "codex-resources/codex-windows-sandbox-setup.exe",
+ ]
+ )
+ for name in files:
+ destination = source / name
+ destination.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(executable, destination)
+ (source / "codex-package.json").write_text(json.dumps(metadata))
+ original = {
+ path.relative_to(source).as_posix(): path.read_bytes()
+ for path in source.rglob("*")
+ if path.is_file()
+ }
+ archive_path = root / "package.tgz"
+ subprocess.run(
+ [
+ sys.executable,
+ str(
+ REPO_ROOT / "codex-cli" / "scripts" / "build_npm_package.py"
+ ),
+ "--package",
+ package,
+ "--version",
+ "1.2.3",
+ "--vendor-src",
+ str(source.parent),
+ "--staging-dir",
+ str(root / "staged"),
+ "--pack-output",
+ str(archive_path),
+ ],
+ check=True,
+ capture_output=True,
+ )
+ with tarfile.open(archive_path, "r:gz") as archive:
+ prefix = f"package/vendor/{target}/"
+ actual = {
+ member.name.removeprefix(prefix): archive.extractfile(
+ member
+ ).read()
+ for member in archive.getmembers()
+ if member.isfile() and member.name.startswith(prefix)
+ }
+ expected = original.copy()
+ metadata = json.loads(expected.pop("codex-package.json"))
+ expected.pop(metadata.pop("execServer"))
+ self.assertEqual(json.loads(actual.pop("codex-package.json")), metadata)
+ self.assertEqual(actual, expected)
+ self.assertEqual(
+ {
+ path.relative_to(source).as_posix(): path.read_bytes()
+ for path in source.rglob("*")
+ if path.is_file()
+ },
+ original,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/codex-rs/.cargo/audit.toml b/codex-rs/.cargo/audit.toml
index a1e347895cab..d7ebe861a0bc 100644
--- a/codex-rs/.cargo/audit.toml
+++ b/codex-rs/.cargo/audit.toml
@@ -17,4 +17,12 @@ ignore = [
"RUSTSEC-2026-0372", # zvariant_derive 4.2.0 via zbus 4.4.0 in Linux TUI and keyring/secret-service; remove when those paths migrate off the retired standalone crates
# Reviewed 2026-10-20.
"RUSTSEC-2026-0374", # basic-toml 0.1.10 via i18n-config/i18n-embed-fl under age 0.12.1 in codex-secrets; no patched release; remove when age's localization dependencies migrate off basic-toml
+ "RUSTSEC-2026-0387", # crypto-bigint 0.5.5 via elliptic-curve 0.13.8 is used by age, AWS login, user verification, and WebRTC; timing leakage can affect private-key or session-secret operations; remove when these dependency paths use crypto-bigint >=0.7.6
+ # Reviewed 2026-10-26.
+ "RUSTSEC-2026-0391", # Codex, tonic and zip use zstd 0.13.3 without reusing borrowed contexts across streams. Remove when all paths use zstd >=0.14.3.
+ "RUSTSEC-2026-0398", # zstd 0.13.3 pulls in zstd-safe 7.2.4 with the affected seekable feature disabled. Remove when all paths use zstd-safe >=8.2.0.
+ "RUSTSEC-2026-0403", # zstd 0.13.3 pulls in zstd-safe 7.2.4. Codex, tonic and zip use read decoders or initialized output buffers and discard output on error. Remove when all paths use zstd-safe >=8.2.0.
+ "RUSTSEC-2026-0410", # zstd-safe 7.2.4 pulls in zstd-sys 2.0.16. Its allocator bug only affects wasm32, and Codex ships native binaries. Remove when the lockfile uses zstd-sys >=2.1.2.
+ # Reviewed 2026-11-05: unmaintained only; no known exploitable issue or fixed upstream release.
+ "RUSTSEC-2026-0441", # pin-utils 0.1.0 via aws-smithy-http 0.62.6 -> aws-config 1.8.12 -> codex-aws-auth. Remove when AWS SDK dependencies drop pin-utils.
]
diff --git a/codex-rs/BUILD.bazel b/codex-rs/BUILD.bazel
index e1698941536a..fc151fdc2ed2 100644
--- a/codex-rs/BUILD.bazel
+++ b/codex-rs/BUILD.bazel
@@ -53,6 +53,7 @@ runtime_archive(
"//codex-rs/app-server:release_files",
"//codex-rs/cli:release_files",
"//codex-rs/code-mode-host:release_files",
+ "//codex-rs/exec-server-cli:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
"@platforms//os:windows": [
@@ -70,6 +71,7 @@ runtime_archive(
srcs = [
"//codex-rs/cli:release_files",
"//codex-rs/code-mode-host:release_files",
+ "//codex-rs/exec-server-cli:release_files",
"//codex-rs/responses-api-proxy:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
@@ -84,6 +86,7 @@ runtime_archive(
srcs = [
"//codex-rs/app-server:release_files",
"//codex-rs/code-mode-host:release_files",
+ "//codex-rs/exec-server-cli:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
"//conditions:default": [],
diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock
index 96316db08871..35ced85bcc13 100644
--- a/codex-rs/Cargo.lock
+++ b/codex-rs/Cargo.lock
@@ -287,7 +287,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "app_test_support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -1888,7 +1888,7 @@ dependencies = [
[[package]]
name = "codex-agent-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-core",
@@ -1900,7 +1900,7 @@ dependencies = [
[[package]]
name = "codex-agent-graph-store"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-state",
@@ -1915,7 +1915,7 @@ dependencies = [
[[package]]
name = "codex-agent-identity"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -1935,7 +1935,7 @@ dependencies = [
[[package]]
name = "codex-agent-message-board-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -1958,7 +1958,7 @@ dependencies = [
[[package]]
name = "codex-agent-message-board-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-extension-api",
"codex-message-board",
@@ -1972,7 +1972,7 @@ dependencies = [
[[package]]
name = "codex-agent-roles"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-config",
"codex-file-system",
@@ -1985,7 +1985,7 @@ dependencies = [
[[package]]
name = "codex-analytics"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-app-server-protocol",
"codex-git-utils",
@@ -2008,7 +2008,7 @@ dependencies = [
[[package]]
name = "codex-ansi-escape"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"ansi-to-tui",
"pretty_assertions",
@@ -2019,7 +2019,7 @@ dependencies = [
[[package]]
name = "codex-api"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"assert_matches",
@@ -2057,7 +2057,7 @@ dependencies = [
[[package]]
name = "codex-app-server"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"app_test_support",
@@ -2065,7 +2065,6 @@ dependencies = [
"base64 0.22.1",
"chrono",
"clap",
- "codex-agent-extension",
"codex-analytics",
"codex-app-server-daemon",
"codex-app-server-protocol",
@@ -2175,10 +2174,11 @@ dependencies = [
[[package]]
name = "codex-app-server-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-app-server",
"codex-app-server-protocol",
+ "codex-app-server-transport",
"codex-arg0",
"codex-config",
"codex-core",
@@ -2202,7 +2202,7 @@ dependencies = [
[[package]]
name = "codex-app-server-daemon"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"blake3",
@@ -2232,7 +2232,7 @@ dependencies = [
[[package]]
name = "codex-app-server-protocol"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-app-server-protocol-noop-macros",
@@ -2266,11 +2266,11 @@ dependencies = [
[[package]]
name = "codex-app-server-protocol-noop-macros"
-version = "0.0.0"
+version = "0.166.0"
[[package]]
name = "codex-app-server-test-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -2292,7 +2292,7 @@ dependencies = [
[[package]]
name = "codex-app-server-transport"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"axum",
"base64 0.22.1",
@@ -2335,7 +2335,7 @@ dependencies = [
[[package]]
name = "codex-apply-patch"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"assert_cmd",
@@ -2355,7 +2355,7 @@ dependencies = [
[[package]]
name = "codex-arg0"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-apply-patch",
@@ -2378,7 +2378,7 @@ dependencies = [
[[package]]
name = "codex-async-utils"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"rand 0.9.3",
@@ -2388,7 +2388,7 @@ dependencies = [
[[package]]
name = "codex-attachment-store"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"serde",
@@ -2398,7 +2398,7 @@ dependencies = [
[[package]]
name = "codex-aws-auth"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"aws-config",
"aws-credential-types",
@@ -2424,7 +2424,7 @@ dependencies = [
[[package]]
name = "codex-backend-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-api",
@@ -2444,7 +2444,7 @@ dependencies = [
[[package]]
name = "codex-backend-openapi-models"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"serde",
"serde_json",
@@ -2453,7 +2453,7 @@ dependencies = [
[[package]]
name = "codex-build-info"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-install-context",
"pretty_assertions",
@@ -2466,7 +2466,7 @@ dependencies = [
[[package]]
name = "codex-bwrap"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"cc",
"libc",
@@ -2475,7 +2475,7 @@ dependencies = [
[[package]]
name = "codex-chatgpt"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -2497,7 +2497,7 @@ dependencies = [
[[package]]
name = "codex-cli"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"app_test_support",
@@ -2513,7 +2513,6 @@ dependencies = [
"codex-app-server-protocol",
"codex-app-server-test-client",
"codex-arg0",
- "codex-aws-auth",
"codex-build-info",
"codex-chatgpt",
"codex-cloud-config",
@@ -2523,6 +2522,7 @@ dependencies = [
"codex-core-plugins",
"codex-exec",
"codex-exec-server",
+ "codex-exec-server-cli",
"codex-execpolicy",
"codex-extension-api",
"codex-features",
@@ -2570,7 +2570,6 @@ dependencies = [
"insta",
"jsonwebtoken",
"libc",
- "libsqlite3-sys",
"os_info",
"owo-colors",
"predicates",
@@ -2602,7 +2601,7 @@ dependencies = [
[[package]]
name = "codex-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-http-client",
"eventsource-stream",
@@ -2616,7 +2615,7 @@ dependencies = [
[[package]]
name = "codex-cloud-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"async-stream",
@@ -2640,7 +2639,7 @@ dependencies = [
[[package]]
name = "codex-cloud-config"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -2668,7 +2667,7 @@ dependencies = [
[[package]]
name = "codex-cloud-tasks"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -2701,7 +2700,7 @@ dependencies = [
[[package]]
name = "codex-cloud-tasks-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -2716,7 +2715,7 @@ dependencies = [
[[package]]
name = "codex-cloud-tasks-mock-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"chrono",
"codex-cloud-tasks-client",
@@ -2725,7 +2724,7 @@ dependencies = [
[[package]]
name = "codex-code-mode"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-code-mode-protocol",
"codex-http-client",
@@ -2749,7 +2748,7 @@ dependencies = [
[[package]]
name = "codex-code-mode-host"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"axum",
@@ -2760,8 +2759,10 @@ dependencies = [
"codex-http-client",
"codex-otel",
"codex-otel-trace-websocket",
+ "codex-plugin-host",
"codex-protocol",
"codex-utils-cargo-bin",
+ "codex-utils-process",
"futures",
"pretty_assertions",
"prost",
@@ -2778,7 +2779,7 @@ dependencies = [
[[package]]
name = "codex-code-mode-protocol"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"glob",
@@ -2796,10 +2797,11 @@ dependencies = [
[[package]]
name = "codex-code-mode-runtime"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-code-mode-protocol",
+ "codex-plugin-host",
"codex-protocol",
"deno_core_icudata",
"futures",
@@ -2814,11 +2816,11 @@ dependencies = [
[[package]]
name = "codex-collaboration-mode-templates"
-version = "0.0.0"
+version = "0.166.0"
[[package]]
name = "codex-config"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -2853,6 +2855,7 @@ dependencies = [
"serde_path_to_error",
"sha2 0.10.9",
"tempfile",
+ "test-case",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
@@ -2871,7 +2874,7 @@ dependencies = [
[[package]]
name = "codex-config-schema"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -2880,7 +2883,7 @@ dependencies = [
[[package]]
name = "codex-connectors"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -2902,7 +2905,7 @@ dependencies = [
[[package]]
name = "codex-connectors-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-connectors",
"codex-core-plugins",
@@ -2916,7 +2919,7 @@ dependencies = [
[[package]]
name = "codex-context-fragments"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-protocol-test-support",
@@ -2927,7 +2930,7 @@ dependencies = [
[[package]]
name = "codex-core"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -2954,6 +2957,7 @@ dependencies = [
"codex-context-fragments",
"codex-core-plugins",
"codex-diagnostics",
+ "codex-direct-tool-timing",
"codex-exec-server",
"codex-exec-server-test-support",
"codex-execpolicy",
@@ -2989,6 +2993,7 @@ dependencies = [
"codex-rmcp-client",
"codex-rollout",
"codex-rollout-trace",
+ "codex-runtime-agent-instance",
"codex-sandboxing",
"codex-shell-command",
"codex-skills",
@@ -3002,6 +3007,7 @@ dependencies = [
"codex-utils-audio",
"codex-utils-cache",
"codex-utils-cargo-bin",
+ "codex-utils-critical-path",
"codex-utils-git-discovery",
"codex-utils-head-tail-buffer",
"codex-utils-home-dir",
@@ -3074,7 +3080,7 @@ dependencies = [
[[package]]
name = "codex-core-api"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-analytics",
"codex-app-server-protocol",
@@ -3097,7 +3103,7 @@ dependencies = [
[[package]]
name = "codex-core-plugin-common"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-absolute-path",
"semver",
@@ -3108,7 +3114,7 @@ dependencies = [
[[package]]
name = "codex-core-plugins"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -3165,7 +3171,7 @@ dependencies = [
[[package]]
name = "codex-diagnostics"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"libc",
"pretty_assertions",
@@ -3173,15 +3179,18 @@ dependencies = [
[[package]]
name = "codex-direct-tool-timing"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-critical-path",
"pretty_assertions",
+ "test-case",
+ "tokio",
+ "tokio-util",
]
[[package]]
name = "codex-exec"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"assert_cmd",
@@ -3234,7 +3243,7 @@ dependencies = [
[[package]]
name = "codex-exec-server"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -3277,6 +3286,7 @@ dependencies = [
"futures",
"http 1.4.0",
"libc",
+ "notify",
"opentelemetry",
"opentelemetry_sdk",
"pretty_assertions",
@@ -3304,9 +3314,49 @@ dependencies = [
"wiremock",
]
+[[package]]
+name = "codex-exec-server-cli"
+version = "0.166.0"
+dependencies = [
+ "anyhow",
+ "assert_matches",
+ "clap",
+ "codex-api",
+ "codex-app-server",
+ "codex-arg0",
+ "codex-aws-auth",
+ "codex-build-info",
+ "codex-cloud-config",
+ "codex-config",
+ "codex-core",
+ "codex-exec-server",
+ "codex-http-client",
+ "codex-login",
+ "codex-model-provider",
+ "codex-protocol",
+ "codex-sandboxing",
+ "codex-utils-absolute-path",
+ "codex-utils-cargo-bin",
+ "codex-utils-cli",
+ "codex-utils-process",
+ "codex-websocket-auth",
+ "http 1.4.0",
+ "insta",
+ "pretty_assertions",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "tikv-jemallocator",
+ "tokio",
+ "toml 0.9.11+spec-1.1.0",
+ "tracing",
+ "tracing-subscriber",
+ "url",
+]
+
[[package]]
name = "codex-exec-server-protocol"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-file-system",
@@ -3324,7 +3374,7 @@ dependencies = [
[[package]]
name = "codex-exec-server-test-support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-exec-server",
@@ -3339,7 +3389,7 @@ dependencies = [
[[package]]
name = "codex-execpolicy"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -3357,7 +3407,7 @@ dependencies = [
[[package]]
name = "codex-experimental-api-macros"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"proc-macro2",
"quote",
@@ -3366,7 +3416,7 @@ dependencies = [
[[package]]
name = "codex-extension-api"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-api",
"codex-config",
@@ -3389,7 +3439,7 @@ dependencies = [
[[package]]
name = "codex-extension-items"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-absolute-path",
"pretty_assertions",
@@ -3401,7 +3451,7 @@ dependencies = [
[[package]]
name = "codex-external-agent-migration"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"chrono",
"codex-analytics",
@@ -3432,7 +3482,7 @@ dependencies = [
[[package]]
name = "codex-features"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-network-proxy",
"codex-protocol",
@@ -3446,7 +3496,7 @@ dependencies = [
[[package]]
name = "codex-feedback"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"bytes",
@@ -3474,7 +3524,7 @@ dependencies = [
[[package]]
name = "codex-file-search"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -3490,7 +3540,7 @@ dependencies = [
[[package]]
name = "codex-file-system"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"bytes",
"codex-protocol",
@@ -3504,7 +3554,7 @@ dependencies = [
[[package]]
name = "codex-file-watcher"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"notify",
"pretty_assertions",
@@ -3515,7 +3565,7 @@ dependencies = [
[[package]]
name = "codex-git-attribution"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-backend-client",
"codex-extension-api",
@@ -3528,7 +3578,7 @@ dependencies = [
[[package]]
name = "codex-git-utils"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -3556,7 +3606,7 @@ dependencies = [
[[package]]
name = "codex-goal-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -3581,7 +3631,7 @@ dependencies = [
[[package]]
name = "codex-guardian-context"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-context-fragments",
"codex-history",
@@ -3595,21 +3645,32 @@ dependencies = [
[[package]]
name = "codex-guardian-reviewer"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-analytics",
+ "codex-context-fragments",
"codex-extension-api",
+ "codex-features",
"codex-feedback",
"codex-guardian-context",
+ "codex-history",
"codex-mcp",
+ "codex-models-manager",
"codex-otel",
"codex-prompts",
"codex-protocol",
+ "codex-protocol-test-support",
+ "codex-rmcp-client",
+ "codex-shell-command",
+ "codex-utils-output-truncation",
+ "codex-utils-path-uri",
+ "codex-utils-string",
"opentelemetry",
"opentelemetry_sdk",
"pretty_assertions",
"rand 0.9.3",
+ "rmcp",
"serde",
"serde_json",
"test-case",
@@ -3620,7 +3681,7 @@ dependencies = [
[[package]]
name = "codex-guardian-v2"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-analytics",
@@ -3659,7 +3720,7 @@ dependencies = [
[[package]]
name = "codex-guardian-v3"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-message-board",
"codex-protocol",
@@ -3668,7 +3729,7 @@ dependencies = [
[[package]]
name = "codex-history"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-protocol",
@@ -3680,7 +3741,7 @@ dependencies = [
[[package]]
name = "codex-history-notes-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-analytics",
"codex-client",
@@ -3706,7 +3767,7 @@ dependencies = [
[[package]]
name = "codex-home"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-extension-api",
"codex-utils-absolute-path",
@@ -3718,7 +3779,7 @@ dependencies = [
[[package]]
name = "codex-hooks"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"async-channel",
@@ -3747,7 +3808,7 @@ dependencies = [
[[package]]
name = "codex-http-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"bytes",
"codex-utils-cargo-bin",
@@ -3787,7 +3848,7 @@ dependencies = [
[[package]]
name = "codex-image-generation-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-api",
@@ -3819,7 +3880,7 @@ dependencies = [
[[package]]
name = "codex-install-context"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-absolute-path",
"codex-utils-home-dir",
@@ -3832,7 +3893,7 @@ dependencies = [
[[package]]
name = "codex-keyring-store"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"keyring",
"pretty_assertions",
@@ -3843,7 +3904,7 @@ dependencies = [
[[package]]
name = "codex-linux-sandbox"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"clap",
"codex-core",
@@ -3872,7 +3933,7 @@ dependencies = [
[[package]]
name = "codex-lmstudio"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-core",
"codex-http-client",
@@ -3886,7 +3947,7 @@ dependencies = [
[[package]]
name = "codex-login"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -3932,7 +3993,7 @@ dependencies = [
[[package]]
name = "codex-mcp"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -3976,7 +4037,7 @@ dependencies = [
[[package]]
name = "codex-mcp-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"axum",
@@ -4009,7 +4070,7 @@ dependencies = [
[[package]]
name = "codex-memories-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-core",
"codex-extension-api",
@@ -4031,7 +4092,7 @@ dependencies = [
[[package]]
name = "codex-memories-read"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-shell-command",
@@ -4041,7 +4102,7 @@ dependencies = [
[[package]]
name = "codex-memories-write"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -4078,7 +4139,7 @@ dependencies = [
[[package]]
name = "codex-mermaid"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"insta",
"pretty_assertions",
@@ -4087,7 +4148,7 @@ dependencies = [
[[package]]
name = "codex-message-board"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"caseless",
@@ -4110,7 +4171,7 @@ dependencies = [
[[package]]
name = "codex-message-history"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-config",
"memchr",
@@ -4124,13 +4185,14 @@ dependencies = [
[[package]]
name = "codex-model-provider"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"chrono",
"codex-agent-identity",
"codex-api",
"codex-aws-auth",
+ "codex-features",
"codex-feedback",
"codex-http-client",
"codex-keyring-store",
@@ -4140,12 +4202,16 @@ dependencies = [
"codex-otel",
"codex-protocol",
"codex-response-debug-context",
+ "codex-runtime-agent-instance",
"codex-secrets",
"codex-utils-cargo-bin",
"codex-utils-process",
"codex-utils-redacted-string",
+ "codex-utils-rustls-provider",
"http 1.4.0",
"pretty_assertions",
+ "rcgen",
+ "rustls",
"serde",
"serde_json",
"sha2 0.10.9",
@@ -4158,7 +4224,7 @@ dependencies = [
[[package]]
name = "codex-model-provider-info"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-client",
"codex-http-client",
@@ -4176,7 +4242,7 @@ dependencies = [
[[package]]
name = "codex-models-manager"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"chrono",
"codex-http-client",
@@ -4195,7 +4261,7 @@ dependencies = [
[[package]]
name = "codex-multi-env-copy-file"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-config",
@@ -4221,7 +4287,7 @@ dependencies = [
[[package]]
name = "codex-mxc-sandbox"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-network-proxy",
@@ -4242,7 +4308,7 @@ dependencies = [
[[package]]
name = "codex-network-proxy"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"aws-credential-types",
@@ -4293,7 +4359,7 @@ dependencies = [
[[package]]
name = "codex-ollama"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"assert_matches",
"async-stream",
@@ -4313,7 +4379,7 @@ dependencies = [
[[package]]
name = "codex-otel"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"bytes",
"chrono",
@@ -4335,11 +4401,13 @@ dependencies = [
"opentelemetry_sdk",
"os_info",
"pretty_assertions",
+ "rcgen",
"reqwest 0.12.28",
"seccompiler",
"serde",
"serde_json",
"strum_macros 0.28.0",
+ "tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-tungstenite",
@@ -4350,7 +4418,7 @@ dependencies = [
[[package]]
name = "codex-otel-trace-websocket"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"axum",
@@ -4361,7 +4429,7 @@ dependencies = [
[[package]]
name = "codex-plugin"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-config",
"codex-core-plugin-common",
@@ -4374,9 +4442,26 @@ dependencies = [
"thiserror 2.0.18",
]
+[[package]]
+name = "codex-plugin-host"
+version = "0.166.0"
+dependencies = [
+ "codex-utils-cargo-bin",
+ "codex-utils-process",
+ "codex-utils-pty",
+ "futures",
+ "libc",
+ "pretty_assertions",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "tokio",
+ "tokio-util",
+]
+
[[package]]
name = "codex-process-hardening"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"libc",
"pretty_assertions",
@@ -4384,7 +4469,7 @@ dependencies = [
[[package]]
name = "codex-prompts"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-collaboration-mode-templates",
@@ -4402,7 +4487,7 @@ dependencies = [
[[package]]
name = "codex-protocol"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chardetng",
@@ -4448,14 +4533,14 @@ dependencies = [
[[package]]
name = "codex-protocol-test-support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
]
[[package]]
name = "codex-queue-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-core",
@@ -4476,7 +4561,7 @@ dependencies = [
[[package]]
name = "codex-realtime-webrtc"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-build-info",
@@ -4495,7 +4580,7 @@ dependencies = [
[[package]]
name = "codex-response-debug-context"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-api",
@@ -4506,7 +4591,7 @@ dependencies = [
[[package]]
name = "codex-responses-api-proxy"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"bytes",
@@ -4530,7 +4615,7 @@ dependencies = [
[[package]]
name = "codex-rmcp-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"axum",
@@ -4581,7 +4666,7 @@ dependencies = [
[[package]]
name = "codex-rollout"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -4614,7 +4699,7 @@ dependencies = [
[[package]]
name = "codex-rollout-trace"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-code-mode",
@@ -4631,7 +4716,7 @@ dependencies = [
[[package]]
name = "codex-runtime-agent-instance"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-agent-identity",
@@ -4657,7 +4742,7 @@ dependencies = [
[[package]]
name = "codex-sandboxing"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-mxc-sandbox",
@@ -4685,7 +4770,7 @@ dependencies = [
[[package]]
name = "codex-secrets"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"age",
"anyhow",
@@ -4706,7 +4791,7 @@ dependencies = [
[[package]]
name = "codex-shell-command"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -4729,7 +4814,7 @@ dependencies = [
[[package]]
name = "codex-skills"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-shell-command",
@@ -4746,7 +4831,7 @@ dependencies = [
[[package]]
name = "codex-skills-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"assert_matches",
"blake3",
@@ -4786,7 +4871,7 @@ dependencies = [
[[package]]
name = "codex-state"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"chrono",
@@ -4806,6 +4891,7 @@ dependencies = [
"sqlx-macros",
"sqlx-sqlite",
"strum 0.27.2",
+ "tempfile",
"tokio",
"tracing",
"tracing-subscriber",
@@ -4814,7 +4900,7 @@ dependencies = [
[[package]]
name = "codex-stdio-to-uds"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-uds",
@@ -4826,7 +4912,7 @@ dependencies = [
[[package]]
name = "codex-tcp-tunnel"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"bytes",
@@ -4847,7 +4933,7 @@ dependencies = [
[[package]]
name = "codex-terminal-detection"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"tracing",
@@ -4855,7 +4941,7 @@ dependencies = [
[[package]]
name = "codex-test-binary-support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-arg0",
"tempfile",
@@ -4863,7 +4949,7 @@ dependencies = [
[[package]]
name = "codex-thread-manager-sample"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"clap",
@@ -4874,7 +4960,7 @@ dependencies = [
[[package]]
name = "codex-thread-store"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"chrono",
"codex-app-server-protocol",
@@ -4905,7 +4991,7 @@ dependencies = [
[[package]]
name = "codex-tools"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"bitflags 2.13.1",
"codex-attachment-store",
@@ -4931,7 +5017,7 @@ dependencies = [
[[package]]
name = "codex-tui"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"app_test_support",
@@ -5059,7 +5145,7 @@ dependencies = [
[[package]]
name = "codex-uds"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"async-io",
"libc",
@@ -5073,7 +5159,7 @@ dependencies = [
[[package]]
name = "codex-user-verification"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"block2",
@@ -5094,7 +5180,7 @@ dependencies = [
[[package]]
name = "codex-utils-absolute-path"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"dirs",
"dunce",
@@ -5108,14 +5194,14 @@ dependencies = [
[[package]]
name = "codex-utils-approval-presets"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
]
[[package]]
name = "codex-utils-audio"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-protocol",
@@ -5129,7 +5215,7 @@ dependencies = [
[[package]]
name = "codex-utils-cache"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"lru",
"sha1 0.10.6",
@@ -5138,7 +5224,7 @@ dependencies = [
[[package]]
name = "codex-utils-cargo-bin"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"assert_cmd",
"runfiles",
@@ -5147,7 +5233,7 @@ dependencies = [
[[package]]
name = "codex-utils-cli"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"clap",
"codex-protocol",
@@ -5159,22 +5245,22 @@ dependencies = [
[[package]]
name = "codex-utils-critical-path"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "codex-utils-elapsed"
-version = "0.0.0"
+version = "0.166.0"
[[package]]
name = "codex-utils-fuzzy-match"
-version = "0.0.0"
+version = "0.166.0"
[[package]]
name = "codex-utils-git-discovery"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-git-utils",
"codex-utils-absolute-path",
@@ -5187,14 +5273,14 @@ dependencies = [
[[package]]
name = "codex-utils-head-tail-buffer"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "codex-utils-home-dir"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-absolute-path",
"dirs",
@@ -5204,7 +5290,7 @@ dependencies = [
[[package]]
name = "codex-utils-image"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-utils-cache",
@@ -5218,7 +5304,7 @@ dependencies = [
[[package]]
name = "codex-utils-json-to-toml"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"serde_json",
@@ -5227,7 +5313,7 @@ dependencies = [
[[package]]
name = "codex-utils-oss"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-core",
"codex-lmstudio",
@@ -5237,7 +5323,7 @@ dependencies = [
[[package]]
name = "codex-utils-output-truncation"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-utils-string",
@@ -5247,7 +5333,7 @@ dependencies = [
[[package]]
name = "codex-utils-path"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-utils-absolute-path",
"dunce",
@@ -5258,7 +5344,7 @@ dependencies = [
[[package]]
name = "codex-utils-path-uri"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"base64 0.22.1",
"codex-utils-absolute-path",
@@ -5274,7 +5360,7 @@ dependencies = [
[[package]]
name = "codex-utils-plugins"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-exec-server",
"codex-exec-server-protocol",
@@ -5288,7 +5374,7 @@ dependencies = [
[[package]]
name = "codex-utils-process"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"tokio",
@@ -5296,7 +5382,7 @@ dependencies = [
[[package]]
name = "codex-utils-pty"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-utils-cargo-bin",
@@ -5315,7 +5401,7 @@ dependencies = [
[[package]]
name = "codex-utils-readiness"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"assert_matches",
"thiserror 2.0.18",
@@ -5325,7 +5411,7 @@ dependencies = [
[[package]]
name = "codex-utils-redacted-string"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"schemars 0.8.22",
"serde",
@@ -5333,14 +5419,14 @@ dependencies = [
[[package]]
name = "codex-utils-rustls-provider"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"rustls",
]
[[package]]
name = "codex-utils-sandbox-summary"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-protocol",
"codex-utils-absolute-path",
@@ -5350,7 +5436,7 @@ dependencies = [
[[package]]
name = "codex-utils-sleep-inhibitor"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"core-foundation 0.9.4",
"libc",
@@ -5360,14 +5446,14 @@ dependencies = [
[[package]]
name = "codex-utils-stream-parser"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "codex-utils-string"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"regex-lite",
@@ -5377,14 +5463,14 @@ dependencies = [
[[package]]
name = "codex-utils-template"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "codex-v8-poc"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"pretty_assertions",
"v8",
@@ -5392,7 +5478,7 @@ dependencies = [
[[package]]
name = "codex-voice-host"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-install-context",
@@ -5419,7 +5505,7 @@ dependencies = [
[[package]]
name = "codex-web-search-extension"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-api",
"codex-core",
@@ -5441,7 +5527,7 @@ dependencies = [
[[package]]
name = "codex-websocket-auth"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5460,7 +5546,7 @@ dependencies = [
[[package]]
name = "codex-websocket-client"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-http-client",
"codex-utils-rustls-provider",
@@ -5476,7 +5562,7 @@ dependencies = [
[[package]]
name = "codex-windows-sandbox"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5506,7 +5592,7 @@ dependencies = [
[[package]]
name = "codex-windows-sandbox-service"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-cloud-config",
@@ -5524,14 +5610,14 @@ dependencies = [
[[package]]
name = "codex-windows-sandbox-test-support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"dirs",
]
[[package]]
name = "codex-workload-identity"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"codex-http-client",
"pretty_assertions",
@@ -5546,7 +5632,7 @@ dependencies = [
[[package]]
name = "codex-worktree"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"codex-git-utils",
@@ -5799,7 +5885,7 @@ dependencies = [
[[package]]
name = "core_test_support"
-version = "0.0.0"
+version = "0.166.0"
dependencies = [
"anyhow",
"assert_cmd",
@@ -9987,9 +10073,9 @@ dependencies = [
[[package]]
name = "libz-sys"
-version = "1.1.29"
+version = "1.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
+checksum = "ff20308ecabd635a2dc1dd5834a86c462cca20efba1d5d3ddab1f8aea3455cb2"
dependencies = [
"cc",
"libc",
@@ -15925,6 +16011,7 @@ dependencies = [
"tower-layer",
"tower-service",
"tracing",
+ "zstd",
]
[[package]]
@@ -16523,8 +16610,7 @@ dependencies = [
[[package]]
name = "v8"
version = "152.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a10fe1a92da5c32c7c7f838ce36c0ccfcfd5edf0865b58bdde820aa64cea9888"
+source = "git+https://github.com/seanh-oai/rusty_v8.git?rev=350c5a859a9b3c84ab8701b3ad51e3b7699b30c0#350c5a859a9b3c84ab8701b3ad51e3b7699b30c0"
dependencies = [
"bindgen",
"bitflags 2.13.1",
diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml
index d7bd76b11c5a..85cd80b7c453 100644
--- a/codex-rs/Cargo.toml
+++ b/codex-rs/Cargo.toml
@@ -30,6 +30,7 @@ members = [
"code-mode-host",
"code-mode-protocol",
"code-mode-runtime",
+ "plugin-host",
"codex-home",
"cloud-config",
"cloud-tasks",
@@ -58,6 +59,7 @@ members = [
"file-system",
"exec-server-protocol",
"exec-server",
+ "exec-server-cli",
"exec-server/tests/support",
"execpolicy",
"ext/agent",
@@ -167,7 +169,7 @@ members = [
resolver = "2"
[workspace.package]
-version = "0.0.0"
+version = "0.166.0"
# Track the edition for all workspace crates in one place. Individual
# crates can still override this value, but keeping it here means new
# crates created with `cargo new -w ...` automatically inherit the 2024
@@ -208,13 +210,13 @@ codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
codex-code-mode = { path = "code-mode" }
codex-code-mode-protocol = { path = "code-mode-protocol" }
codex-code-mode-runtime = { path = "code-mode-runtime" }
+codex-plugin-host = { path = "plugin-host" }
codex-home = { path = "codex-home" }
codex-http-client = { path = "http-client" }
codex-websocket-auth = { path = "websocket-auth" }
codex-websocket-client = { path = "websocket-client" }
codex-config = { path = "config" }
codex-connectors = { path = "connectors" }
-codex-agent-extension = { path = "ext/agent" }
codex-agent-message-board-client = { path = "agent-message-board-client" }
codex-agent-message-board-extension = { path = "ext/agent-message-board" }
codex-message-board = { path = "message-board" }
@@ -225,10 +227,12 @@ codex-core-api = { path = "core-api" }
codex-core-plugin-common = { path = "core-plugin-common" }
codex-core-plugins = { path = "core-plugins" }
codex-diagnostics = { path = "diagnostics" }
+codex-direct-tool-timing = { path = "direct-tool-timing" }
codex-exec = { path = "exec" }
codex-file-system = { path = "file-system" }
codex-exec-server-protocol = { path = "exec-server-protocol" }
codex-exec-server = { path = "exec-server" }
+codex-exec-server-cli = { path = "exec-server-cli" }
codex-exec-server-test-support = { path = "exec-server/tests/support" }
codex-execpolicy = { path = "execpolicy" }
codex-extension-api = { path = "ext/extension-api" }
@@ -282,6 +286,7 @@ codex-response-debug-context = { path = "response-debug-context" }
codex-rmcp-client = { path = "rmcp-client" }
codex-rollout = { path = "rollout" }
codex-rollout-trace = { path = "rollout-trace" }
+codex-runtime-agent-instance = { path = "runtime-agent-instance" }
codex-sandboxing = { path = "sandboxing" }
codex-secrets = { path = "secrets" }
codex-shell-command = { path = "shell-command" }
@@ -668,6 +673,7 @@ opt-level = 0
crossterm = { git = "https://github.com/openai-oss-forks/crossterm", rev = "ed1cdab335221515706178d68495bba2aed1924f" }
tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186" }
tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "4fffad30fe373adbdcffab9545e9e9bf4f2fc19f" }
+v8 = { git = "https://github.com/seanh-oai/rusty_v8.git", rev = "350c5a859a9b3c84ab8701b3ad51e3b7699b30c0" }
# Uncomment to debug local changes.
# rmcp = { path = "../../rust-sdk/crates/rmcp" }
diff --git a/codex-rs/agent-identity/src/lib.rs b/codex-rs/agent-identity/src/lib.rs
index 30f2b8a2eba5..14a9e3519011 100644
--- a/codex-rs/agent-identity/src/lib.rs
+++ b/codex-rs/agent-identity/src/lib.rs
@@ -564,6 +564,7 @@ fn signing_key_from_private_key_pkcs8_base64(private_key_pkcs8_base64: &str) ->
#[cfg(test)]
mod tests {
+ use base64::Engine as _;
use ed25519_dalek::Signature;
use ed25519_dalek::Verifier as _;
use jsonwebtoken::EncodingKey;
diff --git a/codex-rs/analytics/src/client.rs b/codex-rs/analytics/src/client.rs
index aef8abef1324..0c54d0167a94 100644
--- a/codex-rs/analytics/src/client.rs
+++ b/codex-rs/analytics/src/client.rs
@@ -65,6 +65,7 @@ use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::item_event_to_server_notification;
use codex_login::AuthManager;
use codex_login::CodexAuth;
+use codex_model_provider::ChatgptBaseUrl;
use codex_plugin::PluginId;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::ThreadId;
@@ -116,7 +117,7 @@ pub struct AnalyticsEventsClient {
#[derive(Clone, Debug, Eq, PartialEq)]
enum AnalyticsEventsDestination {
Http {
- url: String,
+ base_url: ChatgptBaseUrl,
},
#[cfg(debug_assertions)]
CaptureFile {
@@ -125,12 +126,15 @@ enum AnalyticsEventsDestination {
}
impl AnalyticsEventsDestination {
- fn from_base_url(base_url: String) -> Self {
+ fn from_base_url(base_url: ChatgptBaseUrl) -> Self {
let capture_file = analytics_capture_file_from_env();
Self::from_base_url_and_capture_file(base_url, capture_file)
}
- fn from_base_url_and_capture_file(base_url: String, capture_file: Option) -> Self {
+ fn from_base_url_and_capture_file(
+ base_url: ChatgptBaseUrl,
+ capture_file: Option,
+ ) -> Self {
#[cfg(debug_assertions)]
if let Some(path) = capture_file {
if let Err(err) = crate::analytics_capture::initialize(&path) {
@@ -149,9 +153,17 @@ impl AnalyticsEventsDestination {
#[cfg(not(debug_assertions))]
let _ = capture_file;
- let base_url = base_url.trim_end_matches('/');
- Self::Http {
- url: format!("{base_url}/codex/analytics-events/events"),
+ Self::Http { base_url }
+ }
+
+ fn url_for_auth(&self, auth: &CodexAuth) -> Option {
+ match self {
+ Self::Http { base_url } => {
+ let base_url = base_url.resolve(auth).trim_end_matches('/');
+ Some(format!("{base_url}/codex/analytics-events/events"))
+ }
+ #[cfg(debug_assertions)]
+ Self::CaptureFile { .. } => None,
}
}
}
@@ -313,10 +325,10 @@ impl AnalyticsEventsClient {
pub fn new(
auth_manager: Arc,
- base_url: String,
+ base_url: impl Into,
analytics_enabled: Option,
) -> Self {
- let destination = AnalyticsEventsDestination::from_base_url(base_url);
+ let destination = AnalyticsEventsDestination::from_base_url(base_url.into());
Self {
queue: (analytics_enabled != Some(false))
.then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), destination)),
@@ -436,22 +448,29 @@ impl AnalyticsEventsClient {
completed_at_ms: i64,
) {
let thread_id = item.sender_thread_id.to_string();
- let completed_item = TurnItem::CollabAgentToolCall(item.clone()).into();
+ let completed_item = codex_app_server_protocol::ThreadItemPayload::from(
+ TurnItem::CollabAgentToolCall(item.clone()),
+ );
item.status = CollabAgentToolCallStatus::InProgress;
- self.track_notification(&ServerNotification::ItemStarted(ItemStartedNotification {
- item: TurnItem::CollabAgentToolCall(item).into(),
- thread_id: thread_id.clone(),
- turn_id: turn_id.clone(),
- started_at_ms,
- }));
+ self.track_notification(&ServerNotification::ItemStarted(
+ ItemStartedNotification::new(
+ thread_id.clone(),
+ turn_id.clone(),
+ codex_app_server_protocol::ThreadItemPayload::from(TurnItem::CollabAgentToolCall(
+ item,
+ )),
+ started_at_ms,
+ ),
+ ));
self.track_notification(&ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- item: completed_item,
+ ItemCompletedNotification::new(
thread_id,
turn_id,
+ completed_item,
+ Some(started_at_ms),
completed_at_ms,
- },
+ ),
));
}
@@ -935,7 +954,7 @@ fn session_event_to_analytics_notification(
match ¬ification {
ServerNotification::ItemStarted(ItemStartedNotification { item, .. })
| ServerNotification::ItemCompleted(ItemCompletedNotification { item, .. }) => {
- tracked_tool_item_id(item)?;
+ tracked_tool_item_id(&item.payload)?;
}
_ => {}
}
@@ -1050,10 +1069,8 @@ async fn send_track_events_request(
return;
}
- let url = match destination {
- AnalyticsEventsDestination::Http { url } => url,
- #[cfg(debug_assertions)]
- AnalyticsEventsDestination::CaptureFile { .. } => return,
+ let Some(url) = destination.url_for_auth(auth) else {
+ return;
};
let headers = match codex_model_provider::auth_provider_from_auth(auth).to_auth_headers() {
Ok(headers) => headers,
@@ -1077,7 +1094,7 @@ async fn send_track_events_request(
}
};
let mut request = client
- .post(url)
+ .post(&url)
.timeout(ANALYTICS_EVENTS_TIMEOUT)
.headers(headers)
.header("Content-Type", "application/json")
diff --git a/codex-rs/analytics/src/client_product_tests.rs b/codex-rs/analytics/src/client_product_tests.rs
index 029dea7fd103..39c50f73ce75 100644
--- a/codex-rs/analytics/src/client_product_tests.rs
+++ b/codex-rs/analytics/src/client_product_tests.rs
@@ -22,7 +22,7 @@ use codex_app_server_protocol::ItemCompletedNotification;
use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
-use codex_app_server_protocol::ThreadItem;
+use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::TurnStartedNotification;
use codex_login::AuthManager;
use codex_protocol::ThreadId;
@@ -89,7 +89,7 @@ async fn capture_requests(
) {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let destination = AnalyticsEventsDestination::Http {
- url: format!("http://{}/events", listener.local_addr().unwrap()),
+ base_url: format!("http://{}", listener.local_addr().unwrap()).into(),
};
let (paused_tx, paused_rx) = tokio::sync::oneshot::channel();
let (resume_tx, resume_rx) = tokio::sync::oneshot::channel();
@@ -238,11 +238,10 @@ async fn buffered_tool_events_preserve_attribution_or_drop_it_on_queue_overflow(
turn: started.turn,
}));
// Collaborator completions wait in the reducer for subsequent sampling evidence.
- let completed = ItemCompletedNotification {
- thread_id: thread_id.to_string(),
- turn_id: "turn-1".to_string(),
- completed_at_ms: 2,
- item: ThreadItem::CollabAgentToolCall {
+ let completed = ItemCompletedNotification::new(
+ thread_id.to_string(),
+ "turn-1".to_string(),
+ ThreadItemPayload::CollabAgentToolCall {
id: "item".to_string(),
tool: CollabAgentTool::SendMessage,
status: CollabAgentToolCallStatus::Failed,
@@ -253,13 +252,17 @@ async fn buffered_tool_events_preserve_attribution_or_drop_it_on_queue_overflow(
reasoning_effort: None,
agents_states: Default::default(),
},
- };
- client.track_notification(&ServerNotification::ItemStarted(ItemStartedNotification {
- thread_id: thread_id.to_string(),
- turn_id: "turn-1".to_string(),
- started_at_ms: 1,
- item: completed.item.clone(),
- }));
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 2,
+ );
+ client.track_notification(&ServerNotification::ItemStarted(
+ ItemStartedNotification::new(
+ thread_id.to_string(),
+ "turn-1".to_string(),
+ completed.item.payload.clone(),
+ /*started_at_ms*/ 1,
+ ),
+ ));
client.track_notification(&ServerNotification::ItemCompleted(completed));
let measurement = PluginMeasurementsInput {
thread_id: thread_id.to_string(),
diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs
index e28a38e503a9..d7d8ff637e6a 100644
--- a/codex-rs/analytics/src/client_tests.rs
+++ b/codex-rs/analytics/src/client_tests.rs
@@ -95,6 +95,7 @@ use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
#[cfg(debug_assertions)]
use codex_login::AuthManager;
+use codex_model_provider::ChatgptBaseUrl;
use codex_protocol::protocol::RealtimeConversationVersion;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
@@ -308,7 +309,7 @@ fn client_with_receiver() -> (
fn analytics_destination_uses_explicit_capture_file() {
let capture_path = unique_capture_path("destination");
let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
- "https://chatgpt.com/backend-api/".to_string(),
+ "https://chatgpt.com/backend-api/".into(),
Some(capture_path.clone()),
);
@@ -337,31 +338,53 @@ fn analytics_destination_uses_explicit_capture_file() {
#[test]
fn analytics_destination_uses_http_without_capture_file() {
- let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
- "https://chatgpt.com/backend-api/".to_string(),
- /*capture_file*/ None,
- );
-
- assert_eq!(
- destination,
- AnalyticsEventsDestination::Http {
- url: "https://chatgpt.com/backend-api/codex/analytics-events/events".to_string()
+ let commercial_url = "https://chatgpt.com/backend-api/codex/analytics-events/events";
+ let gov_url = "https://gov.chatgpt.com/backend-api/codex/analytics-events/events";
+ for (base_url, expected_urls) in [
+ (
+ ChatgptBaseUrl::Default,
+ [commercial_url, gov_url, commercial_url],
+ ),
+ (
+ ChatgptBaseUrl::from("https://chatgpt.com/backend-api/"),
+ [commercial_url; 3],
+ ),
+ (
+ ChatgptBaseUrl::from("https://proxy.example/backend-api/"),
+ ["https://proxy.example/backend-api/codex/analytics-events/events"; 3],
+ ),
+ ] {
+ let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
+ base_url.clone(),
+ /*capture_file*/ None,
+ );
+ assert_eq!(destination, AnalyticsEventsDestination::Http { base_url });
+
+ for (api_key, expected_url) in ["sk-test", "sk-proj-fedramp-test", "sk-test"]
+ .into_iter()
+ .zip(expected_urls)
+ {
+ let auth = codex_login::CodexAuth::from_api_key(api_key);
+ assert_eq!(
+ destination.url_for_auth(&auth).as_deref(),
+ Some(expected_url)
+ );
}
- );
+ }
}
#[test]
#[cfg(not(debug_assertions))]
fn analytics_destination_ignores_capture_file_in_release() {
let destination = AnalyticsEventsDestination::from_base_url_and_capture_file(
- "https://chatgpt.com/backend-api/".to_string(),
+ "https://chatgpt.com/backend-api/".into(),
Some(std::path::PathBuf::from("ignored.jsonl")),
);
assert_eq!(
destination,
AnalyticsEventsDestination::Http {
- url: "https://chatgpt.com/backend-api/codex/analytics-events/events".to_string()
+ base_url: "https://chatgpt.com/backend-api/".into()
}
);
}
@@ -1172,7 +1195,7 @@ async fn account_switch_between_batches_does_not_send_old_credentials() {
assert!(controller.publish(policy.revision(), DestinationPolicy::Unrestricted));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let destination = AnalyticsEventsDestination::Http {
- url: format!("http://{}/events", listener.local_addr().unwrap()),
+ base_url: format!("http://{}", listener.local_addr().unwrap()).into(),
};
let requests = Arc::new(Mutex::new(Vec::new()));
let captured = Arc::clone(&requests);
diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs
index cb0aacff8dcc..0cd0f66ad5fd 100644
--- a/codex-rs/analytics/src/events.rs
+++ b/codex-rs/analytics/src/events.rs
@@ -85,6 +85,7 @@ pub(crate) enum TrackEventRequest {
CommandExecution(CodexCommandExecutionEventRequest),
PluginMeasurement(CodexPluginMeasurementEventRequest),
FileChange(CodexFileChangeEventRequest),
+ CuaToolCallStarted(CodexCuaToolCallStartedEventRequest),
McpToolCall(CodexMcpToolCallEventRequest),
DynamicToolCall(CodexDynamicToolCallEventRequest),
ControlToolCall(CodexControlToolCallEventRequest),
@@ -167,6 +168,7 @@ impl TrackEventRequest {
match self {
Self::PluginUsed(event) => event.event_params.plugin.plugin_id.is_some(),
Self::SkillInvocation(event) => event.event_params.plugin_id.is_some(),
+ Self::CuaToolCallStarted(event) => event.event_params.plugin_id.is_some(),
Self::McpToolCall(event) => event.event_params.plugin_id.is_some(),
Self::ArtifactOperation(event) => !event.event_params.plugin_id.is_empty(),
Self::PluginMeasurement(event) => !event.event_params.plugin_id.is_empty(),
@@ -367,6 +369,7 @@ impl From<&AdditionalPermissionProfile> for GuardianAdditionalPermissions {
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GuardianReviewedAction {
+ NativeToolCall {},
Shell {
sandbox_permissions: SandboxPermissions,
additional_permissions: Option,
@@ -714,6 +717,35 @@ pub(crate) struct CodexToolItemEventBase {
pub(crate) requested_network_access: bool,
}
+#[derive(Serialize)]
+pub(crate) struct CodexCuaToolCallStartedEventParams {
+ pub(crate) thread_id: String,
+ pub(crate) session_id: String,
+ pub(crate) turn_id: String,
+ pub(crate) root_turn_id: Option,
+ pub(crate) item_id: String,
+ pub(crate) cell_id: Option,
+ pub(crate) parent_call_id: Option,
+ pub(crate) originating_response_id: Option,
+ pub(crate) app_server_client: CodexAppServerClientMetadata,
+ pub(crate) runtime: CodexRuntimeMetadata,
+ pub(crate) thread_source: Option,
+ pub(crate) subagent_source: Option,
+ pub(crate) parent_thread_id: Option,
+ pub(crate) tool_event_type: Option,
+ pub(crate) started_at_ms: u64,
+ pub(crate) mcp_server_name: String,
+ pub(crate) mcp_tool_name: String,
+ pub(crate) plugin_id: Option,
+ pub(crate) connector_id: Option,
+}
+
+#[derive(Serialize)]
+pub(crate) struct CodexCuaToolCallStartedEventRequest {
+ pub(crate) event_type: &'static str,
+ pub(crate) event_params: CodexCuaToolCallStartedEventParams,
+}
+
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
@@ -1079,6 +1111,8 @@ pub(crate) struct CodexGoalEventRequest {
#[derive(Serialize)]
pub(crate) struct CodexTurnEventParams {
+ #[serde(flatten)]
+ pub(crate) critical_path: crate::turn_critical_path::CriticalPathEventParams,
pub(crate) thread_id: String,
pub(crate) session_id: String,
pub(crate) turn_id: String,
diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs
index 9189babd9f1b..68d055c80fca 100644
--- a/codex-rs/analytics/src/facts.rs
+++ b/codex-rs/analytics/src/facts.rs
@@ -246,6 +246,7 @@ pub struct TurnTokenUsageFact {
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct TurnProfile {
+ pub critical_path: crate::TurnCriticalPath,
pub before_first_sampling_ms: u64,
pub sampling_ms: u64,
pub compaction_ms: u64,
diff --git a/codex-rs/analytics/src/lib.rs b/codex-rs/analytics/src/lib.rs
index 6160dd704a0a..15a1d4fd0bbf 100644
--- a/codex-rs/analytics/src/lib.rs
+++ b/codex-rs/analytics/src/lib.rs
@@ -8,6 +8,7 @@ mod guardian_v2;
mod product_attribution;
mod reducer;
mod thread_hint;
+mod turn_critical_path;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
@@ -82,6 +83,8 @@ pub use guardian_v2::GuardianV2EventKind;
pub use product_attribution::ThreadProductUpdate;
pub use thread_hint::ThreadHintStatus;
pub use thread_hint::ThreadHintStatusEvent;
+pub use turn_critical_path::TurnCriticalPath;
+pub use turn_critical_path::TurnCriticalPathDurations;
#[cfg(test)]
mod tests;
diff --git a/codex-rs/analytics/src/product_attribution.rs b/codex-rs/analytics/src/product_attribution.rs
index 4fa872102e68..da5958f35d52 100644
--- a/codex-rs/analytics/src/product_attribution.rs
+++ b/codex-rs/analytics/src/product_attribution.rs
@@ -88,6 +88,7 @@ fn event_thread_id(event: &TrackEventRequest) -> Option<&str> {
TrackEventRequest::CommandExecution(event) => Some(&event.event_params.base.thread_id),
TrackEventRequest::PluginMeasurement(event) => Some(&event.event_params.thread_id),
TrackEventRequest::FileChange(event) => Some(&event.event_params.base.thread_id),
+ TrackEventRequest::CuaToolCallStarted(event) => Some(&event.event_params.thread_id),
TrackEventRequest::McpToolCall(event) => Some(&event.event_params.base.thread_id),
TrackEventRequest::DynamicToolCall(event) => Some(&event.event_params.base.thread_id),
TrackEventRequest::ControlToolCall(event) => Some(&event.event_params.base.thread_id),
diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs
index 0bebe6771d3f..d0707e36c34d 100644
--- a/codex-rs/analytics/src/reducer.rs
+++ b/codex-rs/analytics/src/reducer.rs
@@ -14,6 +14,8 @@ use crate::events::CodexCommandExecutionEventRequest;
use crate::events::CodexCompactionEventRequest;
use crate::events::CodexControlToolCallEventParams;
use crate::events::CodexControlToolCallEventRequest;
+use crate::events::CodexCuaToolCallStartedEventParams;
+use crate::events::CodexCuaToolCallStartedEventRequest;
use crate::events::CodexDynamicToolCallEventParams;
use crate::events::CodexDynamicToolCallEventRequest;
use crate::events::CodexFileChangeEventParams;
@@ -152,7 +154,7 @@ use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ServerResponse;
use codex_app_server_protocol::SubAgentActivityKind;
-use codex_app_server_protocol::ThreadItem;
+use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::TurnSteerResponse;
use codex_app_server_protocol::UserInput;
use codex_app_server_protocol::WebSearchAction;
@@ -165,6 +167,7 @@ use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::items::ModelInvocationContext;
use codex_protocol::items::is_safe_plugin_relative_path;
+use codex_protocol::mcp::is_node_repl_backed_connector;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SkillScope;
@@ -342,6 +345,16 @@ impl<'a> AnalyticsDropSite<'a> {
}
}
+ fn cua_tool_call_started(thread_id: &'a str, turn_id: &'a str, item_id: &'a str) -> Self {
+ Self {
+ event_name: "CUA tool call start",
+ thread_id,
+ turn_id: Some(turn_id),
+ review_id: None,
+ item_id: Some(item_id),
+ }
+ }
+
fn turn_steer(thread_id: &'a str) -> Self {
Self {
event_name: "turn steer",
@@ -478,6 +491,7 @@ struct TurnState {
latest_diff: Option,
steer_count: usize,
tool_counts: TurnToolCounts,
+ completed_cua_tool_item_ids: HashSet,
resource_skill_invocations: HashSet,
turn_event_emitted: bool,
}
@@ -521,36 +535,36 @@ struct TurnToolCounts {
}
impl TurnToolCounts {
- fn record(&mut self, item: &ThreadItem) {
+ fn record(&mut self, item: &ThreadItemPayload) {
match item {
- ThreadItem::CommandExecution { .. } => self.shell_command += 1,
- ThreadItem::FileChange { .. } => self.file_change += 1,
- ThreadItem::McpToolCall { .. } => self.mcp_tool_call += 1,
- ThreadItem::DynamicToolCall { .. } => self.dynamic_tool_call += 1,
- ThreadItem::SubAgentActivity {
+ ThreadItemPayload::CommandExecution { .. } => self.shell_command += 1,
+ ThreadItemPayload::FileChange { .. } => self.file_change += 1,
+ ThreadItemPayload::McpToolCall { .. } => self.mcp_tool_call += 1,
+ ThreadItemPayload::DynamicToolCall { .. } => self.dynamic_tool_call += 1,
+ ThreadItemPayload::SubAgentActivity {
kind: SubAgentActivityKind::Completed,
..
} => return,
- ThreadItem::CollabAgentToolCall { id, .. }
- | ThreadItem::SubAgentActivity { id, .. } => {
+ ThreadItemPayload::CollabAgentToolCall { id, .. }
+ | ThreadItemPayload::SubAgentActivity { id, .. } => {
if !self.subagent_tool_call_ids.insert(id.clone()) {
return;
}
self.subagent_tool_call += 1;
}
- ThreadItem::WebSearch(_) => self.web_search += 1,
- ThreadItem::ImageGeneration(_) => self.image_generation += 1,
- ThreadItem::UserMessage { .. }
- | ThreadItem::HookPrompt { .. }
- | ThreadItem::AgentMessage { .. }
- | ThreadItem::FunctionCallOutput { .. }
- | ThreadItem::Plan { .. }
- | ThreadItem::Reasoning { .. }
- | ThreadItem::ImageView { .. }
- | ThreadItem::Sleep(_)
- | ThreadItem::EnteredReviewMode { .. }
- | ThreadItem::ExitedReviewMode { .. }
- | ThreadItem::ContextCompaction { .. } => return,
+ ThreadItemPayload::WebSearch(_) => self.web_search += 1,
+ ThreadItemPayload::ImageGeneration(_) => self.image_generation += 1,
+ ThreadItemPayload::UserMessage { .. }
+ | ThreadItemPayload::HookPrompt { .. }
+ | ThreadItemPayload::AgentMessage { .. }
+ | ThreadItemPayload::FunctionCallOutput { .. }
+ | ThreadItemPayload::Plan { .. }
+ | ThreadItemPayload::Reasoning { .. }
+ | ThreadItemPayload::ImageView { .. }
+ | ThreadItemPayload::Sleep(_)
+ | ThreadItemPayload::EnteredReviewMode { .. }
+ | ThreadItemPayload::ExitedReviewMode { .. }
+ | ThreadItemPayload::ContextCompaction { .. } => return,
}
self.total += 1;
}
@@ -2046,31 +2060,100 @@ impl AnalyticsReducer {
}));
}
ServerNotification::ItemStarted(notification) => {
- let Some(item_id) = tracked_tool_item_id(¬ification.item) else {
+ let Some(item_id) = tracked_tool_item_id(¬ification.item.payload) else {
return;
};
let Some(started_at_ms) = option_i64_to_u64(Some(notification.started_at_ms))
else {
return;
};
- let model_context = match ¬ification.item {
- ThreadItem::CommandExecution { model_context, .. } => model_context.clone(),
+ let model_context = match ¬ification.item.payload {
+ ThreadItemPayload::CommandExecution { model_context, .. } => {
+ model_context.clone()
+ }
_ => None,
};
self.turn_state(notification.turn_id.clone())
.thread_id
.get_or_insert(notification.thread_id.clone());
+ let key = ToolItemKey {
+ thread_id: notification.thread_id.clone(),
+ turn_id: notification.turn_id.clone(),
+ item_id: item_id.to_string(),
+ };
+ let cua_identity = cua_mcp_tool_identity(¬ification.item.payload);
+ if cua_identity.is_some()
+ && self
+ .turns
+ .get(¬ification.turn_id)
+ .is_some_and(|turn| turn.completed_cua_tool_item_ids.contains(item_id))
+ {
+ return;
+ }
+ let first_notification = !self.tool_items_started_at_ms.contains_key(&key);
self.tool_items_started_at_ms
- .entry(ToolItemKey {
- thread_id: notification.thread_id,
- turn_id: notification.turn_id,
- item_id: item_id.to_string(),
- })
+ .entry(key)
.and_modify(|(timestamp, _)| *timestamp = started_at_ms)
.or_insert((started_at_ms, model_context));
+ if !first_notification {
+ return;
+ }
+ let Some((server, tool, plugin_id, connector_id)) = cua_identity else {
+ return;
+ };
+ let Some((connection_state, thread_state, thread_metadata)) = self
+ .thread_context_or_warn(AnalyticsDropSite::cua_tool_call_started(
+ ¬ification.thread_id,
+ ¬ification.turn_id,
+ item_id,
+ ))
+ else {
+ return;
+ };
+ let attribution = tool_response_attribution(
+ item_id,
+ /*cell_id*/ None,
+ self.tool_response_states
+ .get(&(notification.thread_id.clone(), notification.turn_id.clone())),
+ self.code_mode_cells.get(¬ification.thread_id),
+ );
+ let root_turn_id = self
+ .turns
+ .get(¬ification.turn_id)
+ .and_then(|turn| turn.resolved_config.as_ref())
+ .and_then(|config| config.turn_metadata.root_turn_id());
+ out.push(TrackEventRequest::CuaToolCallStarted(
+ CodexCuaToolCallStartedEventRequest {
+ event_type: "codex_cua_tool_call_started",
+ event_params: CodexCuaToolCallStartedEventParams {
+ thread_id: notification.thread_id,
+ session_id: thread_metadata.session_id.clone(),
+ turn_id: notification.turn_id,
+ root_turn_id,
+ item_id: item_id.to_string(),
+ cell_id: attribution.cell_id,
+ parent_call_id: attribution.parent_call_id,
+ originating_response_id: attribution.originating_response_id,
+ app_server_client: thread_state.app_server_client(connection_state),
+ runtime: connection_state.runtime.clone(),
+ thread_source: thread_metadata.thread_source.clone(),
+ subagent_source: thread_metadata.subagent_source.clone(),
+ parent_thread_id: thread_metadata.parent_thread_id.clone(),
+ tool_event_type: attribution.tool_event_type,
+ started_at_ms,
+ mcp_server_name: server.to_string(),
+ mcp_tool_name: tool.to_string(),
+ plugin_id: plugin_id.map(str::to_string),
+ connector_id: connector_id.map(str::to_string),
+ },
+ },
+ ));
}
ServerNotification::ItemCompleted(notification) => {
- if matches!(notification.item, ThreadItem::SubAgentActivity { .. }) {
+ if matches!(
+ notification.item.payload,
+ ThreadItemPayload::SubAgentActivity { .. }
+ ) {
let Some(turn_state) = self.turns.get_mut(¬ification.turn_id) else {
tracing::warn!(
thread_id = %notification.thread_id,
@@ -2079,10 +2162,10 @@ impl AnalyticsReducer {
);
return;
};
- turn_state.tool_counts.record(¬ification.item);
+ turn_state.tool_counts.record(¬ification.item.payload);
return;
}
- let Some(item_id) = tracked_tool_item_id(¬ification.item) else {
+ let Some(item_id) = tracked_tool_item_id(¬ification.item.payload) else {
return;
};
let key = ToolItemKey {
@@ -2090,18 +2173,29 @@ impl AnalyticsReducer {
turn_id: notification.turn_id.clone(),
item_id: item_id.to_string(),
};
- let elicitation_type =
- if matches!(notification.item, ThreadItem::McpToolCall { .. }) {
- // Producers enqueue known classifications before completion on
- // the same analytics queue, so completed keys need no history.
- self.pending_mcp_tool_elicitations
- .iter()
- .position(|(pending, _)| pending == &key)
- .and_then(|index| self.pending_mcp_tool_elicitations.remove(index))
- .map(|(_, elicitation_type)| elicitation_type)
- } else {
- None
- };
+ let is_cua = cua_mcp_tool_identity(¬ification.item.payload).is_some();
+ if is_cua
+ && self
+ .turns
+ .get(¬ification.turn_id)
+ .is_some_and(|turn| turn.completed_cua_tool_item_ids.contains(item_id))
+ {
+ return;
+ }
+ let elicitation_type = if matches!(
+ notification.item.payload,
+ ThreadItemPayload::McpToolCall { .. }
+ ) {
+ // Producers enqueue known classifications before completion on
+ // the same analytics queue, so completed keys need no history.
+ self.pending_mcp_tool_elicitations
+ .iter()
+ .position(|(pending, _)| pending == &key)
+ .and_then(|index| self.pending_mcp_tool_elicitations.remove(index))
+ .map(|(_, elicitation_type)| elicitation_type)
+ } else {
+ None
+ };
let Some(turn_state) = self.turns.get_mut(¬ification.turn_id) else {
tracing::warn!(
thread_id = %notification.thread_id,
@@ -2111,7 +2205,7 @@ impl AnalyticsReducer {
);
return;
};
- turn_state.tool_counts.record(¬ification.item);
+ turn_state.tool_counts.record(¬ification.item.payload);
let Some((started_at_ms, model_context)) =
self.tool_items_started_at_ms.remove(&key)
else {
@@ -2135,7 +2229,7 @@ impl AnalyticsReducer {
if let Some(mut event) = tool_item_event(ToolItemEventInput {
thread_id: ¬ification.thread_id,
turn_id: ¬ification.turn_id,
- item: ¬ification.item,
+ item: ¬ification.item.payload,
model_context: model_context.as_ref(),
started_at_ms,
completed_at_ms,
@@ -2158,12 +2252,14 @@ impl AnalyticsReducer {
.and_then(|config| config.turn_metadata.root_turn_id());
// Fast collaborator tools can complete before their sampling response.
// Keep the event until that response can supply its originating ID.
- let emission =
- if matches!(¬ification.item, ThreadItem::CollabAgentToolCall { .. }) {
- ToolEventEmission::AwaitResponse
- } else {
- ToolEventEmission::ImmediateUnlessCorrelated
- };
+ let emission = if matches!(
+ ¬ification.item.payload,
+ ThreadItemPayload::CollabAgentToolCall { .. }
+ ) {
+ ToolEventEmission::AwaitResponse
+ } else {
+ ToolEventEmission::ImmediateUnlessCorrelated
+ };
self.record_tool_event(
¬ification.thread_id,
¬ification.turn_id,
@@ -2172,6 +2268,9 @@ impl AnalyticsReducer {
emission,
out,
);
+ if is_cua && let Some(turn) = self.turns.get_mut(¬ification.turn_id) {
+ turn.completed_cua_tool_item_ids.insert(item_id.to_string());
+ }
}
self.item_review_summaries.remove(&key);
if self
@@ -2813,30 +2912,54 @@ fn warn_missing_analytics_context(
);
}
-pub(crate) fn tracked_tool_item_id(item: &ThreadItem) -> Option<&str> {
+pub(crate) fn tracked_tool_item_id(item: &ThreadItemPayload) -> Option<&str> {
match item {
- ThreadItem::CommandExecution { id, .. }
- | ThreadItem::FileChange { id, .. }
- | ThreadItem::McpToolCall { id, .. }
- | ThreadItem::DynamicToolCall { id, .. }
- | ThreadItem::CollabAgentToolCall { id, .. } => Some(id),
- ThreadItem::WebSearch(item) => Some(&item.id),
- ThreadItem::ImageGeneration(item) => Some(&item.id),
- ThreadItem::UserMessage { .. }
- | ThreadItem::HookPrompt { .. }
- | ThreadItem::AgentMessage { .. }
- | ThreadItem::FunctionCallOutput { .. }
- | ThreadItem::Plan { .. }
- | ThreadItem::Reasoning { .. }
- | ThreadItem::SubAgentActivity { .. }
- | ThreadItem::ImageView { .. }
- | ThreadItem::Sleep(_)
- | ThreadItem::EnteredReviewMode { .. }
- | ThreadItem::ExitedReviewMode { .. }
- | ThreadItem::ContextCompaction { .. } => None,
+ ThreadItemPayload::CommandExecution { id, .. }
+ | ThreadItemPayload::FileChange { id, .. }
+ | ThreadItemPayload::McpToolCall { id, .. }
+ | ThreadItemPayload::DynamicToolCall { id, .. }
+ | ThreadItemPayload::CollabAgentToolCall { id, .. } => Some(id),
+ ThreadItemPayload::WebSearch(item) => Some(&item.id),
+ ThreadItemPayload::ImageGeneration(item) => Some(&item.id),
+ ThreadItemPayload::UserMessage { .. }
+ | ThreadItemPayload::HookPrompt { .. }
+ | ThreadItemPayload::AgentMessage { .. }
+ | ThreadItemPayload::FunctionCallOutput { .. }
+ | ThreadItemPayload::Plan { .. }
+ | ThreadItemPayload::Reasoning { .. }
+ | ThreadItemPayload::SubAgentActivity { .. }
+ | ThreadItemPayload::ImageView { .. }
+ | ThreadItemPayload::Sleep(_)
+ | ThreadItemPayload::EnteredReviewMode { .. }
+ | ThreadItemPayload::ExitedReviewMode { .. }
+ | ThreadItemPayload::ContextCompaction { .. } => None,
}
}
+fn cua_mcp_tool_identity(
+ item: &ThreadItemPayload,
+) -> Option<(&str, &str, Option<&str>, Option<&str>)> {
+ let ThreadItemPayload::McpToolCall {
+ server,
+ tool,
+ plugin_id,
+ app_context,
+ ..
+ } = item
+ else {
+ return None;
+ };
+ let connector_id = app_context
+ .as_ref()
+ .map(|app_context| app_context.connector_id.as_str());
+ (tool == "js" && is_node_repl_backed_connector(server, connector_id)).then_some((
+ server,
+ tool,
+ plugin_id.as_deref(),
+ connector_id,
+ ))
+}
+
fn tool_event_base_mut(event: &mut TrackEventRequest) -> Option<&mut CodexToolItemEventBase> {
match event {
TrackEventRequest::CommandExecution(event) => Some(&mut event.event_params.base),
@@ -2859,36 +2982,52 @@ fn enrich_tool_response_event(
let Some(base) = tool_event_base_mut(event) else {
return;
};
+ let attribution =
+ tool_response_attribution(&base.item_id, base.cell_id.clone(), Some(state), cells);
+ base.tool_event_type = attribution.tool_event_type;
+ base.cell_id = attribution.cell_id;
+ base.parent_call_id = attribution.parent_call_id;
+ base.originating_response_id = attribution.originating_response_id;
+}
+
+#[derive(Default)]
+struct ToolResponseAttribution {
+ tool_event_type: Option,
+ cell_id: Option,
+ parent_call_id: Option,
+ originating_response_id: Option,
+}
+
+fn tool_response_attribution(
+ item_id: &str,
+ cell_id: Option,
+ state: Option<&ToolResponseState>,
+ cells: Option<&HashMap>,
+) -> ToolResponseAttribution {
// A cell association can also describe a separately sampled wait call. Classify
// only from evidence about this exact call ID, not its parent or response lineage.
- base.tool_event_type = match (
- state.response_ids_by_call_id.contains_key(&base.item_id),
- state.cell_ids_by_child_call_id.contains_key(&base.item_id),
+ let tool_event_type = match (
+ state.is_some_and(|state| state.response_ids_by_call_id.contains_key(item_id)),
+ state.is_some_and(|state| state.cell_ids_by_child_call_id.contains_key(item_id)),
) {
(true, false) => Some(ToolEventType::ModelToolCall),
(false, true) => Some(ToolEventType::InnerToolCall),
(false, false) | (true, true) => None,
};
- if base.cell_id.is_none() {
- base.cell_id = state.cell_ids_by_child_call_id.get(&base.item_id).cloned();
- }
-
- let cell = base
- .cell_id
- .as_ref()
- .and_then(|cell_id| cells?.get(cell_id));
- if let Some(cell) = cell {
- base.parent_call_id =
- (cell.parent_call_id != base.item_id).then(|| cell.parent_call_id.clone());
- } else {
- base.cell_id = None;
- base.parent_call_id = None;
- }
- base.originating_response_id = state
- .response_ids_by_call_id
- .get(&base.item_id)
- .cloned()
+ let cell_id = cell_id
+ .or_else(|| state.and_then(|state| state.cell_ids_by_child_call_id.get(item_id).cloned()));
+ let cell = cell_id.as_ref().and_then(|cell_id| cells?.get(cell_id));
+ let parent_call_id =
+ cell.and_then(|cell| (cell.parent_call_id != item_id).then(|| cell.parent_call_id.clone()));
+ let originating_response_id = state
+ .and_then(|state| state.response_ids_by_call_id.get(item_id).cloned())
.or_else(|| cell.and_then(|cell| cell.originating_response_id.clone()));
+ ToolResponseAttribution {
+ tool_event_type,
+ cell_id: cell.is_some().then_some(cell_id).flatten(),
+ parent_call_id,
+ originating_response_id,
+ }
}
fn item_review_summary_key(pending_review: &PendingReviewState) -> Option {
@@ -2909,7 +3048,7 @@ fn item_review_summary_key(pending_review: &PendingReviewState) -> Option {
thread_id: &'a str,
turn_id: &'a str,
- item: &'a ThreadItem,
+ item: &'a ThreadItemPayload,
model_context: Option<&'a ModelInvocationContext>,
started_at_ms: u64,
completed_at_ms: u64,
@@ -2935,7 +3074,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
elicitation_type,
} = input;
match item {
- ThreadItem::CommandExecution {
+ ThreadItemPayload::CommandExecution {
id,
sandbox_type,
plugin_id,
@@ -2994,7 +3133,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
))
}
- ThreadItem::FileChange {
+ ThreadItemPayload::FileChange {
id,
changes,
status,
@@ -3032,7 +3171,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
}))
}
- ThreadItem::McpToolCall {
+ ThreadItemPayload::McpToolCall {
id,
server,
tool,
@@ -3081,7 +3220,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
))
}
- ThreadItem::DynamicToolCall {
+ ThreadItemPayload::DynamicToolCall {
id,
tool,
status,
@@ -3128,7 +3267,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
))
}
- ThreadItem::CollabAgentToolCall {
+ ThreadItemPayload::CollabAgentToolCall {
id,
tool,
status,
@@ -3195,7 +3334,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
))
}
- ThreadItem::WebSearch(item) => {
+ ThreadItemPayload::WebSearch(item) => {
let base = tool_item_base(
thread_id,
turn_id,
@@ -3225,7 +3364,7 @@ fn tool_item_event(input: ToolItemEventInput<'_>) -> Option {
},
}))
}
- ThreadItem::ImageGeneration(item) => {
+ ThreadItemPayload::ImageGeneration(item) => {
let (terminal_status, failure_kind) = image_generation_outcome(item.status.as_str());
let base = tool_item_base(
thread_id,
@@ -3819,6 +3958,7 @@ fn codex_turn_event_params(
is_first_turn,
} = resolved_config;
let TurnProfile {
+ critical_path,
before_first_sampling_ms,
sampling_ms,
compaction_ms,
@@ -3832,6 +3972,7 @@ fn codex_turn_event_params(
let token_usage = turn_state.token_usage.clone();
let codex_error = turn_state.codex_error.as_ref();
CodexTurnEventParams {
+ critical_path: critical_path.into(),
thread_id,
session_id: thread_metadata.session_id.clone(),
turn_id,
@@ -4107,11 +4248,10 @@ mod tests {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: keys[2].thread_id.clone(),
- turn_id: keys[2].turn_id.clone(),
- completed_at_ms: 1_000,
- item: ThreadItem::McpToolCall {
+ ItemCompletedNotification::new(
+ keys[2].thread_id.clone(),
+ keys[2].turn_id.clone(),
+ ThreadItemPayload::McpToolCall {
id: keys[2].item_id.clone(),
server: "server".to_string(),
tool: "tool".to_string(),
@@ -4126,7 +4266,9 @@ mod tests {
error: None,
duration_ms: None,
},
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut events,
)
diff --git a/codex-rs/analytics/src/tests/suite/reducer_ordering_tests.rs b/codex-rs/analytics/src/tests/suite/reducer_ordering_tests.rs
index 1275543ce45f..d6e8c65f2a92 100644
--- a/codex-rs/analytics/src/tests/suite/reducer_ordering_tests.rs
+++ b/codex-rs/analytics/src/tests/suite/reducer_ordering_tests.rs
@@ -308,16 +308,16 @@ async fn completed_background_tool_item_emits_after_turn_event() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- started_at_ms: 998,
- item: sample_command_execution_item(
+ ItemStartedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ sample_command_execution_item(
CommandExecutionStatus::InProgress,
/*exit_code*/ None,
/*duration_ms*/ None,
),
- },
+ /*started_at_ms*/ 998,
+ ),
))),
&mut out,
)
@@ -343,16 +343,17 @@ async fn completed_background_tool_item_emits_after_turn_event() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- completed_at_ms: 1_000,
- item: sample_command_execution_item(
+ ItemCompletedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ sample_command_execution_item(
CommandExecutionStatus::Completed,
Some(0),
Some(1),
),
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut out,
)
@@ -430,16 +431,17 @@ async fn item_completed_without_turn_state_does_not_create_turn_state() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- completed_at_ms: 1_000,
- item: sample_command_execution_item(
+ ItemCompletedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ sample_command_execution_item(
CommandExecutionStatus::Completed,
Some(0),
Some(1),
),
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut out,
)
diff --git a/codex-rs/analytics/src/tests/suite/threads_tests.rs b/codex-rs/analytics/src/tests/suite/threads_tests.rs
index 49eb280e7c70..b0c4cc0f0847 100644
--- a/codex-rs/analytics/src/tests/suite/threads_tests.rs
+++ b/codex-rs/analytics/src/tests/suite/threads_tests.rs
@@ -830,16 +830,16 @@ async fn subagent_tool_items_inherit_parent_connection_metadata() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-subagent".to_string(),
- turn_id: "turn-subagent".to_string(),
- started_at_ms: 1_000,
- item: sample_command_execution_item(
+ ItemStartedNotification::new(
+ "thread-subagent".to_string(),
+ "turn-subagent".to_string(),
+ sample_command_execution_item(
CommandExecutionStatus::InProgress,
/*exit_code*/ None,
/*duration_ms*/ None,
),
- },
+ /*started_at_ms*/ 1_000,
+ ),
))),
&mut events,
)
@@ -847,16 +847,17 @@ async fn subagent_tool_items_inherit_parent_connection_metadata() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-subagent".to_string(),
- turn_id: "turn-subagent".to_string(),
- completed_at_ms: 1_042,
- item: sample_command_execution_item(
+ ItemCompletedNotification::new(
+ "thread-subagent".to_string(),
+ "turn-subagent".to_string(),
+ sample_command_execution_item(
CommandExecutionStatus::Completed,
Some(0),
Some(42),
),
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_042,
+ ),
))),
&mut events,
)
diff --git a/codex-rs/analytics/src/tests/suite/tools_tests.rs b/codex-rs/analytics/src/tests/suite/tools_tests.rs
index 6272b8458fe3..9ca6b21d45ca 100644
--- a/codex-rs/analytics/src/tests/suite/tools_tests.rs
+++ b/codex-rs/analytics/src/tests/suite/tools_tests.rs
@@ -47,7 +47,7 @@ use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::McpToolCallAppContext;
use codex_app_server_protocol::McpToolCallStatus;
use codex_app_server_protocol::ServerNotification;
-use codex_app_server_protocol::ThreadItem;
+use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::sandbox::SandboxType;
@@ -64,9 +64,9 @@ fn sample_command_execution_item_with_actions(
plugin_id: Option<&str>,
script_path: Option<&str>,
sandbox_type: Option,
-) -> ThreadItem {
+) -> ThreadItemPayload {
let mut item = sample_command_execution_item(status, exit_code, duration_ms);
- let ThreadItem::CommandExecution {
+ let ThreadItemPayload::CommandExecution {
command_actions: item_command_actions,
plugin_id: item_plugin_id,
script_path: item_script_path,
@@ -218,6 +218,101 @@ fn command_execution_event_serializes_expected_shape() {
assert_eq!(payload, expected);
}
+#[tokio::test]
+async fn cua_tool_call_lifecycle_requires_a_start_and_deduplicates_replay() {
+ let mut reducer = AnalyticsReducer::default();
+ let mut events = Vec::new();
+ ingest_turn_prerequisites(
+ &mut reducer,
+ &mut events,
+ /*include_initialize*/ true,
+ /*include_resolved_config*/ true,
+ /*include_started*/ true,
+ /*include_token_usage*/ false,
+ )
+ .await;
+ events.clear();
+
+ let item = |status| ThreadItemPayload::McpToolCall {
+ id: "cua-call".into(),
+ server: "codex_apps".into(),
+ tool: "js".into(),
+ status,
+ arguments: json!({"script": "synthetic-private-input"}),
+ app_context: Some(McpToolCallAppContext {
+ connector_id: "connector_openai_browser".into(),
+ link_id: None,
+ resource_uri: None,
+ app_name: None,
+ action_name: None,
+ }),
+ mcp_app_resource_uri: None,
+ mcp_app_ui: None,
+ plugin_id: Some("browser@openai-curated".into()),
+ read_only_hint: None,
+ result: None,
+ error: None,
+ duration_ms: None,
+ };
+ let started = ServerNotification::ItemStarted(ItemStartedNotification::new(
+ "thread-2".into(),
+ "turn-2".into(),
+ item(McpToolCallStatus::InProgress),
+ /*started_at_ms*/ 1_000,
+ ));
+ let completed = ServerNotification::ItemCompleted(ItemCompletedNotification::new(
+ "thread-2".into(),
+ "turn-2".into(),
+ item(McpToolCallStatus::Completed),
+ Some(1_000),
+ /*completed_at_ms*/ 1_100,
+ ));
+ for notification in [
+ completed.clone(), // Missing start must not poison later deduplication.
+ started.clone(),
+ started.clone(),
+ completed.clone(),
+ started, // Replay after a valid terminal.
+ completed,
+ ] {
+ reducer
+ .ingest(
+ AnalyticsFact::Notification(Box::new(notification)),
+ &mut events,
+ )
+ .await;
+ }
+
+ let [
+ TrackEventRequest::CuaToolCallStarted(start),
+ TrackEventRequest::McpToolCall(terminal),
+ ] = events.as_slice()
+ else {
+ panic!("expected exactly one CUA start and one terminal");
+ };
+ assert_eq!(
+ (
+ start.event_type,
+ start.event_params.connector_id.as_deref(),
+ start.event_params.plugin_id.as_deref(),
+ start.event_params.started_at_ms,
+ terminal.event_params.base.started_at_ms,
+ ),
+ (
+ "codex_cua_tool_call_started",
+ Some("connector_openai_browser"),
+ Some("browser@openai-curated"),
+ 1_000,
+ 1_000,
+ )
+ );
+ assert!(
+ !serde_json::to_string(&events)
+ .expect("serialize CUA lifecycle events")
+ .contains("synthetic-private-input")
+ );
+}
+
#[tokio::test]
async fn item_lifecycle_notifications_publish_command_execution_event() {
let mut reducer = AnalyticsReducer::default();
@@ -236,17 +331,18 @@ async fn item_lifecycle_notifications_publish_command_execution_event() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-1".to_string(),
- turn_id: "turn-1".to_string(),
- started_at_ms: 1_000,
- item: {
+ ItemStartedNotification::new(
+ "thread-1".to_string(),
+ "turn-1".to_string(),
+ {
let mut item = sample_command_execution_item(
CommandExecutionStatus::InProgress,
/*exit_code*/ None,
/*duration_ms*/ None,
);
- if let ThreadItem::CommandExecution { model_context, .. } = &mut item {
+ if let ThreadItemPayload::CommandExecution { model_context, .. } =
+ &mut item
+ {
*model_context =
Some(codex_protocol::items::ModelInvocationContext {
model_slug: model.to_string(),
@@ -255,7 +351,8 @@ async fn item_lifecycle_notifications_publish_command_execution_event() {
}
item
},
- },
+ /*started_at_ms*/ 1_000,
+ ),
))),
&mut events,
)
@@ -269,11 +366,10 @@ async fn item_lifecycle_notifications_publish_command_execution_event() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-1".to_string(),
- turn_id: "turn-1".to_string(),
- completed_at_ms: 1_045,
- item: sample_command_execution_item_with_actions(
+ ItemCompletedNotification::new(
+ "thread-1".to_string(),
+ "turn-1".to_string(),
+ sample_command_execution_item_with_actions(
CommandExecutionStatus::Completed,
Some(0),
Some(42),
@@ -300,7 +396,9 @@ async fn item_lifecycle_notifications_publish_command_execution_event() {
Some("scripts/run.py"),
Some(SandboxType::WindowsMxc),
),
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_045,
+ ),
))),
&mut events,
)
@@ -372,7 +470,7 @@ async fn collaborator_tool_events_keep_response_ids_when_completion_races_sampli
&mut events,
)
.await;
- let item = ThreadItem::CollabAgentToolCall {
+ let item = ThreadItemPayload::CollabAgentToolCall {
id: "call-1".into(),
tool: CollabAgentTool::SendMessage,
status: CollabAgentToolCallStatus::Failed,
@@ -386,12 +484,12 @@ async fn collaborator_tool_events_keep_response_ids_when_completion_races_sampli
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-1".into(),
- turn_id: "turn-1".into(),
- started_at_ms: 1_000,
- item: item.clone(),
- },
+ ItemStartedNotification::new(
+ "thread-1".into(),
+ "turn-1".into(),
+ item.clone(),
+ /*started_at_ms*/ 1_000,
+ ),
))),
&mut events,
)
@@ -400,12 +498,13 @@ async fn collaborator_tool_events_keep_response_ids_when_completion_races_sampli
sampling_response("turn-1", "response-1", &["call-1"]),
));
let completion = AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-1".into(),
- turn_id: "turn-1".into(),
- completed_at_ms: 1_010,
+ ItemCompletedNotification::new(
+ "thread-1".into(),
+ "turn-1".into(),
item,
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_010,
+ ),
)));
let facts = if response_first {
[response, completion]
@@ -691,7 +790,7 @@ async fn mcp_elicitation_classification_survives_turn_completion_and_preserves_c
("retry", "calendar", None),
("denied", "drive", Some(ElicitationType::Approval)),
] {
- let item = ThreadItem::McpToolCall {
+ let item = ThreadItemPayload::McpToolCall {
id: item_id.to_string(),
server: "server".to_string(),
tool: "search".to_string(),
@@ -715,12 +814,12 @@ async fn mcp_elicitation_classification_survives_turn_completion_and_preserves_c
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- started_at_ms: 998,
- item: item.clone(),
- },
+ ItemStartedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ item.clone(),
+ /*started_at_ms*/ 998,
+ ),
))),
&mut events,
)
@@ -757,12 +856,13 @@ async fn mcp_elicitation_classification_survives_turn_completion_and_preserves_c
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- completed_at_ms: 1_000,
+ ItemCompletedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
item,
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut events,
)
@@ -872,7 +972,7 @@ async fn image_generation_events_preserve_transparent_background_metadata() {
)
.await;
- let item = ThreadItem::ImageGeneration(ImageGenerationItem {
+ let item = ThreadItemPayload::ImageGeneration(ImageGenerationItem {
id: "image-1".to_string(),
status: status.to_string(),
revised_prompt: None,
@@ -890,12 +990,12 @@ async fn image_generation_events_preserve_transparent_background_metadata() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- started_at_ms: 998,
- item: item.clone(),
- },
+ ItemStartedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ item.clone(),
+ /*started_at_ms*/ 998,
+ ),
))),
&mut out,
)
@@ -903,12 +1003,13 @@ async fn image_generation_events_preserve_transparent_background_metadata() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- completed_at_ms: 1_000,
+ ItemCompletedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
item,
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut out,
)
diff --git a/codex-rs/analytics/src/tests/suite/turns/events_tests.rs b/codex-rs/analytics/src/tests/suite/turns/events_tests.rs
index 959dfe72c471..5a86e9a05828 100644
--- a/codex-rs/analytics/src/tests/suite/turns/events_tests.rs
+++ b/codex-rs/analytics/src/tests/suite/turns/events_tests.rs
@@ -34,7 +34,7 @@ use codex_app_server_protocol::McpToolCallStatus;
use codex_app_server_protocol::PatchApplyStatus;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SubAgentActivityKind;
-use codex_app_server_protocol::ThreadItem;
+use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::ThreadRealtimeStartedNotification;
use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
use codex_app_server_protocol::WebSearchItem;
@@ -102,6 +102,7 @@ fn turn_event_serializes_expected_shape() {
let event = TrackEventRequest::TurnEvent(Box::new(CodexTurnEventRequest {
event_type: "codex_turn_event",
event_params: crate::events::CodexTurnEventParams {
+ critical_path: crate::TurnCriticalPath::Disabled.into(),
thread_id: "thread-2".to_string(),
session_id: "session-thread-2".to_string(),
turn_id: "turn-2".to_string(),
@@ -187,6 +188,14 @@ fn turn_event_serializes_expected_shape() {
r#"{
"event_type": "codex_turn_event",
"event_params": {
+ "critical_path_status": "disabled",
+ "critical_path_duration_ms": null,
+ "critical_path_inference_ms": null,
+ "critical_path_auto_review_ms": null,
+ "critical_path_user_wait_ms": null,
+ "critical_path_tools_ms": null,
+ "critical_path_overhead_ms": null,
+ "critical_path_unknown_ms": null,
"thread_id": "thread-2",
"session_id": "session-thread-2",
"turn_id": "turn-2",
@@ -474,7 +483,7 @@ async fn turn_event_counts_completed_tool_items() {
)
.await;
- let mcp_tool_call_item = |status, duration_ms| ThreadItem::McpToolCall {
+ let mcp_tool_call_item = |status, duration_ms| ThreadItemPayload::McpToolCall {
id: "mcp-1".to_string(),
server: "server".to_string(),
tool: "search".to_string(),
@@ -497,13 +506,13 @@ async fn turn_event_counts_completed_tool_items() {
};
let completed_tool_items = vec![
sample_command_execution_item(CommandExecutionStatus::Completed, Some(0), Some(1)),
- ThreadItem::FileChange {
+ ThreadItemPayload::FileChange {
id: "file-change-1".to_string(),
changes: Vec::new(),
status: PatchApplyStatus::Completed,
},
mcp_tool_call_item(McpToolCallStatus::Completed, Some(2)),
- ThreadItem::DynamicToolCall {
+ ThreadItemPayload::DynamicToolCall {
id: "dynamic-1".to_string(),
// A name collision with code-mode exec must not change the event boundary.
namespace: Some("custom".to_string()),
@@ -514,7 +523,7 @@ async fn turn_event_counts_completed_tool_items() {
success: Some(true),
duration_ms: Some(3),
},
- ThreadItem::CollabAgentToolCall {
+ ThreadItemPayload::CollabAgentToolCall {
id: "collab-1".to_string(),
tool: CollabAgentTool::SpawnAgent,
status: CollabAgentToolCallStatus::Completed,
@@ -525,7 +534,7 @@ async fn turn_event_counts_completed_tool_items() {
reasoning_effort: None,
agents_states: Default::default(),
},
- ThreadItem::SubAgentActivity {
+ ThreadItemPayload::SubAgentActivity {
model: None,
reasoning_effort: None,
id: "sub-agent-activity-1".to_string(),
@@ -533,7 +542,7 @@ async fn turn_event_counts_completed_tool_items() {
agent_thread_id: "thread-child".to_string(),
agent_path: "/root/child".to_string(),
},
- ThreadItem::SubAgentActivity {
+ ThreadItemPayload::SubAgentActivity {
model: None,
reasoning_effort: None,
id: "sub-agent-activity-completed".to_string(),
@@ -541,13 +550,13 @@ async fn turn_event_counts_completed_tool_items() {
agent_thread_id: "thread-child".to_string(),
agent_path: "/root/child".to_string(),
},
- ThreadItem::WebSearch(WebSearchItem {
+ ThreadItemPayload::WebSearch(WebSearchItem {
id: "web-1".to_string(),
query: "codex".to_string(),
action: None,
results: None,
}),
- ThreadItem::ImageGeneration(ImageGenerationItem {
+ ThreadItemPayload::ImageGeneration(ImageGenerationItem {
id: "image-1".to_string(),
status: "completed".to_string(),
revised_prompt: None,
@@ -567,12 +576,12 @@ async fn turn_event_counts_completed_tool_items() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- started_at_ms: 998,
- item: item.clone(),
- },
+ ItemStartedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
+ item.clone(),
+ /*started_at_ms*/ 998,
+ ),
))),
&mut out,
)
@@ -583,12 +592,13 @@ async fn turn_event_counts_completed_tool_items() {
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: "thread-2".to_string(),
- turn_id: "turn-2".to_string(),
- completed_at_ms: 1_000,
+ ItemCompletedNotification::new(
+ "thread-2".to_string(),
+ "turn-2".to_string(),
item,
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_000,
+ ),
))),
&mut out,
)
@@ -957,3 +967,107 @@ async fn turn_completed_without_started_notification_emits_null_started_at() {
);
assert_eq!(payload["event_params"]["total_tokens"], json!(null));
}
+
+#[tokio::test]
+async fn critical_path_payload_preserves_capture_status_and_emits_once() {
+ use crate::TurnCriticalPath;
+ use crate::TurnCriticalPathDurations;
+ use crate::TurnProfileFact;
+ use crate::tests::support::sample_turn_profile;
+
+ for status in [
+ AppServerTurnStatus::Completed,
+ AppServerTurnStatus::Interrupted,
+ ] {
+ for (critical_path, expected) in [
+ (
+ TurnCriticalPath::Disabled,
+ json!(["disabled", null, null, null, null, null, null, null]),
+ ),
+ (
+ TurnCriticalPath::Incomplete,
+ json!(["incomplete", null, null, null, null, null, null, null]),
+ ),
+ (
+ TurnCriticalPath::Complete(TurnCriticalPathDurations {
+ duration_ms: 1234,
+ inference_ms: 700,
+ auto_review_ms: 200,
+ user_wait_ms: 100,
+ tools_ms: 150,
+ overhead_ms: 80,
+ unknown_ms: 4,
+ }),
+ json!(["complete", 1234, 700, 200, 100, 150, 80, 4]),
+ ),
+ (
+ TurnCriticalPath::Complete(TurnCriticalPathDurations {
+ duration_ms: 1234,
+ inference_ms: 1234,
+ auto_review_ms: 0,
+ user_wait_ms: 0,
+ tools_ms: 0,
+ overhead_ms: 0,
+ unknown_ms: 0,
+ }),
+ json!(["complete", 1234, 1234, 0, 0, 0, 0, 0]),
+ ),
+ ] {
+ let mut reducer = AnalyticsReducer::default();
+ let mut out = Vec::new();
+ ingest_turn_prerequisites(
+ &mut reducer,
+ &mut out,
+ /*include_initialize*/ true,
+ /*include_resolved_config*/ true,
+ /*include_started*/ true,
+ /*include_token_usage*/ true,
+ )
+ .await;
+ let mut profile = sample_turn_profile();
+ profile.critical_path = critical_path;
+ reducer
+ .ingest(
+ AnalyticsFact::Custom(CustomAnalyticsFact::TurnProfile(Box::new(
+ TurnProfileFact {
+ turn_id: "turn-2".into(),
+ profile,
+ },
+ ))),
+ &mut out,
+ )
+ .await;
+ for _ in 0..2 {
+ reducer
+ .ingest(
+ AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
+ "thread-2",
+ "turn-2",
+ status.clone(),
+ /*codex_error_info*/ None,
+ ))),
+ &mut out,
+ )
+ .await;
+ }
+ assert_eq!(out.len(), 1);
+ let payload = serde_json::to_value(&out[0]).unwrap();
+ let params = &payload["event_params"];
+ assert_eq!(
+ json!([
+ params["critical_path_status"],
+ params["critical_path_duration_ms"],
+ params["critical_path_inference_ms"],
+ params["critical_path_auto_review_ms"],
+ params["critical_path_user_wait_ms"],
+ params["critical_path_tools_ms"],
+ params["critical_path_overhead_ms"],
+ params["critical_path_unknown_ms"],
+ ]),
+ expected
+ );
+ assert_eq!(params["tool_blocking_ms"], json!(250));
+ assert_eq!(params["duration_ms"], json!(1234));
+ }
+ }
+}
diff --git a/codex-rs/analytics/src/tests/support.rs b/codex-rs/analytics/src/tests/support.rs
index e32fe28aca58..c47593d50d9c 100644
--- a/codex-rs/analytics/src/tests/support.rs
+++ b/codex-rs/analytics/src/tests/support.rs
@@ -31,7 +31,7 @@ use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionSource as AppServerSessionSource;
use codex_app_server_protocol::Thread;
-use codex_app_server_protocol::ThreadItem;
+use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadSource as AppServerThreadSource;
use codex_app_server_protocol::ThreadStartResponse;
@@ -374,6 +374,7 @@ pub(super) fn sample_turn_resolved_config(
pub(super) fn sample_turn_profile() -> TurnProfile {
TurnProfile {
+ critical_path: crate::TurnCriticalPath::Disabled,
before_first_sampling_ms: 100,
sampling_ms: 700,
compaction_ms: 40,
@@ -545,17 +546,17 @@ pub(super) async fn ingest_completed_command_execution_item(
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemStarted(
- ItemStartedNotification {
- thread_id: thread_id.to_string(),
- turn_id: "turn-1".to_string(),
- started_at_ms: 1_000,
- item: sample_command_execution_item_with_id(
+ ItemStartedNotification::new(
+ thread_id.to_string(),
+ "turn-1".to_string(),
+ sample_command_execution_item_with_id(
item_id,
CommandExecutionStatus::InProgress,
/*exit_code*/ None,
/*duration_ms*/ None,
),
- },
+ /*started_at_ms*/ 1_000,
+ ),
))),
events,
)
@@ -563,17 +564,18 @@ pub(super) async fn ingest_completed_command_execution_item(
reducer
.ingest(
AnalyticsFact::Notification(Box::new(ServerNotification::ItemCompleted(
- ItemCompletedNotification {
- thread_id: thread_id.to_string(),
- turn_id: "turn-1".to_string(),
- completed_at_ms: 1_042,
- item: sample_command_execution_item_with_id(
+ ItemCompletedNotification::new(
+ thread_id.to_string(),
+ "turn-1".to_string(),
+ sample_command_execution_item_with_id(
item_id,
CommandExecutionStatus::Completed,
Some(0),
Some(42),
),
- },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 1_042,
+ ),
))),
events,
)
@@ -640,7 +642,7 @@ pub(super) fn sample_command_execution_item(
status: CommandExecutionStatus,
exit_code: Option,
duration_ms: Option,
-) -> ThreadItem {
+) -> ThreadItemPayload {
sample_command_execution_item_with_id("item-1", status, exit_code, duration_ms)
}
@@ -649,8 +651,8 @@ pub(super) fn sample_command_execution_item_with_id(
status: CommandExecutionStatus,
exit_code: Option,
duration_ms: Option,
-) -> ThreadItem {
- ThreadItem::CommandExecution {
+) -> ThreadItemPayload {
+ ThreadItemPayload::CommandExecution {
model_context: None,
sandbox_type: None,
id: id.to_string(),
diff --git a/codex-rs/analytics/src/turn_critical_path.rs b/codex-rs/analytics/src/turn_critical_path.rs
new file mode 100644
index 000000000000..ef8e4738d807
--- /dev/null
+++ b/codex-rs/analytics/src/turn_critical_path.rs
@@ -0,0 +1,59 @@
+//! Turn critical-path facts and their nullable analytics fields.
+//! Missing capture never becomes a zero-duration measurement.
+
+use serde::Serialize;
+
+/// Capture coverage for one finalized turn, independent of its completion status.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
+pub enum TurnCriticalPath {
+ #[default]
+ Disabled,
+ Incomplete,
+ Complete(TurnCriticalPathDurations),
+}
+
+/// Category totals over the server turn interval, truncated to milliseconds.
+/// Sampling and compaction both contribute to inference. Independent truncation
+/// can leave the category sum up to five milliseconds below duration_ms.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub struct TurnCriticalPathDurations {
+ pub duration_ms: u64,
+ pub inference_ms: u64,
+ pub auto_review_ms: u64,
+ pub user_wait_ms: u64,
+ pub tools_ms: u64,
+ pub overhead_ms: u64,
+ pub unknown_ms: u64,
+}
+
+#[derive(Serialize)]
+pub(crate) struct CriticalPathEventParams {
+ critical_path_status: &'static str,
+ critical_path_duration_ms: Option,
+ critical_path_inference_ms: Option,
+ critical_path_auto_review_ms: Option,
+ critical_path_user_wait_ms: Option,
+ critical_path_tools_ms: Option,
+ critical_path_overhead_ms: Option,
+ critical_path_unknown_ms: Option,
+}
+
+impl From for CriticalPathEventParams {
+ fn from(path: TurnCriticalPath) -> Self {
+ let (status, durations) = match path {
+ TurnCriticalPath::Disabled => ("disabled", None),
+ TurnCriticalPath::Incomplete => ("incomplete", None),
+ TurnCriticalPath::Complete(durations) => ("complete", Some(durations)),
+ };
+ Self {
+ critical_path_status: status,
+ critical_path_duration_ms: durations.as_ref().map(|value| value.duration_ms),
+ critical_path_inference_ms: durations.as_ref().map(|value| value.inference_ms),
+ critical_path_auto_review_ms: durations.as_ref().map(|value| value.auto_review_ms),
+ critical_path_user_wait_ms: durations.as_ref().map(|value| value.user_wait_ms),
+ critical_path_tools_ms: durations.as_ref().map(|value| value.tools_ms),
+ critical_path_overhead_ms: durations.as_ref().map(|value| value.overhead_ms),
+ critical_path_unknown_ms: durations.as_ref().map(|value| value.unknown_ms),
+ }
+ }
+}
diff --git a/codex-rs/app-server-client/Cargo.toml b/codex-rs/app-server-client/Cargo.toml
index daa6ef661f00..272173b83a7f 100644
--- a/codex-rs/app-server-client/Cargo.toml
+++ b/codex-rs/app-server-client/Cargo.toml
@@ -15,6 +15,7 @@ workspace = true
[dependencies]
codex-app-server = { workspace = true }
codex-app-server-protocol = { workspace = true }
+codex-app-server-transport = { workspace = true }
codex-arg0 = { workspace = true }
codex-config = { workspace = true }
codex-core = { workspace = true }
diff --git a/codex-rs/app-server-client/src/lib.rs b/codex-rs/app-server-client/src/lib.rs
index e038e439f691..e7df794ea809 100644
--- a/codex-rs/app-server-client/src/lib.rs
+++ b/codex-rs/app-server-client/src/lib.rs
@@ -23,6 +23,7 @@
mod cloud;
mod path;
mod remote;
+mod remote_websocket;
pub use cloud::CloudClient;
pub use cloud::CloudCommand;
pub use cloud::CloudOperation;
@@ -1134,19 +1135,22 @@ mod tests {
}
fn item_completed_notification(text: &str) -> ServerNotification {
- ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification {
- thread_id: "thread".to_string(),
- turn_id: "turn".to_string(),
- completed_at_ms: 0,
- item: codex_app_server_protocol::ThreadItem::AgentMessage {
- id: "item".to_string(),
- text: text.to_string(),
- phase: None,
- memory_citation: None,
- delivery: None,
- questions: None,
- },
- })
+ ServerNotification::ItemCompleted(
+ codex_app_server_protocol::ItemCompletedNotification::new(
+ "thread".to_string(),
+ "turn".to_string(),
+ codex_app_server_protocol::ThreadItemPayload::AgentMessage {
+ id: "item".to_string(),
+ text: text.to_string(),
+ phase: None,
+ memory_citation: None,
+ delivery: None,
+ questions: None,
+ },
+ /*started_at_ms*/ None,
+ /*completed_at_ms*/ 0,
+ ),
+ )
}
fn turn_completed_notification() -> ServerNotification {
@@ -1882,8 +1886,8 @@ mod tests {
}
ServerNotification::ItemCompleted(notification)
if matches!(
- ¬ification.item,
- codex_app_server_protocol::ThreadItem::AgentMessage { text, .. }
+ ¬ification.item.payload,
+ codex_app_server_protocol::ThreadItemPayload::AgentMessage { text, .. }
if text == "hello"
) =>
{
diff --git a/codex-rs/app-server-client/src/remote.rs b/codex-rs/app-server-client/src/remote.rs
index c5d9b0321f05..ad8d28bceced 100644
--- a/codex-rs/app-server-client/src/remote.rs
+++ b/codex-rs/app-server-client/src/remote.rs
@@ -22,6 +22,8 @@ use crate::RequestResult;
use crate::SHUTDOWN_TIMEOUT;
use crate::TypedRequestError;
use crate::decode_typed_response;
+use crate::remote_websocket::RemoteWebSocket;
+use crate::remote_websocket::WebSocketHandshake;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientNotification;
use codex_app_server_protocol::ClientRequest;
@@ -37,11 +39,11 @@ use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::Result as JsonRpcResult;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
+use codex_app_server_transport::WEBSOCKET_COMPRESSION_HEADER;
+use codex_app_server_transport::ZSTD_STREAM_PROTOCOL;
use codex_uds::UnixStream;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
-use futures::SinkExt;
-use futures::StreamExt;
use serde::de::DeserializeOwned;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
@@ -50,12 +52,12 @@ use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio::time::timeout;
use tokio_tungstenite::MaybeTlsStream;
-use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::client_async_with_config;
use tokio_tungstenite::connect_async_with_config;
use tokio_tungstenite::tungstenite::Error as TungsteniteError;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
+use tokio_tungstenite::tungstenite::handshake::client::Request;
use tokio_tungstenite::tungstenite::http::HeaderValue;
use tokio_tungstenite::tungstenite::http::header::AUTHORIZATION;
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
@@ -239,13 +241,14 @@ impl RemoteAppServerClient {
async fn connect_with_stream(
channel_capacity: usize,
endpoint: String,
- stream: WebSocketStream,
+ stream: WebSocketHandshake,
initialize_params: InitializeParams,
) -> IoResult
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
- let mut stream = stream;
+ let mut stream =
+ RemoteWebSocket::from_handshake(stream, REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE)?;
let (pending_events, metadata) = initialize_remote_connection(
&mut stream,
&endpoint,
@@ -264,7 +267,7 @@ impl RemoteAppServerClient {
tokio::select! {
command = command_rx.recv() => {
let Some(command) = command else {
- let _ = stream.close(None).await;
+ let _ = stream.close().await;
break;
};
match command {
@@ -346,7 +349,7 @@ impl RemoteAppServerClient {
let _ = response_tx.send(result);
}
RemoteClientCommand::Shutdown { response_tx } => {
- let close_result = stream.close(None).await.or_else(|err| {
+ let close_result = stream.close().await.or_else(|err| {
if websocket_close_error_is_already_closed(&err) {
Ok(())
} else {
@@ -719,7 +722,7 @@ impl RemoteAppServerRequestHandle {
async fn connect_websocket_endpoint(
websocket_url: String,
auth_token: Option,
-) -> IoResult<(String, WebSocketStream>)> {
+) -> IoResult<(String, WebSocketHandshake>)> {
let url = Url::parse(&websocket_url).map_err(|err| {
IoError::new(
ErrorKind::InvalidInput,
@@ -735,7 +738,7 @@ async fn connect_websocket_endpoint(
));
}
- let mut request = url.as_str().into_client_request().map_err(|err| {
+ let mut request = websocket_request(url.as_str()).map_err(|err| {
IoError::new(
ErrorKind::InvalidInput,
format!("invalid websocket URL `{websocket_url}`: {err}"),
@@ -769,7 +772,6 @@ async fn connect_websocket_endpoint(
format!("timed out connecting to remote app server at `{websocket_url}`"),
)
})?
- .map(|(stream, _response)| stream)
.map_err(|err| {
IoError::other(format!(
"failed to connect to remote app server at `{websocket_url}`: {err}"
@@ -782,16 +784,14 @@ async fn connect_websocket_endpoint(
async fn connect_unix_socket_endpoint(
socket_path: AbsolutePathBuf,
peer_policy: SocketPeerPolicy,
-) -> IoResult<(String, WebSocketStream)> {
+) -> IoResult<(String, WebSocketHandshake)> {
let endpoint = format!("unix://{}", socket_path.display());
- let request = UDS_WEBSOCKET_HANDSHAKE_URL
- .into_client_request()
- .map_err(|err| {
- IoError::new(
- ErrorKind::InvalidInput,
- format!("invalid UDS websocket handshake URL: {err}"),
- )
- })?;
+ let request = websocket_request(UDS_WEBSOCKET_HANDSHAKE_URL).map_err(|err| {
+ IoError::new(
+ ErrorKind::InvalidInput,
+ format!("invalid UDS websocket handshake URL: {err}"),
+ )
+ })?;
let stream = timeout(CONNECT_TIMEOUT, UnixStream::connect(socket_path.as_path()))
.await
.map_err(|_| {
@@ -822,7 +822,6 @@ async fn connect_unix_socket_endpoint(
format!("timed out upgrading remote app server at `{endpoint}`"),
)
})?
- .map(|(stream, _response)| stream)
.map_err(|err| {
IoError::other(format!(
"failed to upgrade remote app server at `{endpoint}`: {err}"
@@ -832,6 +831,15 @@ async fn connect_unix_socket_endpoint(
Ok((endpoint, stream))
}
+fn websocket_request(url: &str) -> Result {
+ let mut request = url.into_client_request()?;
+ request.headers_mut().insert(
+ WEBSOCKET_COMPRESSION_HEADER,
+ HeaderValue::from_static(ZSTD_STREAM_PROTOCOL),
+ );
+ Ok(request)
+}
+
fn remote_websocket_config() -> WebSocketConfig {
WebSocketConfig::default()
.max_frame_size(Some(REMOTE_APP_SERVER_MAX_WEBSOCKET_MESSAGE_SIZE))
@@ -839,7 +847,7 @@ fn remote_websocket_config() -> WebSocketConfig {
}
async fn initialize_remote_connection(
- stream: &mut WebSocketStream,
+ stream: &mut RemoteWebSocket,
endpoint: &str,
params: InitializeParams,
initialize_timeout: Duration,
@@ -1030,7 +1038,7 @@ fn jsonrpc_notification_from_client_notification(
}
async fn write_jsonrpc_message(
- stream: &mut WebSocketStream,
+ stream: &mut RemoteWebSocket,
message: JSONRPCMessage,
endpoint: &str,
) -> IoResult<()>
@@ -1038,14 +1046,11 @@ where
S: AsyncRead + AsyncWrite + Unpin,
{
let payload = serde_json::to_string(&message).map_err(IoError::other)?;
- stream
- .send(Message::Text(payload.into()))
- .await
- .map_err(|err| {
- IoError::other(format!(
- "failed to write websocket message to `{endpoint}`: {err}"
- ))
- })
+ stream.send_text(payload).await.map_err(|err| {
+ IoError::other(format!(
+ "failed to write websocket message to `{endpoint}`: {err}"
+ ))
+ })
}
fn websocket_close_error_is_already_closed(err: &TungsteniteError) -> bool {
diff --git a/codex-rs/app-server-client/src/remote_websocket.rs b/codex-rs/app-server-client/src/remote_websocket.rs
new file mode 100644
index 000000000000..44fac9537732
--- /dev/null
+++ b/codex-rs/app-server-client/src/remote_websocket.rs
@@ -0,0 +1,120 @@
+//! Negotiated WebSocket compression. Codec history lasts for the connection.
+
+use std::io;
+
+use codex_app_server_transport::CompressionConnection;
+use codex_app_server_transport::WEBSOCKET_COMPRESSION_HEADER;
+use codex_app_server_transport::WebSocketCompression;
+use codex_app_server_transport::ZstdDecoder;
+use codex_app_server_transport::ZstdEncoder;
+use codex_app_server_transport::zstd_codec_pair;
+use futures::SinkExt;
+use futures::StreamExt;
+use tokio::io::AsyncRead;
+use tokio::io::AsyncWrite;
+use tokio_tungstenite::WebSocketStream;
+use tokio_tungstenite::tungstenite::Bytes;
+use tokio_tungstenite::tungstenite::Error;
+use tokio_tungstenite::tungstenite::Message;
+use tokio_tungstenite::tungstenite::handshake::client::Response;
+use tokio_tungstenite::tungstenite::http::HeaderValue;
+use tokio_tungstenite::tungstenite::protocol::frame::Frame;
+use tokio_tungstenite::tungstenite::protocol::frame::coding::Data;
+use tokio_tungstenite::tungstenite::protocol::frame::coding::OpCode;
+
+// Match the listener's default incoming frame limit when no smaller cap is advertised.
+const DEFAULT_MAX_FRAME_BYTES: usize = 16 << 20;
+const MAX_UNFRAGMENTED_MESSAGE_BYTES_HEADER: &str =
+ "x-codex-websocket-max-unfragmented-message-bytes";
+
+pub(crate) type WebSocketHandshake = (WebSocketStream, Response);
+
+pub(crate) struct RemoteWebSocket {
+ stream: WebSocketStream,
+ codec: Option<(ZstdEncoder, ZstdDecoder)>,
+ max_frame_bytes: usize,
+ write_failed: bool,
+}
+
+impl RemoteWebSocket {
+ pub(crate) fn from_handshake(
+ (stream, response): WebSocketHandshake,
+ max_message_size: usize,
+ ) -> io::Result {
+ let headers = response.headers();
+ let header = headers
+ .get(WEBSOCKET_COMPRESSION_HEADER)
+ .map(HeaderValue::to_str)
+ .transpose()
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+ let codec = match WebSocketCompression::from_response_header(header)? {
+ WebSocketCompression::Plain => None,
+ WebSocketCompression::Zstd => Some(zstd_codec_pair(
+ max_message_size,
+ CompressionConnection::NativeClient,
+ )?),
+ };
+ let max_frame_bytes = headers
+ .get(MAX_UNFRAGMENTED_MESSAGE_BYTES_HEADER)
+ .and_then(|value| value.to_str().ok())
+ .and_then(|value| value.parse::().ok())
+ .filter(|value| *value > 0)
+ .unwrap_or(DEFAULT_MAX_FRAME_BYTES)
+ .min(DEFAULT_MAX_FRAME_BYTES);
+ Ok(Self {
+ stream,
+ codec,
+ max_frame_bytes,
+ write_failed: false,
+ })
+ }
+
+ pub(crate) async fn next(&mut self) -> Option> {
+ if self.write_failed {
+ return Some(Err(Error::Io(io::ErrorKind::BrokenPipe.into())));
+ }
+ let message = self.stream.next().await?;
+ Some(match (&mut self.codec, message) {
+ (Some((_, decoder)), Ok(Message::Binary(bytes))) => decoder
+ .decode(&bytes)
+ .map(|text| Message::Text(text.into()))
+ .map_err(Error::Io),
+ (Some(_), Ok(Message::Text(_))) => Err(Error::Io(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "expected compressed binary app-server message",
+ ))),
+ (_, message) => message,
+ })
+ }
+
+ pub(crate) async fn send_text(&mut self, text: String) -> Result<(), Error> {
+ if self.write_failed {
+ return Err(Error::Io(io::ErrorKind::BrokenPipe.into()));
+ }
+ let result = async {
+ let Some((encoder, _)) = &mut self.codec else {
+ return self.stream.send(Message::Text(text.into())).await;
+ };
+ let mut bytes = Bytes::from(encoder.encode(&text).map_err(Error::Io)?);
+ if bytes.len() <= self.max_frame_bytes {
+ return self.stream.send(Message::Binary(bytes)).await;
+ }
+ let mut opcode = OpCode::Data(Data::Binary);
+ while !bytes.is_empty() {
+ let chunk = bytes.split_to(bytes.len().min(self.max_frame_bytes));
+ let frame = Frame::message(chunk, opcode, bytes.is_empty());
+ self.stream.feed(Message::Frame(frame)).await?;
+ opcode = OpCode::Data(Data::Continue);
+ }
+ self.stream.flush().await
+ }
+ .await;
+ // A failed write may leave the compression history ahead of the receiver.
+ self.write_failed = result.is_err();
+ result
+ }
+
+ pub(crate) async fn close(&mut self) -> Result<(), Error> {
+ self.stream.close(/*msg*/ None).await
+ }
+}
diff --git a/codex-rs/app-server-client/tests/remote_compression.rs b/codex-rs/app-server-client/tests/remote_compression.rs
new file mode 100644
index 000000000000..cbab159c1d7d
--- /dev/null
+++ b/codex-rs/app-server-client/tests/remote_compression.rs
@@ -0,0 +1,115 @@
+//! Exercise a negotiated compressed session through the remote client API.
+
+use std::error::Error;
+
+use codex_app_server_client::AppServerEvent;
+use codex_app_server_client::RemoteAppServerClient;
+use codex_app_server_client::RemoteAppServerConnectArgs;
+use codex_app_server_client::RemoteAppServerEndpoint;
+use codex_app_server_protocol::JSONRPCRequest;
+use codex_app_server_protocol::RequestId;
+use codex_app_server_transport::WEBSOCKET_COMPRESSION_HEADER;
+use codex_app_server_transport::ZSTD_STREAM_PROTOCOL;
+use codex_app_server_transport::ZstdDecoder;
+use codex_app_server_transport::ZstdEncoder;
+use codex_uds::UnixListener;
+use codex_utils_absolute_path::AbsolutePathBuf;
+use futures::SinkExt;
+use futures::StreamExt;
+use pretty_assertions::assert_eq;
+use serde_json::Value;
+use serde_json::json;
+use tempfile::TempDir;
+use tokio_tungstenite::accept_hdr_async_with_config;
+use tokio_tungstenite::tungstenite::Message;
+use tokio_tungstenite::tungstenite::handshake::server::Request;
+use tokio_tungstenite::tungstenite::handshake::server::Response;
+use tokio_tungstenite::tungstenite::http::HeaderValue;
+use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
+
+#[tokio::test]
+async fn compressed_session_negotiates_fragments_and_retains_history() -> Result<(), Box>
+{
+ const FRAME_LIMIT: usize = 16;
+ let dir = TempDir::new()?;
+ let socket_path = AbsolutePathBuf::from_absolute_path(dir.path().join("codex.sock"))?;
+ let mut listener = UnixListener::bind(socket_path.as_path()).await?;
+ let server = async {
+ let stream = listener.accept().await?;
+ let mut websocket = accept_hdr_async_with_config(
+ stream,
+ |request: &Request, mut response: Response| {
+ assert_eq!(
+ request.headers()[WEBSOCKET_COMPRESSION_HEADER],
+ ZSTD_STREAM_PROTOCOL
+ );
+ response.headers_mut().insert(
+ WEBSOCKET_COMPRESSION_HEADER,
+ HeaderValue::from_static(ZSTD_STREAM_PROTOCOL),
+ );
+ response.headers_mut().insert(
+ "x-codex-websocket-max-unfragmented-message-bytes",
+ HeaderValue::from(FRAME_LIMIT),
+ );
+ Ok(response)
+ },
+ Some(WebSocketConfig::default().max_frame_size(Some(FRAME_LIMIT))),
+ )
+ .await?;
+ let mut decoder = ZstdDecoder::new(/*max_message_size*/ 1024)?;
+ let mut encoder = ZstdEncoder::new()?;
+ for expected_method in ["initialize", "initialized", "echo"] {
+ let Message::Binary(bytes) = websocket.next().await.ok_or("connection closed")?? else {
+ return Err("expected compressed message".into());
+ };
+ if expected_method == "initialize" {
+ assert!(
+ bytes.len() > FRAME_LIMIT,
+ "initialize must require fragmentation"
+ );
+ }
+ let request: Value = serde_json::from_str(&decoder.decode(&bytes)?)?;
+ assert_eq!(request["method"], expected_method);
+ if expected_method != "initialized" {
+ let response = json!({"id": request["id"], "result": {"ok": true}}).to_string();
+ websocket
+ .send(Message::Binary(encoder.encode(&response)?.into()))
+ .await?;
+ }
+ }
+ websocket.send(Message::Text("{}".into())).await?;
+ Ok::<(), Box>(())
+ };
+ let client = async {
+ let mut client = RemoteAppServerClient::connect(RemoteAppServerConnectArgs {
+ endpoint: RemoteAppServerEndpoint::UnixSocket { socket_path },
+ client_name: "compression-test".to_string(),
+ client_version: "0.0.0".to_string(),
+ experimental_api: false,
+ mcp_server_openai_form_elicitation: false,
+ opt_out_notification_methods: Vec::new(),
+ channel_capacity: 8,
+ })
+ .await?;
+ assert_eq!(
+ client
+ .request_handle()
+ .request_json_rpc(JSONRPCRequest {
+ id: RequestId::Integer(1),
+ method: "echo".to_string(),
+ params: None,
+ trace: None,
+ })
+ .await?,
+ Ok(json!({"ok": true}))
+ );
+ let Some(AppServerEvent::Disconnected { message }) = client.next_event().await else {
+ return Err("expected disconnect after uncompressed message".into());
+ };
+ assert!(message.contains("expected compressed binary"));
+ client.shutdown().await?;
+ Ok::<(), Box>(())
+ };
+ tokio::try_join!(server, client)?;
+ Ok(())
+}
diff --git a/codex-rs/app-server-daemon/Cargo.toml b/codex-rs/app-server-daemon/Cargo.toml
index 788f92f69c17..4bd1678402c2 100644
--- a/codex-rs/app-server-daemon/Cargo.toml
+++ b/codex-rs/app-server-daemon/Cargo.toml
@@ -45,6 +45,7 @@ tracing-appender = { workspace = true }
[dev-dependencies]
codex-utils-cargo-bin = { workspace = true }
pretty_assertions = { workspace = true }
+tokio = { workspace = true, features = ["test-util"] }
tracing-subscriber = { workspace = true }
[target.'cfg(windows)'.dependencies]
diff --git a/codex-rs/app-server-daemon/src/backend/mod.rs b/codex-rs/app-server-daemon/src/backend/mod.rs
index 1d2f4ecfacef..1c2ea755e939 100644
--- a/codex-rs/app-server-daemon/src/backend/mod.rs
+++ b/codex-rs/app-server-daemon/src/backend/mod.rs
@@ -10,6 +10,7 @@ use std::path::PathBuf;
use serde::Serialize;
pub(crate) use pid::PidBackend;
+pub(crate) use pid::wait_for_process_exit;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
diff --git a/codex-rs/app-server-daemon/src/backend/pid.rs b/codex-rs/app-server-daemon/src/backend/pid.rs
index 60e793e18d76..97738dd8e24f 100644
--- a/codex-rs/app-server-daemon/src/backend/pid.rs
+++ b/codex-rs/app-server-daemon/src/backend/pid.rs
@@ -1,4 +1,5 @@
//! PID reservations serialize detached launches; process identities protect stale-record cleanup.
+//! PID probes explicitly unlock so concurrent forks cannot retain their transient reservations.
#[path = "pid_identity.rs"]
mod identity;
@@ -460,6 +461,20 @@ impl PidBackend {
}
}
+/// Wait for a published process to exit without modifying its PID reservation.
+pub(crate) async fn wait_for_process_exit(pid_file: &Path) {
+ loop {
+ // A missing or empty record can belong to a launch still publishing its PID.
+ if let Ok(contents) = fs::read(pid_file).await
+ && let Ok(record) = serde_json::from_slice::(&contents)
+ && matches!(process_matches_record(&record).await, Ok(false))
+ {
+ return;
+ }
+ sleep(crate::START_POLL_INTERVAL).await;
+ }
+}
+
pub(crate) async fn read_stderr_log_tail(pid_file: &Path) -> Result