diff --git a/.bazelrc b/.bazelrc index ce7c1e1d43..b9275426fa 100644 --- a/.bazelrc +++ b/.bazelrc @@ -20,9 +20,6 @@ common:windows --host_platform=//:local_windows common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False common --@llvm//config:experimental_stub_libgcc_s -# We need to use the sh toolchain on windows so we don't send host bash paths to the linux executor. -common:windows --@rules_rust//rust/settings:experimental_use_sh_toolchain_for_bootstrap_process_wrapper - # TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec... # common --@rules_rust//rust/settings:pipelined_compilation @@ -75,6 +72,16 @@ common:ci --disk_cache= common:ci-bazel --config=ci common:ci-bazel --build_metadata=TAG_workflow=bazel +# Shared config for Bazel-backed Rust linting. +build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect +build:clippy --output_groups=+clippy_checks +build:clippy --@rules_rust//rust/settings:clippy.toml=//codex-rs:clippy.toml + +# Shared config for Bazel-backed argument-comment-lint. +build:argument-comment-lint --aspects=//tools/argument-comment-lint:lint_aspect.bzl%rust_argument_comment_lint_aspect +build:argument-comment-lint --output_groups=argument_comment_lint_checks +build:argument-comment-lint --@rules_rust//rust/toolchain/channel=nightly + # Rearrange caches on Windows so they're on the same volume as the checkout. common:ci-windows --config=ci-bazel common:ci-windows --build_metadata=TAG_os=windows diff --git a/.codespellrc b/.codespellrc index 4f3067ed76..87e3468c66 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,4 +3,4 @@ skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new,*meriyah.umd.min.js check-hidden = true ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b -ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE +ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE,PASE,SEH diff --git a/.codex/skills/babysit-pr/SKILL.md b/.codex/skills/babysit-pr/SKILL.md index 4e44563c7b..303bb030fa 100644 --- a/.codex/skills/babysit-pr/SKILL.md +++ b/.codex/skills/babysit-pr/SKILL.md @@ -29,14 +29,15 @@ Accept any of the following: 4. If `diagnose_ci_failure` is present, inspect failed run logs and classify the failure. 5. If the failure is likely caused by the current branch, patch code locally, commit, and push. 6. If `process_review_comment` is present, inspect surfaced review items and decide whether to address them. -7. If a review item is actionable and correct, patch code locally, commit, and push. -8. If the failure is likely flaky/unrelated and `retry_failed_checks` is present, rerun failed jobs with `--retry-failed-now`. -9. If both actionable review feedback and `retry_failed_checks` are present, prioritize review feedback first; a new commit will retrigger CI, so avoid rerunning flaky checks on the old SHA unless you intentionally defer the review change. -10. On every loop, verify mergeability / merge-conflict status (for example via `gh pr view`) in addition to CI and review state. -11. After any push or rerun action, immediately return to step 1 and continue polling on the updated SHA/state. -12. If you had been using `--watch` before pausing to patch/commit/push, relaunch `--watch` yourself in the same turn immediately after the push (do not wait for the user to re-invoke the skill). -13. Repeat polling until the PR is green + review-clean + mergeable, `stop_pr_closed` appears, or a user-help-required blocker is reached. -14. Maintain terminal/session ownership: while babysitting is active, keep consuming watcher output in the same turn; do not leave a detached `--watch` process running and then end the turn as if monitoring were complete. +7. If a review item is actionable and correct, patch code locally, commit, push, and then mark the associated review thread/comment as resolved once the fix is on GitHub. +8. If a review item from another author is non-actionable, already addressed, or not valid, post one reply on the comment/thread explaining that decision (for example answering the question or explaining why no change is needed). If the watcher later surfaces your own reply, treat that self-authored item as already handled and do not reply again. +9. If the failure is likely flaky/unrelated and `retry_failed_checks` is present, rerun failed jobs with `--retry-failed-now`. +10. If both actionable review feedback and `retry_failed_checks` are present, prioritize review feedback first; a new commit will retrigger CI, so avoid rerunning flaky checks on the old SHA unless you intentionally defer the review change. +11. On every loop, verify mergeability / merge-conflict status (for example via `gh pr view`) in addition to CI and review state. +12. After any push or rerun action, immediately return to step 1 and continue polling on the updated SHA/state. +13. If you had been using `--watch` before pausing to patch/commit/push, relaunch `--watch` yourself in the same turn immediately after the push (do not wait for the user to re-invoke the skill). +14. Repeat polling until the PR is green + review-clean + mergeable, `stop_pr_closed` appears, or a user-help-required blocker is reached. +15. Maintain terminal/session ownership: while babysitting is active, keep consuming watcher output in the same turn; do not leave a detached `--watch` process running and then end the turn as if monitoring were complete. ## Commands @@ -94,10 +95,11 @@ When you agree with a comment and it is actionable: 1. Patch code locally. 2. Commit with `codex: address PR review feedback (#)`. 3. Push to the PR head branch. -4. Resume watching on the new SHA immediately (do not stop after reporting the push). -5. If monitoring was running in `--watch` mode, restart `--watch` immediately after the push in the same turn; do not wait for the user to ask again. +4. After the push succeeds, mark the associated GitHub review thread/comment as resolved. +5. Resume watching on the new SHA immediately (do not stop after reporting the push). +6. If monitoring was running in `--watch` mode, restart `--watch` immediately after the push in the same turn; do not wait for the user to ask again. -If you disagree or the comment is non-actionable/already addressed, record it as handled by continuing the watcher loop (the script de-duplicates surfaced items via state after surfacing them). +If you disagree or the comment is non-actionable/already addressed, reply once directly on the GitHub comment/thread so the reviewer gets an explicit answer, then continue the watcher loop. If the watcher later surfaces your own reply because the authenticated operator is treated as a trusted review author, treat that self-authored item as already handled and do not reply again. If a code review comment/thread is already marked as resolved in GitHub, treat it as non-actionable and safely ignore it unless new unresolved follow-up feedback appears. ## Git Safety Rules @@ -124,13 +126,14 @@ Use this loop in a live Codex session: 3. First check whether the PR is now merged or otherwise closed; if so, report that terminal state and stop polling immediately. 4. Check CI summary, new review items, and mergeability/conflict status. 5. Diagnose CI failures and classify branch-related vs flaky/unrelated. -6. Process actionable review comments before flaky reruns when both are present; if a review fix requires a commit, push it and skip rerunning failed checks on the old SHA. -7. Retry failed checks only when `retry_failed_checks` is present and you are not about to replace the current SHA with a review/CI fix commit. -8. If you pushed a commit or triggered a rerun, report the action briefly and continue polling (do not stop). -9. After a review-fix push, proactively restart continuous monitoring (`--watch`) in the same turn unless a strict stop condition has already been reached. -10. If everything is passing, mergeable, not blocked on required review approval, and there are no unaddressed review items, report success and stop. -11. If blocked on a user-help-required issue (infra outage, exhausted flaky retries, unclear reviewer request, permissions), report the blocker and stop. -12. Otherwise sleep according to the polling cadence below and repeat. +6. For each surfaced review item from another author, either reply once with an explanation if it is non-actionable or patch/commit/push and then resolve it if it is actionable. If a later snapshot surfaces your own reply, treat it as informational and continue without responding again. +7. Process actionable review comments before flaky reruns when both are present; if a review fix requires a commit, push it and skip rerunning failed checks on the old SHA. +8. Retry failed checks only when `retry_failed_checks` is present and you are not about to replace the current SHA with a review/CI fix commit. +9. If you pushed a commit, resolved a review thread, replied to a review comment, or triggered a rerun, report the action briefly and continue polling (do not stop). +10. After a review-fix push, proactively restart continuous monitoring (`--watch`) in the same turn unless a strict stop condition has already been reached. +11. If everything is passing, mergeable, not blocked on required review approval, and there are no unaddressed review items, report success and stop. +12. If blocked on a user-help-required issue (infra outage, exhausted flaky retries, unclear reviewer request, permissions), report the blocker and stop. +13. Otherwise sleep according to the polling cadence below and repeat. When the user explicitly asks to monitor/watch/babysit a PR, prefer `--watch` so polling continues autonomously in one command. Use repeated `--once` snapshots only for debugging, local testing, or when the user explicitly asks for a one-shot check. Do not stop to ask the user whether to continue polling; continue autonomously until a strict stop condition is met or the user explicitly interrupts. diff --git a/.github/actions/setup-bazel-ci/action.yml b/.github/actions/setup-bazel-ci/action.yml new file mode 100644 index 0000000000..a1e46d2d04 --- /dev/null +++ b/.github/actions/setup-bazel-ci/action.yml @@ -0,0 +1,133 @@ +name: setup-bazel-ci +description: Prepare a Bazel CI runner with shared caches and optional test prerequisites. +inputs: + target: + description: Target triple used for cache namespacing. + required: true + install-test-prereqs: + description: Install Node.js and DotSlash for Bazel-backed test jobs. + required: false + default: "false" +outputs: + cache-hit: + description: Whether the Bazel repository cache key was restored exactly. + value: ${{ steps.cache_bazel_repository_restore.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Set up Node.js for js_repl tests + if: inputs.install-test-prereqs == 'true' + uses: actions/setup-node@v6 + with: + node-version-file: codex-rs/node-version.txt + + # Some integration tests rely on DotSlash being installed. + # See https://github.com/openai/codex/pull/7617. + - name: Install DotSlash + if: inputs.install-test-prereqs == 'true' + uses: facebook/install-dotslash@v2 + + - name: Make DotSlash available in PATH (Unix) + if: inputs.install-test-prereqs == 'true' && runner.os != 'Windows' + shell: bash + run: cp "$(which dotslash)" /usr/local/bin + + - name: Make DotSlash available in PATH (Windows) + if: inputs.install-test-prereqs == 'true' && runner.os == 'Windows' + shell: pwsh + run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe" + + - name: Set up Bazel + uses: bazelbuild/setup-bazelisk@v3 + + # Restore bazel repository cache so we don't have to redownload all the external dependencies + # on every CI run. + - name: Restore bazel repository cache + id: cache_bazel_repository_restore + uses: actions/cache/restore@v5 + with: + path: | + ~/.cache/bazel-repo-cache + key: bazel-cache-${{ inputs.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} + restore-keys: | + bazel-cache-${{ inputs.target }} + + - name: Configure Bazel output root (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + # Use the shortest available drive to reduce argv/path length issues, + # but avoid the drive root because some Windows test launchers mis-handle + # MANIFEST paths there. + $hasDDrive = Test-Path 'D:\' + $bazelOutputUserRoot = if ($hasDDrive) { 'D:\b' } else { 'C:\b' } + $repoContentsCache = Join-Path $env:RUNNER_TEMP "bazel-repo-contents-cache-$env:GITHUB_RUN_ID-$env:GITHUB_JOB" + "BAZEL_OUTPUT_USER_ROOT=$bazelOutputUserRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "BAZEL_REPO_CONTENTS_CACHE=$repoContentsCache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + if (-not $hasDDrive) { + $repositoryCache = Join-Path $env:USERPROFILE '.cache\bazel-repo-cache' + "BAZEL_REPOSITORY_CACHE=$repositoryCache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + } + + - name: Expose MSVC SDK environment (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + # Bazel exec-side Rust build scripts do not reliably inherit the MSVC developer + # shell on GitHub-hosted Windows runners, so discover the latest VS install and + # ask `VsDevCmd.bat` to materialize the x64/x64 compiler + SDK environment. + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { + throw "vswhere.exe not found" + } + + $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null + if (-not $installPath) { + throw "Could not locate a Visual Studio installation with VC tools" + } + + $vsDevCmd = Join-Path $installPath 'Common7\Tools\VsDevCmd.bat' + if (-not (Test-Path $vsDevCmd)) { + throw "VsDevCmd.bat not found at $vsDevCmd" + } + + # Keep the export surface explicit: these are the paths and SDK roots that the + # MSVC toolchain probes need later when Bazel runs Windows exec-platform build + # scripts such as `aws-lc-sys`. + $varsToExport = @( + 'INCLUDE', + 'LIB', + 'LIBPATH', + 'PATH', + 'UCRTVersion', + 'UniversalCRTSdkDir', + 'VCINSTALLDIR', + 'VCToolsInstallDir', + 'WindowsLibPath', + 'WindowsSdkBinPath', + 'WindowsSdkDir', + 'WindowsSDKLibVersion', + 'WindowsSDKVersion' + ) + + # `VsDevCmd.bat` is a batch file, so invoke it under `cmd.exe`, suppress its + # banner, then dump the resulting environment with `set`. Re-export only the + # approved keys into `GITHUB_ENV` so later steps inherit the same MSVC context. + $envLines = & cmd.exe /c ('"{0}" -no_logo -arch=x64 -host_arch=x64 >nul && set' -f $vsDevCmd) + foreach ($line in $envLines) { + if ($line -notmatch '^(.*?)=(.*)$') { + continue + } + + $name = $matches[1] + $value = $matches[2] + if ($varsToExport -contains $name) { + "$name=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + } + } + + - name: Enable Git long paths (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: git config --global core.longpaths true diff --git a/.github/scripts/run-argument-comment-lint-bazel.sh b/.github/scripts/run-argument-comment-lint-bazel.sh new file mode 100755 index 0000000000..e2f494d620 --- /dev/null +++ b/.github/scripts/run-argument-comment-lint-bazel.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ci_config=ci-linux +case "${RUNNER_OS:-}" in + macOS) + ci_config=ci-macos + ;; + Windows) + ci_config=ci-windows + ;; +esac + +bazel_lint_args=("$@") +if [[ "${RUNNER_OS:-}" == "Windows" ]]; then + has_host_platform_override=0 + for arg in "${bazel_lint_args[@]}"; do + if [[ "$arg" == --host_platform=* ]]; then + has_host_platform_override=1 + break + fi + done + + if [[ $has_host_platform_override -eq 0 ]]; then + # The nightly Windows lint toolchain is registered with an MSVC exec + # platform even though the lint target platform stays on `windows-gnullvm`. + # Override the host platform here so the exec-side helper binaries actually + # match the registered toolchain set. + bazel_lint_args+=("--host_platform=//:local_windows_msvc") + fi + + # Native Windows lint runs need exec-side Rust helper binaries and proc-macros + # to use rust-lld instead of the C++ linker path. The default `none` + # preference resolves to `cc` when a cc_toolchain is present, which currently + # routes these exec actions through clang++ with an argument shape it cannot + # consume. + bazel_lint_args+=("--@rules_rust//rust/settings:toolchain_linker_preference=rust") + + # Some Rust top-level targets are still intentionally incompatible with the + # local Windows MSVC exec platform. Skip those explicit targets so the native + # lint aspect can run across the compatible crate graph instead of failing the + # whole build after analysis. + bazel_lint_args+=("--skip_incompatible_explicit_targets") +fi + +bazel_startup_args=() +if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then + bazel_startup_args+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}") +fi + +run_bazel() { + if [[ "${RUNNER_OS:-}" == "Windows" ]]; then + MSYS2_ARG_CONV_EXCL='*' bazel "$@" + return + fi + + bazel "$@" +} + +run_bazel_with_startup_args() { + if [[ ${#bazel_startup_args[@]} -gt 0 ]]; then + run_bazel "${bazel_startup_args[@]}" "$@" + return + fi + + run_bazel "$@" +} + +read_query_labels() { + local query="$1" + local query_stdout + local query_stderr + query_stdout="$(mktemp)" + query_stderr="$(mktemp)" + + if ! run_bazel_with_startup_args \ + --noexperimental_remote_repo_contents_cache \ + query \ + --keep_going \ + --output=label \ + "$query" >"$query_stdout" 2>"$query_stderr"; then + cat "$query_stderr" >&2 + rm -f "$query_stdout" "$query_stderr" + exit 1 + fi + + cat "$query_stdout" + rm -f "$query_stdout" "$query_stderr" +} + +final_build_targets=(//codex-rs/...) +if [[ "${RUNNER_OS:-}" == "Windows" ]]; then + # Bazel's local Windows platform currently lacks a default test toolchain for + # `rust_test`, so target the concrete Rust crate rules directly. The lint + # aspect still walks their crate graph, which preserves incremental reuse for + # non-test code while avoiding non-Rust wrapper targets such as platform_data. + final_build_targets=() + while IFS= read -r label; do + [[ -n "$label" ]] || continue + final_build_targets+=("$label") + done < <(read_query_labels 'kind("rust_(library|binary|proc_macro) rule", //codex-rs/...)') + + if [[ ${#final_build_targets[@]} -eq 0 ]]; then + echo "Failed to discover Windows Bazel lint targets." >&2 + exit 1 + fi +fi + +./.github/scripts/run-bazel-ci.sh \ + -- \ + build \ + "${bazel_lint_args[@]}" \ + -- \ + "${final_build_targets[@]}" diff --git a/.github/scripts/run-bazel-ci.sh b/.github/scripts/run-bazel-ci.sh new file mode 100755 index 0000000000..d08b32e3d4 --- /dev/null +++ b/.github/scripts/run-bazel-ci.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash + +set -euo pipefail + +print_failed_bazel_test_logs=0 +use_node_test_env=0 +remote_download_toplevel=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --print-failed-test-logs) + print_failed_bazel_test_logs=1 + shift + ;; + --use-node-test-env) + use_node_test_env=1 + shift + ;; + --remote-download-toplevel) + remote_download_toplevel=1 + shift + ;; + --) + shift + break + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +if [[ $# -eq 0 ]]; then + echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] [--remote-download-toplevel] -- -- " >&2 + exit 1 +fi + +bazel_startup_args=() +if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then + bazel_startup_args+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}") +fi + +run_bazel() { + if [[ "${RUNNER_OS:-}" == "Windows" ]]; then + MSYS2_ARG_CONV_EXCL='*' bazel "$@" + return + fi + + bazel "$@" +} + +ci_config=ci-linux +case "${RUNNER_OS:-}" in + macOS) + ci_config=ci-macos + ;; + Windows) + ci_config=ci-windows + ;; +esac + +print_bazel_test_log_tails() { + local console_log="$1" + local testlogs_dir + local -a bazel_info_cmd=(bazel) + + if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_info_cmd+=("${bazel_startup_args[@]}") + fi + + testlogs_dir="$(run_bazel "${bazel_info_cmd[@]:1}" info bazel-testlogs 2>/dev/null || echo bazel-testlogs)" + + local failed_targets=() + while IFS= read -r target; do + failed_targets+=("$target") + done < <( + grep -E '^FAIL: //' "$console_log" \ + | sed -E 's#^FAIL: (//[^ ]+).*#\1#' \ + | sort -u + ) + + if [[ ${#failed_targets[@]} -eq 0 ]]; then + echo "No failed Bazel test targets were found in console output." + return + fi + + for target in "${failed_targets[@]}"; do + local rel_path="${target#//}" + rel_path="${rel_path/:/\/}" + local test_log="${testlogs_dir}/${rel_path}/test.log" + + echo "::group::Bazel test log tail for ${target}" + if [[ -f "$test_log" ]]; then + tail -n 200 "$test_log" + else + echo "Missing test log: $test_log" + fi + echo "::endgroup::" + done +} + +bazel_args=() +bazel_targets=() +found_target_separator=0 +for arg in "$@"; do + if [[ "$arg" == "--" && $found_target_separator -eq 0 ]]; then + found_target_separator=1 + continue + fi + + if [[ $found_target_separator -eq 0 ]]; then + bazel_args+=("$arg") + else + bazel_targets+=("$arg") + fi +done + +if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then + echo "Expected Bazel args and targets separated by --" >&2 + exit 1 +fi + +if [[ $use_node_test_env -eq 1 && "${RUNNER_OS:-}" != "Windows" ]]; then + # Bazel test sandboxes on macOS may resolve an older Homebrew `node` + # before the `actions/setup-node` runtime on PATH. + node_bin="$(which node)" + bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}") +fi + +post_config_bazel_args=() +if [[ $remote_download_toplevel -eq 1 ]]; then + # Override the CI config's remote_download_minimal setting when callers need + # the built artifact to exist on disk after the command completes. + post_config_bazel_args+=(--remote_download_toplevel) +fi + +if [[ -n "${BAZEL_REPO_CONTENTS_CACHE:-}" ]]; then + # Windows self-hosted runners can run multiple Bazel jobs concurrently. Give + # each job its own repo contents cache so they do not fight over the shared + # path configured in `ci-windows`. + post_config_bazel_args+=("--repo_contents_cache=${BAZEL_REPO_CONTENTS_CACHE}") +fi + +if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then + post_config_bazel_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}") +fi + +if [[ "${RUNNER_OS:-}" == "Windows" ]]; then + windows_action_env_vars=( + INCLUDE + LIB + LIBPATH + PATH + UCRTVersion + UniversalCRTSdkDir + VCINSTALLDIR + VCToolsInstallDir + WindowsLibPath + WindowsSdkBinPath + WindowsSdkDir + WindowsSDKLibVersion + WindowsSDKVersion + ) + + for env_var in "${windows_action_env_vars[@]}"; do + if [[ -n "${!env_var:-}" ]]; then + post_config_bazel_args+=("--action_env=${env_var}" "--host_action_env=${env_var}") + fi + done +fi + +bazel_console_log="$(mktemp)" +trap 'rm -f "$bazel_console_log"' EXIT + +bazel_cmd=(bazel) +if (( ${#bazel_startup_args[@]} > 0 )); then + bazel_cmd+=("${bazel_startup_args[@]}") +fi + +if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then + echo "BuildBuddy API key is available; using remote Bazel configuration." + # Work around Bazel 9 remote repo contents cache / overlay materialization failures + # seen in CI (for example "is not a symlink" or permission errors while + # materializing external repos such as rules_perl). We still use BuildBuddy for + # remote execution/cache; this only disables the startup-level repo contents cache. + bazel_run_args=( + "${bazel_args[@]}" + "--config=${ci_config}" + "--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}" + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e +else + echo "BuildBuddy API key is not available; using local Bazel configuration." + # Keep fork/community PRs on Bazel but disable remote services that are + # configured in .bazelrc and require auth. + # + # Flag docs: + # - Command-line reference: https://bazel.build/reference/command-line-reference + # - Remote caching overview: https://bazel.build/remote/caching + # - Remote execution overview: https://bazel.build/remote/rbe + # - Build Event Protocol overview: https://bazel.build/remote/bep + # + # --noexperimental_remote_repo_contents_cache: + # disable remote repo contents cache enabled in .bazelrc startup options. + # https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache + # --remote_cache= and --remote_executor=: + # clear remote cache/execution endpoints configured in .bazelrc. + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache + # https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor + bazel_run_args=( + "${bazel_args[@]}" + --remote_cache= + --remote_executor= + ) + if (( ${#post_config_bazel_args[@]} > 0 )); then + bazel_run_args+=("${post_config_bazel_args[@]}") + fi + set +e + run_bazel "${bazel_cmd[@]:1}" \ + --noexperimental_remote_repo_contents_cache \ + "${bazel_run_args[@]}" \ + -- \ + "${bazel_targets[@]}" \ + 2>&1 | tee "$bazel_console_log" + bazel_status=${PIPESTATUS[0]} + set -e +fi + +if [[ ${bazel_status:-0} -ne 0 ]]; then + if [[ $print_failed_bazel_test_logs -eq 1 ]]; then + print_bazel_test_log_tails "$bazel_console_log" + fi + exit "$bazel_status" +fi diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..e7bad82677 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,33 @@ +# Workflow Strategy + +The workflows in this directory are split so that pull requests get fast, review-friendly signal while `main` still gets the full cross-platform verification pass. + +## Pull Requests + +- `bazel.yml` is the main pre-merge verification path for Rust code. + It runs Bazel `test` and Bazel `clippy` on the supported Bazel targets. +- `rust-ci.yml` keeps the Cargo-native PR checks intentionally small: + - `cargo fmt --check` + - `cargo shear` + - `argument-comment-lint` on Linux, macOS, and Windows + - `tools/argument-comment-lint` package tests when the lint or its workflow wiring changes + +The PR workflow still keeps the Linux lint lane on the default-targets-only invocation for now, but the released linter runs on Linux, macOS, and Windows before merge. + +## Post-Merge On `main` + +- `bazel.yml` also runs on pushes to `main`. + This re-verifies the merged Bazel path and helps keep the BuildBuddy caches warm. +- `rust-ci-full.yml` is the full Cargo-native verification workflow. + It keeps the heavier checks off the PR path while still validating them after merge: + - the full Cargo `clippy` matrix + - the full Cargo `nextest` matrix + - release-profile Cargo builds + - cross-platform `argument-comment-lint` + - Linux remote-env tests + +## Rule Of Thumb + +- If a build/test/clippy check can be expressed in Bazel, prefer putting the PR-time version in `bazel.yml`. +- Keep `rust-ci.yml` fast enough that it usually does not dominate PR latency. +- Reserve `rust-ci-full.yml` for heavyweight Cargo-native coverage that Bazel does not replace yet. diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 79d963a537..a6c9277b61 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -1,4 +1,4 @@ -name: Bazel (experimental) +name: Bazel # Note this workflow was originally derived from: # https://github.com/cerisier/toolchains_llvm_bootstrapped/blob/main/.github/workflows/ci.yaml @@ -17,6 +17,7 @@ concurrency: cancel-in-progress: ${{ github.ref_name != 'main' }} jobs: test: + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -39,194 +40,115 @@ jobs: # - os: ubuntu-24.04-arm # target: aarch64-unknown-linux-gnu - # TODO: Enable Windows once we fix the toolchain issues there. - #- os: windows-latest - # target: x86_64-pc-windows-gnullvm + # Windows + - os: windows-latest + target: x86_64-pc-windows-gnullvm runs-on: ${{ matrix.os }} # Configure a human readable name for each job name: Local Bazel build on ${{ matrix.os }} for ${{ matrix.target }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Node.js for js_repl tests - uses: actions/setup-node@v6 + - name: Set up Bazel CI + id: setup_bazel + uses: ./.github/actions/setup-bazel-ci with: - node-version-file: codex-rs/node-version.txt - - # Some integration tests rely on DotSlash being installed. - # See https://github.com/openai/codex/pull/7617. - - name: Install DotSlash - uses: facebook/install-dotslash@v2 - - - name: Make DotSlash available in PATH (Unix) - if: runner.os != 'Windows' - run: cp "$(which dotslash)" /usr/local/bin - - - name: Make DotSlash available in PATH (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe" - - # Install Bazel via Bazelisk - - name: Set up Bazel - uses: bazelbuild/setup-bazelisk@v3 + target: ${{ matrix.target }} + install-test-prereqs: "true" - name: Check MODULE.bazel.lock is up to date if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu' shell: bash run: ./scripts/check-module-bazel-lock.sh - # Restore bazel repository cache so we don't have to redownload all the external dependencies - # on every CI run. - - name: Restore bazel repository cache - id: cache_bazel_repository_restore - uses: actions/cache/restore@v5 - with: - path: | - ~/.cache/bazel-repo-cache - key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} - restore-keys: | - bazel-cache-${{ matrix.target }} - - - name: Configure Bazel startup args (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # Use a very short path to reduce argv/path length issues. - "BAZEL_STARTUP_ARGS=--output_user_root=C:\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - name: bazel test //... env: BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} shell: bash run: | - set -o pipefail - - bazel_console_log="$(mktemp)" - - print_failed_bazel_test_logs() { - local console_log="$1" - local testlogs_dir - - testlogs_dir="$(bazel $BAZEL_STARTUP_ARGS info bazel-testlogs 2>/dev/null || echo bazel-testlogs)" - - local failed_targets=() - while IFS= read -r target; do - failed_targets+=("$target") - done < <( - grep -E '^FAIL: //' "$console_log" \ - | sed -E 's#^FAIL: (//[^ ]+).*#\1#' \ - | sort -u - ) - - if [[ ${#failed_targets[@]} -eq 0 ]]; then - echo "No failed Bazel test targets were found in console output." - return - fi - - for target in "${failed_targets[@]}"; do - local rel_path="${target#//}" - rel_path="${rel_path/:/\/}" - local test_log="${testlogs_dir}/${rel_path}/test.log" - - echo "::group::Bazel test log tail for ${target}" - if [[ -f "$test_log" ]]; then - tail -n 200 "$test_log" - else - echo "Missing test log: $test_log" - fi - echo "::endgroup::" - done - } - - bazel_args=( - test - --test_verbose_timeout_warnings - --build_metadata=COMMIT_SHA=$(git rev-parse HEAD) - ) - bazel_targets=( //... - # Keep V8 out of the ordinary Bazel CI path. Only the dedicated - # canary and release workflows should build `third_party/v8`. + # Keep standalone V8 library targets out of the ordinary Bazel CI + # path. V8 consumers under `//codex-rs/...` still participate + # transitively through `//...`. -//third_party/v8:all ) - if [[ "${RUNNER_OS:-}" != "Windows" ]]; then - # Bazel test sandboxes on macOS may resolve an older Homebrew `node` - # before the `actions/setup-node` runtime on PATH. - node_bin="$(which node)" - bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}") - fi - - ci_config=ci-linux - if [[ "${RUNNER_OS:-}" == "macOS" ]]; then - ci_config=ci-macos - elif [[ "${RUNNER_OS:-}" == "Windows" ]]; then - ci_config=ci-windows - fi - - if [[ -n "${BUILDBUDDY_API_KEY:-}" ]]; then - echo "BuildBuddy API key is available; using remote Bazel configuration." - # Work around Bazel 9 remote repo contents cache / overlay materialization failures - # seen in CI (for example "is not a symlink" or permission errors while - # materializing external repos such as rules_perl). We still use BuildBuddy for - # remote execution/cache; this only disables the startup-level repo contents cache. - set +e - bazel $BAZEL_STARTUP_ARGS \ - --noexperimental_remote_repo_contents_cache \ - "${bazel_args[@]}" \ - "--config=${ci_config}" \ - "--remote_header=x-buildbuddy-api-key=$BUILDBUDDY_API_KEY" \ - -- \ - "${bazel_targets[@]}" \ - 2>&1 | tee "$bazel_console_log" - bazel_status=${PIPESTATUS[0]} - set -e - else - echo "BuildBuddy API key is not available; using local Bazel configuration." - # Keep fork/community PRs on Bazel but disable remote services that are - # configured in .bazelrc and require auth. - # - # Flag docs: - # - Command-line reference: https://bazel.build/reference/command-line-reference - # - Remote caching overview: https://bazel.build/remote/caching - # - Remote execution overview: https://bazel.build/remote/rbe - # - Build Event Protocol overview: https://bazel.build/remote/bep - # - # --noexperimental_remote_repo_contents_cache: - # disable remote repo contents cache enabled in .bazelrc startup options. - # https://bazel.build/reference/command-line-reference#startup_options-flag--experimental_remote_repo_contents_cache - # --remote_cache= and --remote_executor=: - # clear remote cache/execution endpoints configured in .bazelrc. - # https://bazel.build/reference/command-line-reference#common_options-flag--remote_cache - # https://bazel.build/reference/command-line-reference#common_options-flag--remote_executor - set +e - bazel $BAZEL_STARTUP_ARGS \ - --noexperimental_remote_repo_contents_cache \ - "${bazel_args[@]}" \ - --remote_cache= \ - --remote_executor= \ - -- \ - "${bazel_targets[@]}" \ - 2>&1 | tee "$bazel_console_log" - bazel_status=${PIPESTATUS[0]} - set -e - fi - - if [[ ${bazel_status:-0} -ne 0 ]]; then - print_failed_bazel_test_logs "$bazel_console_log" - exit "$bazel_status" - fi + ./.github/scripts/run-bazel-ci.sh \ + --print-failed-test-logs \ + --use-node-test-env \ + -- \ + test \ + --test_tag_filters=-argument-comment-lint \ + --test_verbose_timeout_warnings \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + -- \ + "${bazel_targets[@]}" # Save bazel repository cache explicitly; make non-fatal so cache uploading # never fails the overall job. Only save when key wasn't hit. - name: Save bazel repository cache - if: always() && !cancelled() && steps.cache_bazel_repository_restore.outputs.cache-hit != 'true' + if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true' continue-on-error: true - uses: actions/cache/save@v5 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cache/bazel-repo-cache + key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} + + clippy: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # Keep Linux lint coverage on x64 and add the arm64 macOS path that + # the Bazel test job already exercises. Add Windows gnullvm as well + # so PRs get Bazel-native lint signal on the same Windows toolchain + # that the Bazel test job uses. + - os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - os: macos-15-xlarge + target: aarch64-apple-darwin + - os: windows-latest + target: x86_64-pc-windows-gnullvm + runs-on: ${{ matrix.os }} + name: Bazel clippy on ${{ matrix.os }} for ${{ matrix.target }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Bazel CI + id: setup_bazel + uses: ./.github/actions/setup-bazel-ci + with: + target: ${{ matrix.target }} + + - name: bazel build --config=clippy //codex-rs/... + env: + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + shell: bash + run: | + # Keep the initial Bazel clippy scope on codex-rs and out of the + # V8 proof-of-concept target for now. + ./.github/scripts/run-bazel-ci.sh \ + -- \ + build \ + --config=clippy \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + --build_metadata=TAG_job=clippy \ + -- \ + //codex-rs/... \ + -//codex-rs/v8-poc:all + + # Save bazel repository cache explicitly; make non-fatal so cache uploading + # never fails the overall job. Only save when key wasn't hit. + - name: Save bazel repository cache + if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: | ~/.cache/bazel-repo-cache diff --git a/.github/workflows/blob-size-policy.yml b/.github/workflows/blob-size-policy.yml index bce6e49790..b96cb98c30 100644 --- a/.github/workflows/blob-size-policy.yml +++ b/.github/workflows/blob-size-policy.yml @@ -8,7 +8,7 @@ jobs: name: Blob size policy runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index 60adb38710..5294d0c7c5 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -14,13 +14,13 @@ jobs: working-directory: ./codex-rs steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable - name: Run cargo-deny - uses: EmbarkStudios/cargo-deny-action@v2 + uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2 with: rust-version: stable manifest-path: ./codex-rs/Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbd2df27cf..6c53900401 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,15 +12,15 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: run_install: false - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 22 @@ -28,7 +28,7 @@ jobs: run: pnpm install --frozen-lockfile # stage_npm_packages.py requires DotSlash when staging releases. - - uses: facebook/install-dotslash@v2 + - uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 - name: Stage npm package id: stage_npm_package @@ -47,7 +47,7 @@ jobs: echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT" - name: Upload staged npm package artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: codex-npm-staging path: ${{ steps.stage_npm_package.outputs.pack_output }} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index bab34d0365..b48fd36fea 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.repository_owner == 'openai' }} runs-on: ubuntu-latest steps: - - uses: contributor-assistant/github-action@v2.6.1 + - uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 # Run on close only if the PR was merged. This will lock the PR to preserve # the CLA agreement. We don't want to lock PRs that have been closed without # merging because the contributor may want to respond with additional comments. diff --git a/.github/workflows/close-stale-contributor-prs.yml b/.github/workflows/close-stale-contributor-prs.yml index 43e6992883..8fb5132772 100644 --- a/.github/workflows/close-stale-contributor-prs.yml +++ b/.github/workflows/close-stale-contributor-prs.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close inactive PRs from contributors - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index bbbb06d06f..8e9f701eec 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Annotate locations with typos uses: codespell-project/codespell-problem-matcher@b80729f885d32f78a716c2f107b4db1025001c42 # v1 - name: Codespell diff --git a/.github/workflows/issue-deduplicator.yml b/.github/workflows/issue-deduplicator.yml index 6f4df87f43..c0fadbcf18 100644 --- a/.github/workflows/issue-deduplicator.yml +++ b/.github/workflows/issue-deduplicator.yml @@ -19,7 +19,7 @@ jobs: reason: ${{ steps.normalize-all.outputs.reason }} has_matches: ${{ steps.normalize-all.outputs.has_matches }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Prepare Codex inputs env: @@ -61,7 +61,7 @@ jobs: # .github/prompts/issue-deduplicator.txt file is obsolete and removed. - id: codex-all name: Find duplicates (pass 1, all issues) - uses: openai/codex-action@main + uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1 with: openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }} allow-users: "*" @@ -155,7 +155,7 @@ jobs: reason: ${{ steps.normalize-open.outputs.reason }} has_matches: ${{ steps.normalize-open.outputs.has_matches }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Prepare Codex inputs env: @@ -195,7 +195,7 @@ jobs: - id: codex-open name: Find duplicates (pass 2, open issues) - uses: openai/codex-action@main + uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1 with: openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }} allow-users: "*" @@ -342,7 +342,7 @@ jobs: issues: write steps: - name: Comment on issue - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: CODEX_OUTPUT: ${{ needs.select-final.outputs.codex_output }} with: diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml index 174b219de9..164805a74b 100644 --- a/.github/workflows/issue-labeler.yml +++ b/.github/workflows/issue-labeler.yml @@ -17,10 +17,10 @@ jobs: outputs: codex_output: ${{ steps.codex.outputs.final-message }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - id: codex - uses: openai/codex-action@main + uses: openai/codex-action@0b91f4a2703c23df3102c3f0967d3c6db34eedef # v1 with: openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }} allow-users: "*" diff --git a/.github/workflows/rust-ci-full.yml b/.github/workflows/rust-ci-full.yml new file mode 100644 index 0000000000..6d36b5f2eb --- /dev/null +++ b/.github/workflows/rust-ci-full.yml @@ -0,0 +1,775 @@ +name: rust-ci-full +on: + push: + branches: + - main + workflow_dispatch: + +# CI builds in debug (dev) for faster signal. + +jobs: + # --- CI that doesn't need specific targets --------------------------------- + general: + name: Format / etc + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: codex-rs + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + with: + components: rustfmt + - name: cargo fmt + run: cargo fmt -- --config imports_granularity=Item --check + + cargo_shear: + name: cargo shear + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: codex-rs + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 + with: + tool: cargo-shear + version: 1.5.1 + - name: cargo shear + run: cargo shear + + argument_comment_lint_package: + name: Argument comment lint package + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + with: + toolchain: nightly-2025-09-18 + components: llvm-tools-preview, rustc-dev, rust-src + - name: Cache cargo-dylint tooling + id: cargo_dylint_cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin/cargo-dylint + ~/.cargo/bin/dylint-link + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml', '.github/workflows/rust-ci-full.yml') }} + - name: Install cargo-dylint tooling + if: ${{ steps.cargo_dylint_cache.outputs.cache-hit != 'true' }} + run: cargo install --locked cargo-dylint dylint-link + - name: Check Python wrapper syntax + run: python3 -m py_compile tools/argument-comment-lint/wrapper_common.py tools/argument-comment-lint/run.py tools/argument-comment-lint/run-prebuilt-linter.py tools/argument-comment-lint/test_wrapper_common.py + - name: Test Python wrapper helpers + run: python3 -m unittest discover -s tools/argument-comment-lint -p 'test_*.py' + - name: Test argument comment lint package + working-directory: tools/argument-comment-lint + run: cargo test + + argument_comment_lint_prebuilt: + name: Argument comment lint - ${{ matrix.name }} + runs-on: ${{ matrix.runs_on || matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: Linux + runner: ubuntu-24.04 + - name: macOS + runner: macos-15-xlarge + - name: Windows + runner: windows-x64 + runs_on: + group: codex-runners + labels: codex-windows-x64 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ./.github/actions/setup-bazel-ci + with: + target: ${{ runner.os }} + install-test-prereqs: true + - name: Install Linux sandbox build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev + - name: Run argument comment lint on codex-rs via Bazel + if: ${{ runner.os != 'Windows' }} + env: + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + shell: bash + run: | + bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)" + ./.github/scripts/run-bazel-ci.sh \ + -- \ + build \ + --config=argument-comment-lint \ + --keep_going \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + -- \ + ${bazel_targets} + - name: Run argument comment lint on codex-rs via Bazel + if: ${{ runner.os == 'Windows' }} + env: + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + shell: bash + run: | + ./.github/scripts/run-argument-comment-lint-bazel.sh \ + --config=argument-comment-lint \ + --platforms=//:local_windows \ + --keep_going \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} + + # --- CI to validate on different os/targets -------------------------------- + lint_build: + name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }} + runs-on: ${{ matrix.runs_on || matrix.runner }} + timeout-minutes: 30 + defaults: + run: + working-directory: codex-rs + env: + # Speed up repeated builds across CI runs by caching compiled objects, except on + # arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce + # mixed-architecture archives under sccache. + USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }} + CARGO_INCREMENTAL: "0" + SCCACHE_CACHE_SIZE: 10G + # In rust-ci, representative release-profile checks use thin LTO for faster feedback. + CARGO_PROFILE_RELEASE_LTO: ${{ matrix.profile == 'release' && 'thin' || 'fat' }} + + strategy: + fail-fast: false + matrix: + include: + - runner: macos-15-xlarge + target: aarch64-apple-darwin + profile: dev + - runner: macos-15-xlarge + target: x86_64-apple-darwin + profile: dev + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-musl + profile: dev + runs_on: + group: codex-runners + labels: codex-linux-x64 + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + profile: dev + runs_on: + group: codex-runners + labels: codex-linux-x64 + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + profile: dev + runs_on: + group: codex-runners + labels: codex-linux-arm64 + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + profile: dev + runs_on: + group: codex-runners + labels: codex-linux-arm64 + - runner: windows-x64 + target: x86_64-pc-windows-msvc + profile: dev + runs_on: + group: codex-runners + labels: codex-windows-x64 + - runner: windows-arm64 + target: aarch64-pc-windows-msvc + profile: dev + runs_on: + group: codex-runners + labels: codex-windows-arm64 + + # Also run representative release builds on Mac and Linux because + # there could be release-only build errors we want to catch. + # Hopefully this also pre-populates the build cache to speed up + # releases. + - runner: macos-15-xlarge + target: aarch64-apple-darwin + profile: release + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-musl + profile: release + runs_on: + group: codex-runners + labels: codex-linux-x64 + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + profile: release + runs_on: + group: codex-runners + labels: codex-linux-arm64 + - runner: windows-x64 + target: x86_64-pc-windows-msvc + profile: release + runs_on: + group: codex-runners + labels: codex-windows-x64 + - runner: windows-arm64 + target: aarch64-pc-windows-msvc + profile: release + runs_on: + group: codex-runners + labels: codex-windows-arm64 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install Linux build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + set -euo pipefail + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update -y + packages=(pkg-config libcap-dev) + if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then + packages+=(libubsan1) + fi + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}" + fi + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + with: + targets: ${{ matrix.target }} + components: clippy + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Use hermetic Cargo home (musl) + shell: bash + run: | + set -euo pipefail + cargo_home="${GITHUB_WORKSPACE}/.cargo-home" + mkdir -p "${cargo_home}/bin" + echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV" + echo "${cargo_home}/bin" >> "$GITHUB_PATH" + : > "${cargo_home}/config.toml" + + - name: Compute lockfile hash + id: lockhash + working-directory: codex-rs + shell: bash + run: | + set -euo pipefail + echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + + # Explicit cache restore: split cargo home vs target, so we can + # avoid caching the large target dir on the gnu-dev job. + - name: Restore cargo home cache + id: cache_cargo_home_restore + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ${{ github.workspace }}/.cargo-home/bin/ + ${{ github.workspace }}/.cargo-home/registry/index/ + ${{ github.workspace }}/.cargo-home/registry/cache/ + ${{ github.workspace }}/.cargo-home/git/db/ + key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} + restore-keys: | + cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- + + # Install and restore sccache cache + - name: Install sccache + if: ${{ env.USE_SCCACHE == 'true' }} + uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 + with: + tool: sccache + version: 0.7.5 + + - name: Configure sccache backend + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: | + set -euo pipefail + if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then + echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" + echo "Using sccache GitHub backend" + else + echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV" + echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV" + echo "Using sccache local disk + actions/cache fallback" + fi + + - name: Enable sccache wrapper + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + + - name: Restore sccache cache (fallback) + if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} + id: cache_sccache_restore + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: ${{ github.workspace }}/.sccache/ + key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} + restore-keys: | + sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}- + sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Disable sccache wrapper (musl) + shell: bash + run: | + set -euo pipefail + echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV" + echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV" + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Prepare APT cache directories (musl) + shell: bash + run: | + set -euo pipefail + sudo mkdir -p /var/cache/apt/archives /var/lib/apt/lists + sudo chown -R "$USER:$USER" /var/cache/apt /var/lib/apt/lists + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Restore APT cache (musl) + id: cache_apt_restore + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + /var/cache/apt + key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1 + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Install Zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 + with: + version: 0.14.0 + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Install musl build tools + env: + DEBIAN_FRONTEND: noninteractive + TARGET: ${{ matrix.target }} + APT_UPDATE_ARGS: -o Acquire::Retries=3 + APT_INSTALL_ARGS: --no-install-recommends + shell: bash + run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh" + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Configure rustc UBSan wrapper (musl host) + shell: bash + run: | + set -euo pipefail + ubsan="" + if command -v ldconfig >/dev/null 2>&1; then + ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')" + fi + wrapper_root="${RUNNER_TEMP:-/tmp}" + wrapper="${wrapper_root}/rustc-ubsan-wrapper" + cat > "${wrapper}" <> "$GITHUB_ENV" + echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV" + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} + name: Clear sanitizer flags (musl) + shell: bash + run: | + set -euo pipefail + # Clear global Rust flags so host/proc-macro builds don't pull in UBSan. + echo "RUSTFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV" + echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV" + # Override any runner-level Cargo config rustflags as well. + echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" + + sanitize_flags() { + local input="$1" + input="${input//-fsanitize=undefined/}" + input="${input//-fno-sanitize-recover=undefined/}" + input="${input//-fno-sanitize-trap=undefined/}" + echo "$input" + } + + cflags="$(sanitize_flags "${CFLAGS-}")" + cxxflags="$(sanitize_flags "${CXXFLAGS-}")" + echo "CFLAGS=${cflags}" >> "$GITHUB_ENV" + echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV" + + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }} + name: Configure musl rusty_v8 artifact overrides + env: + TARGET: ${{ matrix.target }} + shell: bash + run: | + set -euo pipefail + version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)" + release_tag="rusty-v8-v${version}" + base_url="https://github.com/openai/codex/releases/download/${release_tag}" + archive="https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz" + binding_dir="${RUNNER_TEMP}/rusty_v8" + binding_path="${binding_dir}/src_binding_release_${TARGET}.rs" + mkdir -p "${binding_dir}" + curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}" + echo "RUSTY_V8_ARCHIVE=${archive}" >> "$GITHUB_ENV" + echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "$GITHUB_ENV" + + - name: Install cargo-chef + if: ${{ matrix.profile == 'release' }} + uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 + with: + tool: cargo-chef + version: 0.1.71 + + - name: Pre-warm dependency cache (cargo-chef) + if: ${{ matrix.profile == 'release' }} + shell: bash + run: | + set -euo pipefail + RECIPE="${RUNNER_TEMP}/chef-recipe.json" + cargo chef prepare --recipe-path "$RECIPE" + cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features + + - name: cargo clippy + run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings + + - name: Upload Cargo timings (clippy) + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn + + # Save caches explicitly; make non-fatal so cache packaging + # never fails the overall job. Only save when key wasn't hit. + - name: Save cargo home cache + if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ${{ github.workspace }}/.cargo-home/bin/ + ${{ github.workspace }}/.cargo-home/registry/index/ + ${{ github.workspace }}/.cargo-home/registry/cache/ + ${{ github.workspace }}/.cargo-home/git/db/ + key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} + + - name: Save sccache cache (fallback) + if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: ${{ github.workspace }}/.sccache/ + key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} + + - name: sccache stats + if: always() && env.USE_SCCACHE == 'true' + continue-on-error: true + run: sccache --show-stats || true + + - name: sccache summary + if: always() && env.USE_SCCACHE == 'true' + shell: bash + run: | + { + echo "### sccache stats — ${{ matrix.target }} (${{ matrix.profile }})"; + echo; + echo '```'; + sccache --show-stats || true; + echo '```'; + } >> "$GITHUB_STEP_SUMMARY" + + - name: Save APT cache (musl) + if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + /var/cache/apt + key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1 + + tests: + name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }} + runs-on: ${{ matrix.runs_on || matrix.runner }} + # Perhaps we can bring this back down to 30m once we finish the cutover + # from tui_app_server/ to tui/. Incidentally, windows-arm64 was the main + # offender for exceeding the timeout. + timeout-minutes: 45 + defaults: + run: + working-directory: codex-rs + env: + # Speed up repeated builds across CI runs by caching compiled objects, except on + # arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce + # mixed-architecture archives under sccache. + USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }} + CARGO_INCREMENTAL: "0" + SCCACHE_CACHE_SIZE: 10G + + strategy: + fail-fast: false + matrix: + include: + - runner: macos-15-xlarge + target: aarch64-apple-darwin + profile: dev + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + profile: dev + remote_env: "true" + runs_on: + group: codex-runners + labels: codex-linux-x64 + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + profile: dev + runs_on: + group: codex-runners + labels: codex-linux-arm64 + - runner: windows-x64 + target: x86_64-pc-windows-msvc + profile: dev + runs_on: + group: codex-runners + labels: codex-windows-x64 + - runner: windows-arm64 + target: aarch64-pc-windows-msvc + profile: dev + runs_on: + group: codex-runners + labels: codex-windows-arm64 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Set up Node.js for js_repl tests + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version-file: codex-rs/node-version.txt + - name: Install Linux build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + set -euo pipefail + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev + fi + + # Some integration tests rely on DotSlash being installed. + # See https://github.com/openai/codex/pull/7617. + - name: Install DotSlash + uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 + + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + with: + targets: ${{ matrix.target }} + + - name: Compute lockfile hash + id: lockhash + working-directory: codex-rs + shell: bash + run: | + set -euo pipefail + echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + + - name: Restore cargo home cache + id: cache_cargo_home_restore + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} + restore-keys: | + cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- + + - name: Install sccache + if: ${{ env.USE_SCCACHE == 'true' }} + uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 + with: + tool: sccache + version: 0.7.5 + + - name: Configure sccache backend + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: | + set -euo pipefail + if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then + echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" + echo "Using sccache GitHub backend" + else + echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV" + echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV" + echo "Using sccache local disk + actions/cache fallback" + fi + + - name: Enable sccache wrapper + if: ${{ env.USE_SCCACHE == 'true' }} + shell: bash + run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + + - name: Restore sccache cache (fallback) + if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} + id: cache_sccache_restore + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: ${{ github.workspace }}/.sccache/ + key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} + restore-keys: | + sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}- + sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- + + - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 + with: + tool: nextest + version: 0.9.103 + + - name: Enable unprivileged user namespaces (Linux) + if: runner.os == 'Linux' + run: | + # Required for bubblewrap to work on Linux CI runners. + sudo sysctl -w kernel.unprivileged_userns_clone=1 + # Ubuntu 24.04+ can additionally gate unprivileged user namespaces + # behind AppArmor. + if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + fi + + - name: Set up remote test env (Docker) + if: ${{ runner.os == 'Linux' && matrix.remote_env == 'true' }} + shell: bash + run: | + set -euo pipefail + export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME=codex-remote-test-env + source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh" + echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV" + + - name: tests + id: test + run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings + env: + RUST_BACKTRACE: 1 + NEXTEST_STATUS_LEVEL: leak + + - name: Upload Cargo timings (nextest) + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }} + path: codex-rs/target/**/cargo-timings/cargo-timing.html + if-no-files-found: warn + + - name: Save cargo home cache + if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} + + - name: Save sccache cache (fallback) + if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: ${{ github.workspace }}/.sccache/ + key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} + + - name: sccache stats + if: always() && env.USE_SCCACHE == 'true' + continue-on-error: true + run: sccache --show-stats || true + + - name: sccache summary + if: always() && env.USE_SCCACHE == 'true' + shell: bash + run: | + { + echo "### sccache stats — ${{ matrix.target }} (tests)"; + echo; + echo '```'; + sccache --show-stats || true; + echo '```'; + } >> "$GITHUB_STEP_SUMMARY" + + - name: Tear down remote test env + if: ${{ always() && runner.os == 'Linux' && matrix.remote_env == 'true' }} + shell: bash + run: | + set +e + if [[ "${{ steps.test.outcome }}" != "success" ]]; then + docker logs codex-remote-test-env || true + fi + docker rm -f codex-remote-test-env >/dev/null 2>&1 || true + + - name: verify tests passed + if: steps.test.outcome == 'failure' + run: | + echo "Tests failed. See logs for details." + exit 1 + + # --- Gatherer job for the full post-merge workflow -------------------------- + results: + name: Full CI results + needs: + [ + general, + cargo_shear, + argument_comment_lint_package, + argument_comment_lint_prebuilt, + lint_build, + tests, + ] + if: always() + runs-on: ubuntu-24.04 + steps: + - name: Summarize + shell: bash + run: | + echo "argpkg : ${{ needs.argument_comment_lint_package.result }}" + echo "arglint: ${{ needs.argument_comment_lint_prebuilt.result }}" + echo "general: ${{ needs.general.result }}" + echo "shear : ${{ needs.cargo_shear.result }}" + echo "lint : ${{ needs.lint_build.result }}" + echo "tests : ${{ needs.tests.result }}" + [[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; } + [[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; } + [[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; } + [[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; } + [[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; } + [[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; } + + - name: sccache summary note + if: always() + run: | + echo "Per-job sccache stats are attached to each matrix job's Step Summary." diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index c203e2b742..3a9eadc8be 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,15 +1,10 @@ name: rust-ci on: pull_request: {} - push: - branches: - - main workflow_dispatch: -# CI builds in debug (dev) for faster signal. - jobs: - # --- Detect what changed to detect which tests to run (always runs) ------------------------------------- + # --- Detect what changed so the fast PR workflow only runs relevant jobs ---- changed: name: Detect changed areas runs-on: ubuntu-24.04 @@ -19,7 +14,7 @@ jobs: codex: ${{ steps.detect.outputs.codex }} workflows: ${{ steps.detect.outputs.workflows }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: Detect changed paths (no external action) @@ -33,11 +28,10 @@ jobs: HEAD_SHA='${{ github.event.pull_request.head.sha }}' echo "Base SHA: $BASE_SHA" echo "Head SHA: $HEAD_SHA" - # List files changed between base and PR head mapfile -t files < <(git diff --name-only --no-renames "$BASE_SHA" "$HEAD_SHA") else - # On push / manual runs, default to running everything - files=("codex-rs/force" ".github/force") + # On manual runs, default to the full fast-PR bundle. + files=("codex-rs/force" "tools/argument-comment-lint/force" ".github/force") fi codex=false @@ -47,7 +41,7 @@ jobs: for f in "${files[@]}"; do [[ $f == codex-rs/* ]] && codex=true [[ $f == codex-rs/* || $f == tools/argument-comment-lint/* || $f == justfile ]] && argument_comment_lint=true - [[ $f == tools/argument-comment-lint/* || $f == .github/workflows/rust-ci.yml ]] && argument_comment_lint_package=true + [[ $f == tools/argument-comment-lint/* || $f == .github/workflows/rust-ci.yml || $f == .github/workflows/rust-ci-full.yml ]] && argument_comment_lint_package=true [[ $f == .github/* ]] && workflows=true done @@ -56,18 +50,18 @@ jobs: echo "codex=$codex" >> "$GITHUB_OUTPUT" echo "workflows=$workflows" >> "$GITHUB_OUTPUT" - # --- CI that doesn't need specific targets --------------------------------- + # --- Fast Cargo-native PR checks ------------------------------------------- general: name: Format / etc runs-on: ubuntu-24.04 needs: changed - if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} + if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' }} defaults: run: working-directory: codex-rs steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.93.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 with: components: rustfmt - name: cargo fmt @@ -77,13 +71,13 @@ jobs: name: cargo shear runs-on: ubuntu-24.04 needs: changed - if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} + if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' }} defaults: run: working-directory: codex-rs steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.93.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 with: tool: cargo-shear @@ -95,16 +89,23 @@ jobs: name: Argument comment lint package runs-on: ubuntu-24.04 needs: changed - if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' || github.event_name == 'push' }} + if: ${{ needs.changed.outputs.argument_comment_lint_package == 'true' }} steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.93.0 - with: - toolchain: nightly-2025-09-18 - components: llvm-tools-preview, rustc-dev, rust-src + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 + - name: Install nightly argument-comment-lint toolchain + shell: bash + run: | + rustup toolchain install nightly-2025-09-18 \ + --profile minimal \ + --component llvm-tools-preview \ + --component rustc-dev \ + --component rust-src \ + --no-self-update + rustup default nightly-2025-09-18 - name: Cache cargo-dylint tooling id: cargo_dylint_cache - uses: actions/cache@v5 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 with: path: | ~/.cargo/bin/cargo-dylint @@ -112,12 +113,14 @@ jobs: ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db - key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml') }} + key: argument-comment-lint-${{ runner.os }}-${{ hashFiles('tools/argument-comment-lint/Cargo.lock', 'tools/argument-comment-lint/rust-toolchain', '.github/workflows/rust-ci.yml', '.github/workflows/rust-ci-full.yml') }} - name: Install cargo-dylint tooling if: ${{ steps.cargo_dylint_cache.outputs.cache-hit != 'true' }} run: cargo install --locked cargo-dylint dylint-link - - name: Check source wrapper syntax - run: bash -n tools/argument-comment-lint/run.sh + - name: Check Python wrapper syntax + run: python3 -m py_compile tools/argument-comment-lint/wrapper_common.py tools/argument-comment-lint/run.py tools/argument-comment-lint/run-prebuilt-linter.py tools/argument-comment-lint/test_wrapper_common.py + - name: Test Python wrapper helpers + run: python3 -m unittest discover -s tools/argument-comment-lint -p 'test_*.py' - name: Test argument comment lint package working-directory: tools/argument-comment-lint run: cargo test @@ -125,651 +128,63 @@ jobs: argument_comment_lint_prebuilt: name: Argument comment lint - ${{ matrix.name }} runs-on: ${{ matrix.runs_on || matrix.runner }} + timeout-minutes: ${{ matrix.timeout_minutes }} needs: changed - if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} + if: ${{ needs.changed.outputs.argument_comment_lint == 'true' || needs.changed.outputs.workflows == 'true' }} strategy: fail-fast: false matrix: include: - name: Linux runner: ubuntu-24.04 + timeout_minutes: 30 - name: macOS runner: macos-15-xlarge + timeout_minutes: 30 - name: Windows runner: windows-x64 + timeout_minutes: 30 runs_on: group: codex-runners labels: codex-windows-x64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ./.github/actions/setup-bazel-ci + with: + target: ${{ runner.os }} + install-test-prereqs: true - name: Install Linux sandbox build dependencies if: ${{ runner.os == 'Linux' }} shell: bash run: | sudo DEBIAN_FRONTEND=noninteractive apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev - - uses: dtolnay/rust-toolchain@1.93.0 - with: - toolchain: nightly-2025-09-18 - components: llvm-tools-preview, rustc-dev, rust-src - - uses: facebook/install-dotslash@v2 - - name: Run argument comment lint on codex-rs - shell: bash - run: ./tools/argument-comment-lint/run-prebuilt-linter.sh - - # --- CI to validate on different os/targets -------------------------------- - lint_build: - name: Lint/Build — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.profile == 'release' && ' (release)' || '' }} - runs-on: ${{ matrix.runs_on || matrix.runner }} - timeout-minutes: 30 - needs: changed - # Keep job-level if to avoid spinning up runners when not needed - if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} - defaults: - run: - working-directory: codex-rs - env: - # Speed up repeated builds across CI runs by caching compiled objects, except on - # arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce - # mixed-architecture archives under sccache. - USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }} - CARGO_INCREMENTAL: "0" - SCCACHE_CACHE_SIZE: 10G - # In rust-ci, representative release-profile checks use thin LTO for faster feedback. - CARGO_PROFILE_RELEASE_LTO: ${{ matrix.profile == 'release' && 'thin' || 'fat' }} - - strategy: - fail-fast: false - matrix: - include: - - runner: macos-15-xlarge - target: aarch64-apple-darwin - profile: dev - - runner: macos-15-xlarge - target: x86_64-apple-darwin - profile: dev - - runner: ubuntu-24.04 - target: x86_64-unknown-linux-musl - profile: dev - runs_on: - group: codex-runners - labels: codex-linux-x64 - - runner: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - profile: dev - runs_on: - group: codex-runners - labels: codex-linux-x64 - - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - profile: dev - runs_on: - group: codex-runners - labels: codex-linux-arm64 - - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-gnu - profile: dev - runs_on: - group: codex-runners - labels: codex-linux-arm64 - - runner: windows-x64 - target: x86_64-pc-windows-msvc - profile: dev - runs_on: - group: codex-runners - labels: codex-windows-x64 - - runner: windows-arm64 - target: aarch64-pc-windows-msvc - profile: dev - runs_on: - group: codex-runners - labels: codex-windows-arm64 - - # Also run representative release builds on Mac and Linux because - # there could be release-only build errors we want to catch. - # Hopefully this also pre-populates the build cache to speed up - # releases. - - runner: macos-15-xlarge - target: aarch64-apple-darwin - profile: release - - runner: ubuntu-24.04 - target: x86_64-unknown-linux-musl - profile: release - runs_on: - group: codex-runners - labels: codex-linux-x64 - - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - profile: release - runs_on: - group: codex-runners - labels: codex-linux-arm64 - - runner: windows-x64 - target: x86_64-pc-windows-msvc - profile: release - runs_on: - group: codex-runners - labels: codex-windows-x64 - - runner: windows-arm64 - target: aarch64-pc-windows-msvc - profile: release - runs_on: - group: codex-runners - labels: codex-windows-arm64 - - steps: - - uses: actions/checkout@v6 - - name: Install Linux build dependencies - if: ${{ runner.os == 'Linux' }} - shell: bash - run: | - set -euo pipefail - if command -v apt-get >/dev/null 2>&1; then - sudo apt-get update -y - packages=(pkg-config libcap-dev) - if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then - packages+=(libubsan1) - fi - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}" - fi - - uses: dtolnay/rust-toolchain@1.93.0 - with: - targets: ${{ matrix.target }} - components: clippy - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Use hermetic Cargo home (musl) - shell: bash - run: | - set -euo pipefail - cargo_home="${GITHUB_WORKSPACE}/.cargo-home" - mkdir -p "${cargo_home}/bin" - echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV" - echo "${cargo_home}/bin" >> "$GITHUB_PATH" - : > "${cargo_home}/config.toml" - - - name: Compute lockfile hash - id: lockhash - working-directory: codex-rs - shell: bash - run: | - set -euo pipefail - echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - - # Explicit cache restore: split cargo home vs target, so we can - # avoid caching the large target dir on the gnu-dev job. - - name: Restore cargo home cache - id: cache_cargo_home_restore - uses: actions/cache/restore@v5 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - ${{ github.workspace }}/.cargo-home/bin/ - ${{ github.workspace }}/.cargo-home/registry/index/ - ${{ github.workspace }}/.cargo-home/registry/cache/ - ${{ github.workspace }}/.cargo-home/git/db/ - key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} - restore-keys: | - cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- - - # Install and restore sccache cache - - name: Install sccache - if: ${{ env.USE_SCCACHE == 'true' }} - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 - with: - tool: sccache - version: 0.7.5 - - - name: Configure sccache backend - if: ${{ env.USE_SCCACHE == 'true' }} - shell: bash - run: | - set -euo pipefail - if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then - echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" - echo "Using sccache GitHub backend" - else - echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV" - echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV" - echo "Using sccache local disk + actions/cache fallback" - fi - - - name: Enable sccache wrapper - if: ${{ env.USE_SCCACHE == 'true' }} - shell: bash - run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - - - name: Restore sccache cache (fallback) - if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} - id: cache_sccache_restore - uses: actions/cache/restore@v5 - with: - path: ${{ github.workspace }}/.sccache/ - key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} - restore-keys: | - sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}- - sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Disable sccache wrapper (musl) - shell: bash - run: | - set -euo pipefail - echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV" - echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV" - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Prepare APT cache directories (musl) - shell: bash - run: | - set -euo pipefail - sudo mkdir -p /var/cache/apt/archives /var/lib/apt/lists - sudo chown -R "$USER:$USER" /var/cache/apt /var/lib/apt/lists - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Restore APT cache (musl) - id: cache_apt_restore - uses: actions/cache/restore@v5 - with: - path: | - /var/cache/apt - key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1 - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Install Zig - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 - with: - version: 0.14.0 - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Install musl build tools + - name: Run argument comment lint on codex-rs via Bazel + if: ${{ runner.os != 'Windows' }} env: - DEBIAN_FRONTEND: noninteractive - TARGET: ${{ matrix.target }} - APT_UPDATE_ARGS: -o Acquire::Retries=3 - APT_INSTALL_ARGS: --no-install-recommends - shell: bash - run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh" - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Configure rustc UBSan wrapper (musl host) + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} shell: bash run: | - set -euo pipefail - ubsan="" - if command -v ldconfig >/dev/null 2>&1; then - ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')" - fi - wrapper_root="${RUNNER_TEMP:-/tmp}" - wrapper="${wrapper_root}/rustc-ubsan-wrapper" - cat > "${wrapper}" <> "$GITHUB_ENV" - echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV" - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} - name: Clear sanitizer flags (musl) - shell: bash - run: | - set -euo pipefail - # Clear global Rust flags so host/proc-macro builds don't pull in UBSan. - echo "RUSTFLAGS=" >> "$GITHUB_ENV" - echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV" - echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV" - # Override any runner-level Cargo config rustflags as well. - echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV" - echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" - echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" - - sanitize_flags() { - local input="$1" - input="${input//-fsanitize=undefined/}" - input="${input//-fno-sanitize-recover=undefined/}" - input="${input//-fno-sanitize-trap=undefined/}" - echo "$input" - } - - cflags="$(sanitize_flags "${CFLAGS-}")" - cxxflags="$(sanitize_flags "${CXXFLAGS-}")" - echo "CFLAGS=${cflags}" >> "$GITHUB_ENV" - echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV" - - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }} - name: Configure musl rusty_v8 artifact overrides + bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)" + ./.github/scripts/run-bazel-ci.sh \ + -- \ + build \ + --config=argument-comment-lint \ + --keep_going \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + -- \ + ${bazel_targets} + - name: Run argument comment lint on codex-rs via Bazel + if: ${{ runner.os == 'Windows' }} env: - TARGET: ${{ matrix.target }} + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} shell: bash run: | - set -euo pipefail - version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)" - release_tag="rusty-v8-v${version}" - base_url="https://github.com/openai/codex/releases/download/${release_tag}" - archive="https://github.com/openai/codex/releases/download/rusty-v8-v${version}/librusty_v8_release_${TARGET}.a.gz" - binding_dir="${RUNNER_TEMP}/rusty_v8" - binding_path="${binding_dir}/src_binding_release_${TARGET}.rs" - mkdir -p "${binding_dir}" - curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}" - echo "RUSTY_V8_ARCHIVE=${archive}" >> "$GITHUB_ENV" - echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "$GITHUB_ENV" - - - name: Install cargo-chef - if: ${{ matrix.profile == 'release' }} - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 - with: - tool: cargo-chef - version: 0.1.71 - - - name: Pre-warm dependency cache (cargo-chef) - if: ${{ matrix.profile == 'release' }} - shell: bash - run: | - set -euo pipefail - RECIPE="${RUNNER_TEMP}/chef-recipe.json" - cargo chef prepare --recipe-path "$RECIPE" - cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features - - - name: cargo clippy - run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings - - - name: Upload Cargo timings (clippy) - if: always() - uses: actions/upload-artifact@v7 - with: - name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }} - path: codex-rs/target/**/cargo-timings/cargo-timing.html - if-no-files-found: warn - - # Save caches explicitly; make non-fatal so cache packaging - # never fails the overall job. Only save when key wasn't hit. - - name: Save cargo home cache - if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' - continue-on-error: true - uses: actions/cache/save@v5 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - ${{ github.workspace }}/.cargo-home/bin/ - ${{ github.workspace }}/.cargo-home/registry/index/ - ${{ github.workspace }}/.cargo-home/registry/cache/ - ${{ github.workspace }}/.cargo-home/git/db/ - key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} - - - name: Save sccache cache (fallback) - if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' - continue-on-error: true - uses: actions/cache/save@v5 - with: - path: ${{ github.workspace }}/.sccache/ - key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} - - - name: sccache stats - if: always() && env.USE_SCCACHE == 'true' - continue-on-error: true - run: sccache --show-stats || true - - - name: sccache summary - if: always() && env.USE_SCCACHE == 'true' - shell: bash - run: | - { - echo "### sccache stats — ${{ matrix.target }} (${{ matrix.profile }})"; - echo; - echo '```'; - sccache --show-stats || true; - echo '```'; - } >> "$GITHUB_STEP_SUMMARY" - - - name: Save APT cache (musl) - if: always() && !cancelled() && (matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl') && steps.cache_apt_restore.outputs.cache-hit != 'true' - continue-on-error: true - uses: actions/cache/save@v5 - with: - path: | - /var/cache/apt - key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1 - - tests: - name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }} - runs-on: ${{ matrix.runs_on || matrix.runner }} - timeout-minutes: ${{ matrix.runner == 'windows-arm64' && 35 || 30 }} - needs: changed - if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} - defaults: - run: - working-directory: codex-rs - env: - # Speed up repeated builds across CI runs by caching compiled objects, except on - # arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce - # mixed-architecture archives under sccache. - USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }} - CARGO_INCREMENTAL: "0" - SCCACHE_CACHE_SIZE: 10G - - strategy: - fail-fast: false - matrix: - include: - - runner: macos-15-xlarge - target: aarch64-apple-darwin - profile: dev - - runner: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - profile: dev - remote_env: "true" - runs_on: - group: codex-runners - labels: codex-linux-x64 - - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-gnu - profile: dev - runs_on: - group: codex-runners - labels: codex-linux-arm64 - - runner: windows-x64 - target: x86_64-pc-windows-msvc - profile: dev - runs_on: - group: codex-runners - labels: codex-windows-x64 - - runner: windows-arm64 - target: aarch64-pc-windows-msvc - profile: dev - runs_on: - group: codex-runners - labels: codex-windows-arm64 - - steps: - - uses: actions/checkout@v6 - - name: Set up Node.js for js_repl tests - uses: actions/setup-node@v6 - with: - node-version-file: codex-rs/node-version.txt - - name: Install Linux build dependencies - if: ${{ runner.os == 'Linux' }} - shell: bash - run: | - set -euo pipefail - if command -v apt-get >/dev/null 2>&1; then - sudo apt-get update -y - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev - fi - - # Some integration tests rely on DotSlash being installed. - # See https://github.com/openai/codex/pull/7617. - - name: Install DotSlash - uses: facebook/install-dotslash@v2 - - - uses: dtolnay/rust-toolchain@1.93.0 - with: - targets: ${{ matrix.target }} - - - name: Compute lockfile hash - id: lockhash - working-directory: codex-rs - shell: bash - run: | - set -euo pipefail - echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - - - name: Restore cargo home cache - id: cache_cargo_home_restore - uses: actions/cache/restore@v5 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} - restore-keys: | - cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- - - - name: Install sccache - if: ${{ env.USE_SCCACHE == 'true' }} - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 - with: - tool: sccache - version: 0.7.5 - - - name: Configure sccache backend - if: ${{ env.USE_SCCACHE == 'true' }} - shell: bash - run: | - set -euo pipefail - if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then - echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" - echo "Using sccache GitHub backend" - else - echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV" - echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV" - echo "Using sccache local disk + actions/cache fallback" - fi - - - name: Enable sccache wrapper - if: ${{ env.USE_SCCACHE == 'true' }} - shell: bash - run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" - - - name: Restore sccache cache (fallback) - if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }} - id: cache_sccache_restore - uses: actions/cache/restore@v5 - with: - path: ${{ github.workspace }}/.sccache/ - key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} - restore-keys: | - sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}- - sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}- - - - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2 - with: - tool: nextest - version: 0.9.103 - - - name: Enable unprivileged user namespaces (Linux) - if: runner.os == 'Linux' - run: | - # Required for bubblewrap to work on Linux CI runners. - sudo sysctl -w kernel.unprivileged_userns_clone=1 - # Ubuntu 24.04+ can additionally gate unprivileged user namespaces - # behind AppArmor. - if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - fi - - - name: Set up remote test env (Docker) - if: ${{ runner.os == 'Linux' && matrix.remote_env == 'true' }} - shell: bash - run: | - set -euo pipefail - export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME=codex-remote-test-env - source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh" - echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV" - - - name: tests - id: test - run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings - env: - RUST_BACKTRACE: 1 - NEXTEST_STATUS_LEVEL: leak - - - name: Upload Cargo timings (nextest) - if: always() - uses: actions/upload-artifact@v7 - with: - name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }} - path: codex-rs/target/**/cargo-timings/cargo-timing.html - if-no-files-found: warn - - - name: Save cargo home cache - if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true' - continue-on-error: true - uses: actions/cache/save@v5 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }} - - - name: Save sccache cache (fallback) - if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' - continue-on-error: true - uses: actions/cache/save@v5 - with: - path: ${{ github.workspace }}/.sccache/ - key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }} - - - name: sccache stats - if: always() && env.USE_SCCACHE == 'true' - continue-on-error: true - run: sccache --show-stats || true - - - name: sccache summary - if: always() && env.USE_SCCACHE == 'true' - shell: bash - run: | - { - echo "### sccache stats — ${{ matrix.target }} (tests)"; - echo; - echo '```'; - sccache --show-stats || true; - echo '```'; - } >> "$GITHUB_STEP_SUMMARY" - - - name: Tear down remote test env - if: ${{ always() && runner.os == 'Linux' && matrix.remote_env == 'true' }} - shell: bash - run: | - set +e - if [[ "${{ steps.test.outcome }}" != "success" ]]; then - docker logs codex-remote-test-env || true - fi - docker rm -f codex-remote-test-env >/dev/null 2>&1 || true - - - name: verify tests passed - if: steps.test.outcome == 'failure' - run: | - echo "Tests failed. See logs for details." - exit 1 + ./.github/scripts/run-argument-comment-lint-bazel.sh \ + --config=argument-comment-lint \ + --platforms=//:local_windows \ + --keep_going \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} # --- Gatherer job that you mark as the ONLY required status ----------------- results: @@ -781,8 +196,6 @@ jobs: cargo_shear, argument_comment_lint_package, argument_comment_lint_prebuilt, - lint_build, - tests, ] if: always() runs-on: ubuntu-24.04 @@ -794,32 +207,23 @@ jobs: echo "arglint: ${{ needs.argument_comment_lint_prebuilt.result }}" echo "general: ${{ needs.general.result }}" echo "shear : ${{ needs.cargo_shear.result }}" - echo "lint : ${{ needs.lint_build.result }}" - echo "tests : ${{ needs.tests.result }}" # If nothing relevant changed (PR touching only root README, etc.), # declare success regardless of other jobs. - if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' && '${{ github.event_name }}' != 'push' ]]; then + if [[ '${{ needs.changed.outputs.argument_comment_lint }}' != 'true' && '${{ needs.changed.outputs.codex }}' != 'true' && '${{ needs.changed.outputs.workflows }}' != 'true' ]]; then echo 'No relevant changes -> CI not required.' exit 0 fi - if [[ '${{ needs.changed.outputs.argument_comment_lint_package }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then + if [[ '${{ needs.changed.outputs.argument_comment_lint_package }}' == 'true' ]]; then [[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; } fi - if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then + if [[ '${{ needs.changed.outputs.argument_comment_lint }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then [[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; } fi - if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' || '${{ github.event_name }}' == 'push' ]]; then + if [[ '${{ needs.changed.outputs.codex }}' == 'true' || '${{ needs.changed.outputs.workflows }}' == 'true' ]]; then [[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; } [[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; } - [[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; } - [[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; } fi - - - name: sccache summary note - if: always() - run: | - echo "Per-job sccache stats are attached to each matrix job's Step Summary." diff --git a/.github/workflows/rust-release-argument-comment-lint.yml b/.github/workflows/rust-release-argument-comment-lint.yml index a0d12d6db4..a6e88d8d3e 100644 --- a/.github/workflows/rust-release-argument-comment-lint.yml +++ b/.github/workflows/rust-release-argument-comment-lint.yml @@ -53,9 +53,9 @@ jobs: labels: codex-windows-x64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: dtolnay/rust-toolchain@1.93.0 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 with: toolchain: nightly-2025-09-18 targets: ${{ matrix.target }} @@ -97,7 +97,7 @@ jobs: (cd "${RUNNER_TEMP}" && tar -czf "$GITHUB_WORKSPACE/$archive_path" argument-comment-lint) fi - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: argument-comment-lint-${{ matrix.target }} path: dist/argument-comment-lint/${{ matrix.target }}/* diff --git a/.github/workflows/rust-release-prepare.yml b/.github/workflows/rust-release-prepare.yml index c9f11f54fc..b8151ae284 100644 --- a/.github/workflows/rust-release-prepare.yml +++ b/.github/workflows/rust-release-prepare.yml @@ -18,7 +18,7 @@ jobs: if: github.repository == 'openai/codex' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: main fetch-depth: 0 @@ -43,7 +43,7 @@ jobs: curl --http1.1 --fail --show-error --location "${headers[@]}" "${url}" | jq '.' > codex-rs/core/models.json - name: Open pull request (if changed) - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8 with: commit-message: "Update models.json" title: "Update models.json" diff --git a/.github/workflows/rust-release-windows.yml b/.github/workflows/rust-release-windows.yml index f762fbc4b5..f1aee51911 100644 --- a/.github/workflows/rust-release-windows.yml +++ b/.github/workflows/rust-release-windows.yml @@ -67,7 +67,7 @@ jobs: labels: codex-windows-arm64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Print runner specs (Windows) shell: powershell run: | @@ -82,7 +82,7 @@ jobs: Write-Host "Total RAM: $ramGiB GiB" Write-Host "Disk usage:" Get-PSDrive -PSProvider FileSystem | Format-Table -AutoSize Name, @{Name='Size(GB)';Expression={[math]::Round(($_.Used + $_.Free) / 1GB, 1)}}, @{Name='Free(GB)';Expression={[math]::Round($_.Free / 1GB, 1)}} - - uses: dtolnay/rust-toolchain@1.93.0 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 with: targets: ${{ matrix.target }} @@ -92,7 +92,7 @@ jobs: cargo build --target ${{ matrix.target }} --release --timings ${{ matrix.build_args }} - name: Upload Cargo timings - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }} path: codex-rs/target/**/cargo-timings/cargo-timing.html @@ -112,7 +112,7 @@ jobs: fi - name: Upload Windows binaries - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: windows-binaries-${{ matrix.target }}-${{ matrix.bundle }} path: | @@ -147,16 +147,16 @@ jobs: labels: codex-windows-arm64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Download prebuilt Windows primary binaries - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: windows-binaries-${{ matrix.target }}-primary path: codex-rs/target/${{ matrix.target }}/release - name: Download prebuilt Windows helper binaries - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: windows-binaries-${{ matrix.target }}-helpers path: codex-rs/target/${{ matrix.target }}/release @@ -193,7 +193,7 @@ jobs: cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe" - name: Install DotSlash - uses: facebook/install-dotslash@v2 + uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 - name: Compress artifacts shell: bash @@ -257,7 +257,7 @@ jobs: "${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base" done - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: ${{ matrix.target }} path: | diff --git a/.github/workflows/rust-release-zsh.yml b/.github/workflows/rust-release-zsh.yml index a0f71aa736..7ec49f9863 100644 --- a/.github/workflows/rust-release-zsh.yml +++ b/.github/workflows/rust-release-zsh.yml @@ -45,7 +45,7 @@ jobs: git \ libncursesw5-dev - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Build, smoke-test, and stage zsh artifact shell: bash @@ -53,7 +53,7 @@ jobs: "${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \ "dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}" - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: codex-zsh-${{ matrix.target }} path: dist/zsh/${{ matrix.target }}/* @@ -81,7 +81,7 @@ jobs: brew install autoconf fi - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Build, smoke-test, and stage zsh artifact shell: bash @@ -89,7 +89,7 @@ jobs: "${GITHUB_WORKSPACE}/.github/scripts/build-zsh-release-artifact.sh" \ "dist/zsh/${{ matrix.target }}/${{ matrix.archive_name }}" - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: codex-zsh-${{ matrix.target }} path: dist/zsh/${{ matrix.target }}/* diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 1ec9bd28ba..37dd9f34f9 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -19,8 +19,8 @@ jobs: tag-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.92 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: dtolnay/rust-toolchain@c2b55edffaf41a251c410bb32bed22afefa800f1 # 1.92 - name: Validate tag matches Cargo.toml version shell: bash run: | @@ -79,7 +79,7 @@ jobs: target: aarch64-unknown-linux-gnu steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Print runner specs (Linux) if: ${{ runner.os == 'Linux' }} shell: bash @@ -125,7 +125,7 @@ jobs: sudo apt-get update -y sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1 fi - - uses: dtolnay/rust-toolchain@1.93.0 + - uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0 with: targets: ${{ matrix.target }} @@ -235,7 +235,7 @@ jobs: cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy - name: Upload Cargo timings - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: cargo-timings-rust-release-${{ matrix.target }} path: codex-rs/target/**/cargo-timings/cargo-timing.html @@ -374,7 +374,7 @@ jobs: zstd -T0 -19 --rm "$dest/$base" done - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: ${{ matrix.target }} # Upload the per-binary .zst files as well as the new .tar.gz @@ -420,7 +420,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Generate release notes from tag commit message id: release_notes @@ -442,7 +442,7 @@ jobs: echo "path=${notes_path}" >> "${GITHUB_OUTPUT}" - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: path: dist @@ -492,12 +492,12 @@ jobs: fi - name: Setup pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: run_install: false - name: Setup Node.js for npm packaging - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 22 @@ -505,7 +505,7 @@ jobs: run: pnpm install --frozen-lockfile # stage_npm_packages.py requires DotSlash when staging releases. - - uses: facebook/install-dotslash@v2 + - uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 - name: Stage npm packages env: GH_TOKEN: ${{ github.token }} @@ -523,7 +523,7 @@ jobs: cp scripts/install/install.ps1 dist/install.ps1 - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 with: name: ${{ steps.release_name.outputs.name }} tag_name: ${{ github.ref_name }} @@ -533,21 +533,21 @@ jobs: # (e.g. -alpha, -beta). Otherwise publish a normal release. prerelease: ${{ contains(steps.release_name.outputs.name, '-') }} - - uses: facebook/dotslash-publish-release@v2 + - uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag: ${{ github.ref_name }} config: .github/dotslash-config.json - - uses: facebook/dotslash-publish-release@v2 + - uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag: ${{ github.ref_name }} config: .github/dotslash-zsh-config.json - - uses: facebook/dotslash-publish-release@v2 + - uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -582,7 +582,7 @@ jobs: steps: - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 22 registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/rusty-v8-release.yml b/.github/workflows/rusty-v8-release.yml index 60aac24366..d06fe0ae88 100644 --- a/.github/workflows/rusty-v8-release.yml +++ b/.github/workflows/rusty-v8-release.yml @@ -25,10 +25,10 @@ jobs: v8_version: ${{ steps.v8_version.outputs.version }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.12" @@ -75,13 +75,13 @@ jobs: target: aarch64-unknown-linux-musl steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Bazel - uses: bazelbuild/setup-bazelisk@v3 + uses: bazelbuild/setup-bazelisk@6ecf4fd8b7d1f9721785f1dd656a689acf9add47 # v3 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.12" @@ -135,7 +135,7 @@ jobs: --output-dir "dist/${TARGET}" - name: Upload staged musl artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }} path: dist/${{ matrix.target }}/* @@ -174,12 +174,12 @@ jobs: exit 1 fi - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: path: dist - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 with: tag_name: ${{ needs.metadata.outputs.release_tag }} name: ${{ needs.metadata.outputs.release_tag }} diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index c5026fe8c5..45c983ac1e 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Linux bwrap build dependencies shell: bash @@ -23,21 +23,82 @@ jobs: sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev - name: Setup pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: run_install: false - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 22 cache: pnpm - - uses: dtolnay/rust-toolchain@1.93.0 + - name: Set up Bazel CI + id: setup_bazel + uses: ./.github/actions/setup-bazel-ci + with: + target: x86_64-unknown-linux-gnu - - name: build codex - run: cargo build --bin codex - working-directory: codex-rs + - name: Build codex with Bazel + env: + BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + shell: bash + run: | + set -euo pipefail + # Use the shared CI wrapper so fork PRs fall back cleanly when + # BuildBuddy credentials are unavailable. This workflow needs the + # built `codex` binary on disk afterwards, so ask the wrapper to + # override CI's default remote_download_minimal behavior. + ./.github/scripts/run-bazel-ci.sh \ + --remote-download-toplevel \ + -- \ + build \ + --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ + --build_metadata=TAG_job=sdk \ + -- \ + //codex-rs/cli:codex + + # Resolve the exact output file using the same wrapper/config path as + # the build instead of guessing which Bazel convenience symlink is + # available on the runner. + cquery_output="$( + ./.github/scripts/run-bazel-ci.sh \ + -- \ + cquery \ + --output=files \ + -- \ + //codex-rs/cli:codex \ + | grep -E '^(/|bazel-out/)' \ + | tail -n 1 + )" + if [[ "${cquery_output}" = /* ]]; then + codex_bazel_output_path="${cquery_output}" + else + codex_bazel_output_path="${GITHUB_WORKSPACE}/${cquery_output}" + fi + if [[ -z "${codex_bazel_output_path}" ]]; then + echo "Bazel did not report an output path for //codex-rs/cli:codex." >&2 + exit 1 + fi + if [[ ! -e "${codex_bazel_output_path}" ]]; then + echo "Unable to locate the Bazel-built codex binary at ${codex_bazel_output_path}." >&2 + exit 1 + fi + + # Stage the binary into the workspace and point the SDK tests at that + # stable path. The tests spawn `codex` directly many times, so using a + # normal executable path is more reliable than invoking Bazel for each + # test process. + install_dir="${GITHUB_WORKSPACE}/.tmp/sdk-ci" + mkdir -p "${install_dir}" + install -m 755 "${codex_bazel_output_path}" "${install_dir}/codex" + echo "CODEX_EXEC_PATH=${install_dir}/codex" >> "$GITHUB_ENV" + + - name: Warm up Bazel-built codex + shell: bash + run: | + set -euo pipefail + "${CODEX_EXEC_PATH}" --version - name: Install dependencies run: pnpm install --frozen-lockfile @@ -50,3 +111,12 @@ jobs: - name: Test SDK packages run: pnpm -r --filter ./sdk/typescript run test + + - name: Save bazel repository cache + if: always() && !cancelled() && steps.setup_bazel.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: | + ~/.cache/bazel-repo-cache + key: bazel-cache-x86_64-unknown-linux-gnu-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} diff --git a/.github/workflows/v8-canary.yml b/.github/workflows/v8-canary.yml index 6e068e8021..0dc7dc0054 100644 --- a/.github/workflows/v8-canary.yml +++ b/.github/workflows/v8-canary.yml @@ -38,10 +38,10 @@ jobs: v8_version: ${{ steps.v8_version.outputs.version }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.12" @@ -72,13 +72,13 @@ jobs: target: aarch64-unknown-linux-musl steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Bazel - uses: bazelbuild/setup-bazelisk@v3 + uses: bazelbuild/setup-bazelisk@6ecf4fd8b7d1f9721785f1dd656a689acf9add47 # v3 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.12" @@ -126,7 +126,7 @@ jobs: --output-dir "dist/${TARGET}" - name: Upload staged musl artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }} path: dist/${{ matrix.target }}/* diff --git a/AGENTS.md b/AGENTS.md index 7a81eab40c..ff36b0e19c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,8 +70,6 @@ See `codex-rs/tui/styles.md`. ## TUI code conventions -- When a change lands in `codex-rs/tui` and `codex-rs/tui_app_server` has a parallel implementation of the same behavior, reflect the change in `codex-rs/tui_app_server` too unless there is a documented reason not to. - - Use concise styling helpers from ratatui’s Stylize trait. - Basic spans: use "text".into() - Styled spans: use "text".red(), "text".green(), "text".magenta(), "text".dim(), etc. diff --git a/BUILD.bazel b/BUILD.bazel index 0be4b711e6..3f59ff1160 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -17,12 +17,19 @@ platform( platform( name = "local_windows", constraint_values = [ - # We just need to pick one of the ABIs. Do the same one we target. "@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm", ], parents = ["@platforms//host"], ) +platform( + name = "local_windows_msvc", + constraint_values = [ + "@rules_rs//rs/experimental/platforms/constraints:windows_msvc", + ], + parents = ["@platforms//host"], +) + alias( name = "rbe", actual = "@rbe_platform", diff --git a/MODULE.bazel b/MODULE.bazel index 8564db701a..d1a32df5f3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,16 +3,35 @@ module(name = "codex") bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "llvm", version = "0.6.8") +# The upstream LLVM archive contains a few unix-only symlink entries and is +# missing a couple of MinGW compatibility archives that windows-gnullvm needs +# during extraction and linking, so patch it until upstream grows native support. +single_version_override( + module_name = "llvm", + patch_strip = 1, + patches = [ + "//patches:llvm_windows_symlink_extract.patch", + ], +) +# Abseil picks a MinGW pthread TLS path that does not match our hermetic +# windows-gnullvm toolchain; force it onto the portable C++11 thread-local path. +single_version_override( + module_name = "abseil-cpp", + patch_strip = 1, + patches = [ + "//patches:abseil_windows_gnullvm_thread_identity.patch", + ], +) register_toolchains("@llvm//toolchain:all") osx = use_extension("@llvm//extensions:osx.bzl", "osx") osx.from_archive( - sha256 = "6a4922f89487a96d7054ec6ca5065bfddd9f1d017c74d82f1d79cecf7feb8228", - strip_prefix = "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX26.2.sdk", + sha256 = "1bde70c0b1c2ab89ff454acbebf6741390d7b7eb149ca2a3ca24cc9203a408b7", + strip_prefix = "Payload/Library/Developer/CommandLineTools/SDKs/MacOSX26.4.sdk", type = "pkg", urls = [ - "https://swcdn.apple.com/content/downloads/26/44/047-81934-A_28TPKM5SD1/ps6pk6dk4x02vgfa5qsctq6tgf23t5f0w2/CLTools_macOSNMOS_SDK.pkg", + "https://swcdn.apple.com/content/downloads/32/53/047-96692-A_OAHIHT53YB/ybtshxmrcju8m2qvw3w5elr4rajtg1x3y3/CLTools_macOSNMOS_SDK.pkg", ], ) osx.frameworks(names = [ @@ -44,10 +63,77 @@ bazel_dep(name = "apple_support", version = "2.1.0") bazel_dep(name = "rules_cc", version = "0.2.16") bazel_dep(name = "rules_platform", version = "0.1.0") bazel_dep(name = "rules_rs", version = "0.0.43") +# `rules_rs` 0.0.43 does not model `windows-gnullvm` as a distinct Windows exec +# platform, so patch it until upstream grows that support for both x86_64 and +# aarch64. +single_version_override( + module_name = "rules_rs", + patch_strip = 1, + patches = [ + "//patches:rules_rs_windows_gnullvm_exec.patch", + ], + version = "0.0.43", +) rules_rust = use_extension("@rules_rs//rs/experimental:rules_rust.bzl", "rules_rust") +# Build-script probe binaries inherit CFLAGS/CXXFLAGS from Bazel's C++ +# toolchain. On `windows-gnullvm`, llvm-mingw does not ship +# `libssp_nonshared`, so strip the forwarded stack-protector flags there. +rules_rust.patch( + patches = [ + "//patches:rules_rust_windows_gnullvm_build_script.patch", + "//patches:rules_rust_windows_exec_msvc_build_script_env.patch", + "//patches:rules_rust_windows_bootstrap_process_wrapper_linker.patch", + "//patches:rules_rust_windows_msvc_direct_link_args.patch", + "//patches:rules_rust_windows_exec_bin_target.patch", + "//patches:rules_rust_windows_exec_std.patch", + "//patches:rules_rust_windows_exec_rustc_dev_rlib.patch", + "//patches:rules_rust_repository_set_exec_constraints.patch", + ], + strip = 1, +) use_repo(rules_rust, "rules_rust") +nightly_rust = use_extension( + "@rules_rs//rs/experimental:rules_rust_reexported_extensions.bzl", + "rust", +) +nightly_rust.toolchain( + versions = ["nightly/2025-09-18"], + dev_components = True, + edition = "2024", +) +# Keep Windows exec tools on MSVC so Bazel helper binaries link correctly, but +# lint crate targets as `windows-gnullvm` to preserve the repo's actual cfgs. +nightly_rust.repository_set( + name = "rust_windows_x86_64", + dev_components = True, + edition = "2024", + exec_triple = "x86_64-pc-windows-msvc", + exec_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@rules_rs//rs/experimental/platforms/constraints:windows_msvc", + ], + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@rules_rs//rs/experimental/platforms/constraints:windows_msvc", + ], + target_triple = "x86_64-pc-windows-msvc", + versions = ["nightly/2025-09-18"], +) +nightly_rust.repository_set( + name = "rust_windows_x86_64", + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@rules_rs//rs/experimental/platforms/constraints:windows_gnullvm", + ], + target_triple = "x86_64-pc-windows-gnullvm", +) +use_repo(nightly_rust, "rust_toolchains") + toolchains = use_extension("@rules_rs//rs/experimental/toolchains:module_extension.bzl", "toolchains") toolchains.toolchain( edition = "2024", @@ -56,6 +142,7 @@ toolchains.toolchain( use_repo(toolchains, "default_rust_toolchains") register_toolchains("@default_rust_toolchains//:all") +register_toolchains("@rust_toolchains//:all") crate = use_extension("@rules_rs//rs:extensions.bzl", "crate") crate.from_cargo( @@ -65,10 +152,33 @@ crate.from_cargo( "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-apple-darwin", + # Keep both Windows ABIs in the generated Cargo metadata: the V8 + # experiment still consumes release assets that only exist under the + # MSVC names while targeting the GNU toolchain. + "aarch64-pc-windows-msvc", "aarch64-pc-windows-gnullvm", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-pc-windows-gnullvm", + ], + use_experimental_platforms = True, +) +crate.from_cargo( + name = "argument_comment_lint_crates", + cargo_lock = "//tools/argument-comment-lint:Cargo.lock", + cargo_toml = "//tools/argument-comment-lint:Cargo.toml", + platform_triples = [ + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "aarch64-apple-darwin", + "aarch64-pc-windows-msvc", + "aarch64-pc-windows-gnullvm", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", "x86_64-pc-windows-gnullvm", ], use_experimental_platforms = True, @@ -89,10 +199,19 @@ crate.annotation( patch_args = ["-p1"], patches = [ "//patches:aws-lc-sys_memcmp_check.patch", + "//patches:aws-lc-sys_windows_msvc_prebuilt_nasm.patch", + "//patches:aws-lc-sys_windows_msvc_memcmp_probe.patch", ], ) +crate.annotation( + # The build script only validates embedded source/version metadata. + crate = "rustc_apfloat", + gen_build_script = "off", +) + inject_repo(crate, "zstd") +use_repo(crate, "argument_comment_lint_crates") bazel_dep(name = "bzip2", version = "1.0.8.bcr.3") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 7b3454a508..e1a08359b2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -611,7 +611,9 @@ "anstyle-query_1.1.5": "{\"dependencies\":[{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.60.2, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "anstyle-wincon_3.0.11": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"name\":\"once_cell_polyfill\",\"req\":\"^1.56.1\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.60.2, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "anstyle_1.0.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.5\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "anstyle_1.0.14": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.23\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "anyhow_1.0.101": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.51\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.6\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "anyhow_1.0.102": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.6\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"backtrace\":[],\"default\":[\"std\"],\"std\":[]}}", "arbitrary_1.4.2": "{\"dependencies\":[{\"name\":\"derive_arbitrary\",\"optional\":true,\"req\":\"~1.4.0\"},{\"kind\":\"dev\",\"name\":\"exhaustigen\",\"req\":\"^0.1.0\"}],\"features\":{\"derive\":[\"derive_arbitrary\"]}}", "arboard_3.6.1": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"clipboard-win\",\"req\":\"^5.3.1\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.2\"},{\"default_features\":false,\"features\":[\"png\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"default_features\":false,\"features\":[\"tiff\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"png\",\"bmp\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(windows)\"},{\"name\":\"log\",\"req\":\"^0.4\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"log\",\"req\":\"^0.4\",\"target\":\"cfg(windows)\"},{\"name\":\"objc2\",\"req\":\"^0.6.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"objc2-core-graphics\",\"NSPasteboard\",\"NSPasteboardItem\",\"NSImage\"],\"name\":\"objc2-app-kit\",\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"CFCGTypes\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"CGImage\",\"CGColorSpace\",\"CGDataProvider\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"NSArray\",\"NSString\",\"NSEnumerator\",\"NSGeometry\",\"NSValue\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3.1\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_DataExchange\",\"Win32_System_Memory\",\"Win32_System_Ole\",\"Win32_UI_Shell\"],\"name\":\"windows-sys\",\"req\":\">=0.52.0, <0.61.0\",\"target\":\"cfg(windows)\"},{\"name\":\"wl-clipboard-rs\",\"optional\":true,\"req\":\"^0.9.0\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"x11rb\",\"req\":\"^0.13\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"}],\"features\":{\"core-graphics\":[\"dep:objc2-core-graphics\"],\"default\":[\"image-data\"],\"image\":[\"dep:image\"],\"image-data\":[\"dep:objc2-core-graphics\",\"dep:objc2-core-foundation\",\"image\",\"windows-sys\",\"core-graphics\"],\"wayland-data-control\":[\"wl-clipboard-rs\"],\"windows-sys\":[\"windows-sys/Win32_Graphics_Gdi\"],\"wl-clipboard-rs\":[\"dep:wl-clipboard-rs\"]}}", "arc-swap_1.9.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adaptive-barrier\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"~0.7\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"~0.8\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"parking_lot\",\"req\":\"~0.12\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"rustversion\",\"req\":\"^1\"},{\"features\":[\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.130\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.177\"}],\"features\":{\"experimental-strategies\":[],\"experimental-thread-local\":[],\"internal-test-strategies\":[],\"weak\":[]}}", @@ -658,6 +660,7 @@ "bit-vec_0.6.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde_no_std\":[\"serde/alloc\"],\"serde_std\":[\"std\",\"serde/std\"],\"std\":[]}}", "bitflags_1.3.2": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3\"}],\"features\":{\"default\":[],\"example_generated\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\"]}}", "bitflags_2.10.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"arbitrary\",\"req\":\"^1.0\"},{\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.12\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.12.2\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.228\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde_lib\",\"package\":\"serde\",\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.18\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"zerocopy\",\"req\":\"^0.8\"}],\"features\":{\"example_generated\":[],\"serde\":[\"serde_core\"],\"std\":[]}}", + "bitflags_2.11.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"arbitrary\",\"req\":\"^1.0\"},{\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.12\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.12.2\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.228\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde_lib\",\"package\":\"serde\",\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.18\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"zerocopy\",\"req\":\"^0.8\"}],\"features\":{\"example_generated\":[],\"serde\":[\"serde_core\"],\"std\":[]}}", "block-buffer_0.10.4": "{\"dependencies\":[{\"name\":\"generic-array\",\"req\":\"^0.14\"}],\"features\":{}}", "block-padding_0.3.3": "{\"dependencies\":[{\"name\":\"generic-array\",\"req\":\"^0.14\"}],\"features\":{\"std\":[]}}", "block2_0.6.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"}],\"features\":{\"alloc\":[],\"compiler-rt\":[\"objc2/unstable-compiler-rt\"],\"default\":[\"std\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\"],\"std\":[\"alloc\"],\"unstable-coerce-pointee\":[],\"unstable-objfw\":[],\"unstable-private\":[],\"unstable-winobjc\":[\"gnustep-1-8\"]}}", @@ -677,10 +680,14 @@ "cached_proc_macro_0.25.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.20.8\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.49\"},{\"name\":\"quote\",\"req\":\"^1.0.6\"},{\"name\":\"syn\",\"req\":\"^2.0.52\"}],\"features\":{}}", "cached_proc_macro_types_0.1.1": "{\"dependencies\":[],\"features\":{}}", "calendrical_calculations_0.2.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"core_maths\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"}],\"features\":{\"logging\":[\"dep:log\"]}}", + "camino_1.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"name\":\"proptest\",\"optional\":true,\"req\":\"^1.0.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.223\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11.8\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"proptest1\":[\"dep:proptest\"],\"serde1\":[\"dep:serde_core\"]}}", + "cargo-platform_0.3.2": "{\"dependencies\":[{\"name\":\"serde\",\"req\":\"^1.0.228\",\"target\":\"cfg(any())\"},{\"name\":\"serde_core\",\"req\":\"^1.0.228\"}],\"features\":{}}", + "cargo_metadata_0.23.1": "{\"dependencies\":[{\"features\":[\"serde1\"],\"name\":\"camino\",\"req\":\"^1.1.10\"},{\"name\":\"cargo-platform\",\"req\":\"^0.3.0\"},{\"name\":\"derive_builder\",\"optional\":true,\"req\":\"^0.20\"},{\"features\":[\"serde\"],\"name\":\"semver\",\"req\":\"^1.0.26\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.219\"},{\"features\":[\"unbounded_depth\"],\"name\":\"serde_json\",\"req\":\"^1.0.142\"},{\"name\":\"thiserror\",\"req\":\"^2.0.12\"}],\"features\":{\"builder\":[\"derive_builder\"],\"default\":[],\"unstable\":[]}}", "cassowary_0.3.0": "{\"dependencies\":[],\"features\":{}}", "castaway_0.2.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\"},{\"name\":\"rustversion\",\"req\":\"^1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "cbc_0.1.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"aes\",\"req\":\"^0.8\"},{\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3.3\"}],\"features\":{\"alloc\":[\"cipher/alloc\"],\"block-padding\":[\"cipher/block-padding\"],\"default\":[\"block-padding\"],\"std\":[\"cipher/std\",\"alloc\"],\"zeroize\":[\"cipher/zeroize\"]}}", "cc_1.2.55": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", + "cc_1.2.56": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", "cesu8_1.1.0": "{\"dependencies\":[],\"features\":{\"unstable\":[]}}", "cexpr_0.6.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clang-sys\",\"req\":\">=0.13.0, <0.29.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7\"}],\"features\":{}}", "cfg-if_1.0.4": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"rustc-dep-of-std\":[\"core\"]}}", @@ -705,8 +712,10 @@ "color-spantrace_0.3.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ansi-parser\",\"req\":\"^0.8\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"name\":\"owo-colors\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.29\"},{\"name\":\"tracing-core\",\"req\":\"^0.1.21\"},{\"name\":\"tracing-error\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.4\"}],\"features\":{}}", "color_quant_1.1.0": "{\"dependencies\":[],\"features\":{}}", "colorchoice_1.0.4": "{\"dependencies\":[],\"features\":{}}", + "colorchoice_1.0.5": "{\"dependencies\":[],\"features\":{}}", "combine_4.6.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"bytes_05\",\"optional\":true,\"package\":\"bytes\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"bytes_05\",\"package\":\"bytes\",\"req\":\"^0.5\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"futures-03-dep\",\"package\":\"futures\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"futures-core-03\",\"optional\":true,\"package\":\"futures-core\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"futures-io-03\",\"optional\":true,\"package\":\"futures-io\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.3\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.0\"},{\"features\":[\"tokio\",\"quickcheck\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.3\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"quick-error\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.6\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio-02-dep\",\"optional\":true,\"package\":\"tokio\",\"req\":\"^0.2.3\"},{\"features\":[\"fs\",\"io-driver\",\"io-util\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio-02-dep\",\"package\":\"tokio\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"tokio-03-dep\",\"optional\":true,\"package\":\"tokio\",\"req\":\"^0.3\"},{\"features\":[\"fs\",\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio-03-dep\",\"package\":\"tokio\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"tokio-dep\",\"optional\":true,\"package\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"rt\",\"rt-multi-thread\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio-dep\",\"package\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"codec\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"futures-03\":[\"pin-project\",\"std\",\"futures-core-03\",\"futures-io-03\",\"pin-project-lite\"],\"mp4\":[],\"pin-project\":[\"pin-project-lite\"],\"std\":[\"memchr/std\",\"bytes\",\"alloc\"],\"tokio\":[\"tokio-dep\",\"tokio-util/io\",\"futures-core-03\",\"pin-project-lite\"],\"tokio-02\":[\"pin-project\",\"std\",\"tokio-02-dep\",\"futures-core-03\",\"pin-project-lite\",\"bytes_05\"],\"tokio-03\":[\"pin-project\",\"std\",\"tokio-03-dep\",\"futures-core-03\",\"pin-project-lite\"]}}", "compact_str_0.8.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"borsh\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"castaway\",\"req\":\"^0.2.3\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"diesel\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"markup\",\"optional\":true,\"req\":\"^0.13\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"proptest\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"size_32\"],\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"alloc\",\"size_32\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7\"},{\"name\":\"rustversion\",\"req\":\"^1\"},{\"name\":\"ryu\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"union\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"sqlx\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"static_assertions\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"test-case\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"test-strategy\",\"req\":\"^0.3\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"borsh\":[\"dep:borsh\"],\"bytes\":[\"dep:bytes\"],\"default\":[\"std\"],\"diesel\":[\"dep:diesel\"],\"markup\":[\"dep:markup\"],\"proptest\":[\"dep:proptest\"],\"quickcheck\":[\"dep:quickcheck\"],\"rkyv\":[\"dep:rkyv\"],\"serde\":[\"dep:serde\"],\"smallvec\":[\"dep:smallvec\"],\"sqlx\":[\"dep:sqlx\",\"std\"],\"sqlx-mysql\":[\"sqlx\",\"sqlx/mysql\"],\"sqlx-postgres\":[\"sqlx\",\"sqlx/postgres\"],\"sqlx-sqlite\":[\"sqlx\",\"sqlx/sqlite\"],\"std\":[]}}", + "compiletest_rs_0.11.2": "{\"dependencies\":[{\"name\":\"diff\",\"req\":\"^0.1.10\"},{\"name\":\"filetime\",\"req\":\"^0.2\"},{\"name\":\"getopts\",\"req\":\"^0.2\"},{\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"miow\",\"req\":\"^0.6\",\"target\":\"cfg(windows)\"},{\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"rustfix\",\"req\":\"^0.8\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.0\"},{\"name\":\"tester\",\"req\":\"^0.9\"},{\"features\":[\"Win32\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"rustc\":[],\"stable\":[],\"tmp\":[\"tempfile\"]}}", "concurrent-queue_2.5.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"crossbeam-utils\",\"req\":\"^0.8.11\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.1.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "console_0.15.11": "{\"dependencies\":[{\"name\":\"encode_unicode\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2.99\"},{\"name\":\"once_cell\",\"req\":\"^1.8\"},{\"default_features\":false,\"features\":[\"std\",\"bit-set\",\"break-dead-code\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.4.2\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\",\"Win32_Storage_FileSystem\",\"Win32_UI_Input_KeyboardAndMouse\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"ansi-parsing\":[],\"default\":[\"unicode-width\",\"ansi-parsing\"],\"windows-console-colors\":[\"ansi-parsing\"]}}", "const-hex_1.17.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"},{\"kind\":\"dev\",\"name\":\"divan\",\"package\":\"codspeed-divan-compat\",\"req\":\"^3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"faster-hex\",\"req\":\"^0.10.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"~0.4.2\"},{\"default_features\":false,\"name\":\"proptest\",\"optional\":true,\"req\":\"^1.4\"},{\"kind\":\"dev\",\"name\":\"rustc-hex\",\"req\":\"^2.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"__fuzzing\":[\"dep:proptest\",\"std\"],\"alloc\":[\"serde_core?/alloc\",\"proptest?/alloc\"],\"core-error\":[],\"default\":[\"std\"],\"force-generic\":[],\"hex\":[],\"nightly\":[],\"portable-simd\":[],\"serde\":[\"dep:serde_core\"],\"std\":[\"serde_core?/std\",\"proptest?/std\",\"alloc\"]}}", @@ -793,6 +802,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_5.0.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.10\"},{\"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\",\"packaging\",\"rustup\"],\"name\":\"dylint_internal\",\"req\":\"=5.0.0\"},{\"features\":[\"cargo\"],\"kind\":\"build\",\"name\":\"dylint_internal\",\"req\":\"=5.0.0\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"dylint_internal\",\"req\":\"=5.0.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\":\"if_chain\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"rewriter\",\"optional\":true,\"req\":\"^0.2\"},{\"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.23\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^0.9\"},{\"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\",\"if_chain\",\"rustc-stable-hash\",\"serde-untagged\",\"toml\",\"url\"],\"default\":[],\"library_packages\":[\"__cargo_cli\"],\"package_options\":[\"chrono\",\"dylint_internal/clippy_utils\",\"dylint_internal/git\",\"heck\",\"if_chain\",\"rewriter\",\"syntect\",\"walkdir\"]}}", + "dylint_internal_5.0.0": "{\"dependencies\":[{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.0\"},{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"cargo-util\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"cargo_metadata\",\"optional\":true,\"req\":\"^0.23\"},{\"name\":\"ctor\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.11\"},{\"name\":\"git2\",\"optional\":true,\"req\":\"^0.20\"},{\"name\":\"home\",\"optional\":true,\"req\":\"=0.5.9\"},{\"name\":\"if_chain\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"predicates\",\"req\":\"^3.1\"},{\"name\":\"regex\",\"req\":\"^1.11\"},{\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"semver\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"tar\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.23\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.23\"},{\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"toml\",\"optional\":true,\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.9\"},{\"name\":\"toml_edit\",\"optional\":true,\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"toml_edit\",\"req\":\"^0.23\"},{\"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\",\"if_chain\"],\"home\":[\"dep:home\",\"rustversion\"],\"match_def_path\":[],\"packaging\":[\"cargo\",\"tar\"],\"rustup\":[\"cargo_metadata\"],\"testing\":[\"ctor\",\"env_logger\",\"packaging\"]}}", + "dylint_linting_5.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_cmd\",\"req\":\"^2.0\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"features\":[\"config\"],\"name\":\"dylint_internal\",\"req\":\"=5.0.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.23\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"name\":\"toml\",\"req\":\"^0.9\"},{\"kind\":\"build\",\"name\":\"toml\",\"req\":\"^0.9\"}],\"features\":{\"constituent\":[]}}", + "dylint_testing_5.0.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"cargo_metadata\",\"req\":\"^0.23\"},{\"name\":\"compiletest_rs\",\"req\":\"^0.11\"},{\"name\":\"dylint\",\"req\":\"=5.0.0\"},{\"name\":\"dylint_internal\",\"req\":\"=5.0.0\"},{\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"regex\",\"req\":\"^1.11\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"tempfile\",\"req\":\"^3.23\"}],\"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\":{}}", "either_1.15.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[],\"use_std\":[\"std\"]}}", "ena_0.14.3": "{\"dependencies\":[{\"name\":\"dogged\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"log\",\"req\":\"^0.4\"}],\"features\":{\"bench\":[],\"persistent\":[\"dogged\"]}}", @@ -866,6 +879,7 @@ "getopts_0.2.24": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"default\":[\"unicode\"],\"rustc-dep-of-std\":[\"std\",\"core\"],\"unicode\":[\"dep:unicode-width\"]}}", "getrandom_0.2.17": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"wasi\",\"req\":\"^0.11\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.18\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"}],\"features\":{\"custom\":[],\"js\":[\"wasm-bindgen\",\"js-sys\"],\"linux_disable_fallback\":[],\"rdrand\":[],\"rustc-dep-of-std\":[\"compiler_builtins\",\"core\",\"libc/rustc-dep-of-std\",\"wasi/rustc-dep-of-std\"],\"std\":[],\"test-in-browser\":[]}}", "getrandom_0.3.4": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.77\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"netbsd\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"solaris\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"vxworks\\\")\"},{\"default_features\":false,\"name\":\"r-efi\",\"req\":\"^5.1\",\"target\":\"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\"},{\"default_features\":false,\"name\":\"wasip2\",\"req\":\"^1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.98\",\"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\":{\"std\":[],\"wasm_js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"]}}", + "getrandom_0.4.2": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.77\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"netbsd\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"solaris\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"vxworks\\\")\"},{\"default_features\":false,\"name\":\"r-efi\",\"req\":\"^6\",\"target\":\"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\"},{\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.10.0\"},{\"default_features\":false,\"name\":\"wasip2\",\"req\":\"^1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\"},{\"name\":\"wasip3\",\"req\":\"^0.4\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p3\\\"))\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.98\",\"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\":{\"std\":[],\"sys_rng\":[\"dep:rand_core\"],\"wasm_js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"]}}", "gif_0.14.1": "{\"dependencies\":[{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"png\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.10.0\"},{\"name\":\"weezl\",\"req\":\"^0.1.10\"}],\"features\":{\"color_quant\":[\"dep:color_quant\"],\"default\":[\"raii_no_panic\",\"std\",\"color_quant\"],\"raii_no_panic\":[],\"std\":[]}}", "gimli_0.32.3": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"}],\"features\":{\"default\":[\"read-all\",\"write\"],\"endian-reader\":[\"read\",\"dep:stable_deref_trait\"],\"fallible-iterator\":[\"dep:fallible-iterator\"],\"read\":[\"read-core\"],\"read-all\":[\"read\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"read-core\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[\"fallible-iterator?/std\",\"stable_deref_trait?/std\"],\"write\":[\"dep:indexmap\"]}}", "git+https://github.com/dzbarsky/rules_rust?rev=b56cbaa8465e74127f1ea216f813cd377295ad81#b56cbaa8465e74127f1ea216f813cd377295ad81_runfiles": "{\"dependencies\":[],\"features\":{},\"strip_prefix\":\"\"}", @@ -875,6 +889,8 @@ "git+https://github.com/nornagon/ratatui?branch=nornagon-v0.29.0-patch#9b2ad1298408c45918ee9f8241a6f95498cdbed2_ratatui": "{\"dependencies\":[{\"name\":\"bitflags\"},{\"name\":\"cassowary\"},{\"name\":\"compact_str\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"document-features\",\"optional\":true},{\"name\":\"indoc\"},{\"name\":\"instability\"},{\"name\":\"itertools\"},{\"name\":\"lru\"},{\"default_features\":true,\"features\":[],\"name\":\"palette\",\"optional\":true},{\"name\":\"paste\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"strum\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"termwiz\",\"optional\":true},{\"default_features\":true,\"features\":[\"local-offset\"],\"name\":\"time\",\"optional\":true},{\"name\":\"unicode-segmentation\"},{\"name\":\"unicode-truncate\"},{\"name\":\"unicode-width\"},{\"default_features\":true,\"features\":[],\"name\":\"termion\",\"optional\":true,\"target\":\"cfg(not(windows))\"}],\"features\":{\"all-widgets\":[\"widget-calendar\"],\"crossterm\":[\"dep:crossterm\"],\"default\":[\"crossterm\",\"underline-color\"],\"macros\":[],\"palette\":[\"dep:palette\"],\"scrolling-regions\":[],\"serde\":[\"dep:serde\",\"bitflags/serde\",\"compact_str/serde\"],\"termion\":[\"dep:termion\"],\"termwiz\":[\"dep:termwiz\"],\"underline-color\":[\"dep:crossterm\"],\"unstable\":[\"unstable-rendered-line-info\",\"unstable-widget-ref\",\"unstable-backend-writer\"],\"unstable-backend-writer\":[],\"unstable-rendered-line-info\":[],\"unstable-widget-ref\":[],\"widget-calendar\":[\"dep:time\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=132f5b39c862e3a970f731d709608b3e6276d5f6#132f5b39c862e3a970f731d709608b3e6276d5f6_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"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=9200079d3b54a1ff51072e24d81fd354f085156f#9200079d3b54a1ff51072e24d81fd354f085156f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true},{\"default_features\":false,\"features\":[\"zlib\"],\"name\":\"flate2\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"name\":\"rand\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true},{\"name\":\"utf-8\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"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=20ce69b9a63bcd2756cd906fe0964d1e901e042a#20ce69b9a63bcd2756cd906fe0964d1e901e042a_clippy_utils": "{\"dependencies\":[{\"default_features\":false,\"features\":[],\"name\":\"arrayvec\",\"optional\":false},{\"name\":\"itertools\"},{\"name\":\"rustc_apfloat\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":false}],\"features\":{},\"strip_prefix\":\"clippy_utils\"}", + "git2_0.20.4": "{\"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.3\"},{\"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.37\"},{\"name\":\"url\",\"req\":\"^2.5.4\"}],\"features\":{\"default\":[\"ssh\",\"https\"],\"https\":[\"libgit2-sys/https\",\"openssl-sys\",\"openssl-probe\"],\"ssh\":[\"libgit2-sys/ssh\"],\"unstable\":[],\"vendored-libgit2\":[\"libgit2-sys/vendored\"],\"vendored-openssl\":[\"openssl-sys/vendored\",\"libgit2-sys/vendored-openssl\"],\"zlib-ng-compat\":[\"libgit2-sys/zlib-ng-compat\"]}}", "glob_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"}],\"features\":{}}", "globset_0.4.18": "{\"dependencies\":[{\"name\":\"aho-corasick\",\"req\":\"^1.1.1\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-syntax\",\"req\":\"^0.8.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.107\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"log\"],\"serde1\":[\"serde\"],\"simd-accel\":[]}}", "gzip-header_1.0.0": "{\"dependencies\":[{\"name\":\"crc32fast\",\"req\":\"^1.2.1\"}],\"features\":{}}", @@ -895,8 +911,8 @@ "hkdf_0.12.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"blobby\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"hmac\",\"req\":\"^0.12.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"std\":[\"hmac/std\"]}}", "hmac_0.12.1": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.3\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"md-5\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha-1\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"reset\":[],\"std\":[\"digest/std\"]}}", "home_0.5.12": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "home_0.5.9": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "hostname_0.4.2": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"redox\\\"))\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.6.1\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"windows-bindgen\",\"req\":\"^0.65\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"default\":[],\"set\":[]}}", - "hound_3.5.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cpal\",\"req\":\"^0.2.12\"}],\"features\":{}}", "http-body-util_0.1.3": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\",\"sync\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{\"channel\":[\"dep:tokio\"],\"default\":[],\"full\":[\"channel\"]}}", "http-body_1.0.1": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"}],\"features\":{}}", "http-range-header_0.4.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.8.3\"}],\"features\":{}}", @@ -928,9 +944,11 @@ "icu_properties_2.1.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"icu_collections\",\"req\":\"~2.1.1\"},{\"default_features\":false,\"features\":[\"zerovec\"],\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"icu_properties_data\",\"optional\":true,\"req\":\"~2.1.2\"},{\"default_features\":false,\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"unicode-bidi\",\"optional\":true,\"req\":\"^0.3.11\"},{\"default_features\":false,\"features\":[\"yoke\",\"zerofrom\"],\"name\":\"zerotrie\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"zerovec/alloc\",\"icu_collections/alloc\",\"serde?/alloc\"],\"compiled_data\":[\"dep:icu_properties_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"zerovec/databake\",\"icu_collections/databake\",\"icu_locale_core/databake\",\"zerotrie/databake\",\"icu_provider/export\"],\"default\":[\"compiled_data\"],\"serde\":[\"dep:serde\",\"icu_locale_core/serde\",\"zerovec/serde\",\"icu_collections/serde\",\"icu_provider/serde\",\"zerotrie/serde\"],\"unicode_bidi\":[\"dep:unicode-bidi\"]}}", "icu_properties_data_2.1.2": "{\"dependencies\":[],\"features\":{}}", "icu_provider_2.1.1": "{\"dependencies\":[{\"name\":\"bincode\",\"optional\":true,\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"name\":\"erased-serde\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"default_features\":false,\"name\":\"postcard\",\"optional\":true,\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.45\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"writeable\",\"optional\":true,\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerotrie\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"icu_locale_core/alloc\",\"serde?/alloc\",\"yoke/alloc\",\"zerofrom/alloc\",\"zerovec/alloc\",\"zerotrie?/alloc\",\"dep:stable_deref_trait\",\"dep:writeable\"],\"baked\":[\"dep:zerotrie\",\"dep:writeable\"],\"deserialize_bincode_1\":[\"serde\",\"dep:bincode\",\"std\"],\"deserialize_json\":[\"serde\",\"dep:serde_json\"],\"deserialize_postcard_1\":[\"serde\",\"dep:postcard\"],\"export\":[\"serde\",\"dep:erased-serde\",\"dep:databake\",\"std\",\"sync\",\"dep:postcard\",\"zerovec/databake\"],\"logging\":[\"dep:log\"],\"serde\":[\"dep:serde\",\"yoke/serde\"],\"std\":[\"alloc\"],\"sync\":[],\"zerotrie\":[]}}", + "id-arena_2.3.0": "{\"dependencies\":[{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0.3\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "ident_case_1.0.1": "{\"dependencies\":[],\"features\":{}}", "idna_1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"name\":\"idna_adapter\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\"],\"name\":\"smallvec\",\"req\":\"^1.13.1\"},{\"kind\":\"dev\",\"name\":\"tester\",\"req\":\"^0.9\"},{\"name\":\"utf8_iter\",\"req\":\"^1.0.4\"}],\"features\":{\"alloc\":[],\"compiled_data\":[\"idna_adapter/compiled_data\"],\"default\":[\"std\",\"compiled_data\"],\"std\":[\"alloc\"]}}", "idna_adapter_1.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"icu_normalizer\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"icu_properties\",\"req\":\"^2\"}],\"features\":{\"compiled_data\":[\"icu_normalizer/compiled_data\",\"icu_properties/compiled_data\"]}}", + "if_chain_1.0.3": "{\"dependencies\":[],\"features\":{}}", "ignore_0.4.25": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-deque\",\"req\":\"^0.8.3\"},{\"name\":\"globset\",\"req\":\"^0.4.18\"},{\"name\":\"log\",\"req\":\"^0.4.20\"},{\"name\":\"memchr\",\"req\":\"^2.6.3\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\",\"dfa-onepass\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"name\":\"same-file\",\"req\":\"^1.0.6\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"simd-accel\":[]}}", "image-webp_0.2.4": "{\"dependencies\":[{\"name\":\"byteorder-lite\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.14\"},{\"kind\":\"dev\",\"name\":\"png\",\"req\":\"^0.17.12\"},{\"name\":\"quick-error\",\"req\":\"^2.0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"webp\",\"req\":\"^0.3.0\"}],\"features\":{\"_benchmarks\":[]}}", "image_0.25.9": "{\"dependencies\":[{\"features\":[\"extern_crate_alloc\"],\"name\":\"bytemuck\",\"req\":\"^1.8.0\"},{\"name\":\"byteorder-lite\",\"req\":\"^0.1.0\"},{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\"},{\"name\":\"dav1d\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"exr\",\"optional\":true,\"req\":\"^1.74.0\"},{\"name\":\"gif\",\"optional\":true,\"req\":\"^0.14.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"name\":\"image-webp\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"moxcms\",\"req\":\"^0.7.4\"},{\"name\":\"mp4parse\",\"optional\":true,\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"num-complex\",\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2.0\"},{\"name\":\"png\",\"optional\":true,\"req\":\"^0.18.0\"},{\"name\":\"qoi\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"ravif\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7.0\"},{\"default_features\":false,\"name\":\"rgb\",\"optional\":true,\"req\":\"^0.8.48\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.214\"},{\"name\":\"tiff\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"zune-core\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"zune-jpeg\",\"optional\":true,\"req\":\"^0.5.5\"}],\"features\":{\"avif\":[\"dep:ravif\",\"dep:rgb\"],\"avif-native\":[\"dep:mp4parse\",\"dep:dav1d\"],\"benchmarks\":[],\"bmp\":[],\"color_quant\":[\"dep:color_quant\"],\"dds\":[],\"default\":[\"rayon\",\"default-formats\"],\"default-formats\":[\"avif\",\"bmp\",\"dds\",\"exr\",\"ff\",\"gif\",\"hdr\",\"ico\",\"jpeg\",\"png\",\"pnm\",\"qoi\",\"tga\",\"tiff\",\"webp\"],\"exr\":[\"dep:exr\"],\"ff\":[],\"gif\":[\"dep:gif\",\"dep:color_quant\"],\"hdr\":[],\"ico\":[\"bmp\",\"png\"],\"jpeg\":[\"dep:zune-core\",\"dep:zune-jpeg\"],\"nasm\":[\"ravif?/asm\"],\"png\":[\"dep:png\"],\"pnm\":[],\"qoi\":[\"dep:qoi\"],\"rayon\":[\"dep:rayon\",\"ravif?/threading\",\"exr?/rayon\"],\"serde\":[\"dep:serde\"],\"tga\":[],\"tiff\":[\"dep:tiff\"],\"webp\":[\"dep:image-webp\"]}}", @@ -957,12 +975,15 @@ "is_ci_1.2.0": "{\"dependencies\":[],\"features\":{}}", "is_terminal_polyfill_1.70.2": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "itertools_0.10.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"= 0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", + "itertools_0.12.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.13.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.14.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itoa_1.0.17": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "ixdtf_0.6.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"serde-json-core\",\"req\":\"^0.6.0\"}],\"features\":{\"default\":[\"duration\"],\"duration\":[]}}", "jiff-static_0.2.18": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", + "jiff-static_0.2.23": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", "jiff_0.2.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.18\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", + "jiff_0.2.23": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.23\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"time-tz\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", "jni-sys_0.3.0": "{\"dependencies\":[],\"features\":{}}", "jni_0.21.1": "{\"dependencies\":[{\"name\":\"cesu8\",\"req\":\"^1.1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"combine\",\"req\":\"^4.1.0\"},{\"name\":\"java-locator\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"jni-sys\",\"req\":\"^0.3.0\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"name\":\"thiserror\",\"req\":\"^1.0.20\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rusty-fork\",\"req\":\"^0.3.0\"},{\"kind\":\"build\",\"name\":\"walkdir\",\"req\":\"^2\"},{\"features\":[\"Win32_Globalization\"],\"name\":\"windows-sys\",\"req\":\"^0.45.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"invocation\":[\"java-locator\",\"libloading\"]}}", "jobserver_0.1.34": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.3.2\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2.171\",\"target\":\"cfg(unix)\"},{\"features\":[\"fs\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.28.0\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"}],\"features\":{}}", @@ -976,16 +997,23 @@ "landlock_0.4.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"enumflags2\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2.175\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.26\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"}],\"features\":{}}", "language-tags_0.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", "lazy_static_1.5.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"once\"],\"name\":\"spin\",\"optional\":true,\"req\":\"^0.9.8\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{\"spin_no_std\":[\"spin\"]}}", + "leb128fmt_0.1.0": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[]}}", "libc_0.2.182": "{\"dependencies\":[{\"name\":\"rustc-std-workspace-core\",\"optional\":true,\"req\":\"^1.0.1\"}],\"features\":{\"align\":[],\"const-extern-fn\":[],\"default\":[\"std\"],\"extra_traits\":[],\"rustc-dep-of-std\":[\"align\",\"rustc-std-workspace-core\"],\"std\":[],\"use_std\":[\"std\"]}}", + "libc_0.2.183": "{\"dependencies\":[{\"name\":\"rustc-std-workspace-core\",\"optional\":true,\"req\":\"^1.0.1\"}],\"features\":{\"align\":[],\"const-extern-fn\":[],\"default\":[\"std\"],\"extra_traits\":[],\"rustc-dep-of-std\":[\"align\",\"rustc-std-workspace-core\"],\"std\":[],\"use_std\":[\"std\"]}}", "libdbus-sys_0.2.7": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.0.78\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"default\":[\"pkg-config\"],\"vendored\":[\"cc\"]}}", + "libgit2-sys_0.18.3+1.9.2": "{\"dependencies\":[{\"features\":[\"parallel\"],\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.43\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"libssh2-sys\",\"optional\":true,\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"libc\"],\"name\":\"libz-sys\",\"req\":\"^1.1.0\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.45\",\"target\":\"cfg(unix)\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.15\"}],\"features\":{\"https\":[\"openssl-sys\"],\"ssh\":[\"libssh2-sys\"],\"vendored\":[],\"vendored-openssl\":[\"openssl-sys/vendored\"],\"zlib-ng-compat\":[\"libz-sys/zlib-ng\",\"libssh2-sys?/zlib-ng-compat\"]}}", "libloading_0.8.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "libm_0.2.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"no-panic\",\"req\":\"^0.1.35\"}],\"features\":{\"arch\":[],\"default\":[\"arch\"],\"force-soft-floats\":[],\"unstable\":[\"unstable-intrinsics\",\"unstable-float\"],\"unstable-float\":[],\"unstable-intrinsics\":[],\"unstable-public-internals\":[]}}", "libredox_0.1.12": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"ioslice\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"redox_syscall\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"call\":[],\"default\":[\"call\",\"std\",\"redox_syscall\"],\"mkns\":[\"ioslice\"],\"std\":[]}}", + "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.30.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.69\"},{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.1.6\"},{\"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.72\"},{\"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\":[],\"default\":[\"min_sqlite_version_3_14_0\"],\"in_gecko\":[],\"loadable_extension\":[\"prettyplease\",\"quote\",\"syn\"],\"min_sqlite_version_3_14_0\":[\"pkg-config\",\"vcpkg\"],\"preupdate_hook\":[\"buildtime_bindgen\"],\"session\":[\"preupdate_hook\",\"buildtime_bindgen\"],\"sqlcipher\":[],\"unlock_notify\":[],\"wasm32-wasi-vfs\":[],\"with-asan\":[]}}", + "libssh2-sys_0.3.1": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.25\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"libc\"],\"name\":\"libz-sys\",\"req\":\"^1.1.0\"},{\"name\":\"openssl-sys\",\"req\":\"^0.9.35\",\"target\":\"cfg(unix)\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.35\",\"target\":\"cfg(windows)\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.11\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2\",\"target\":\"cfg(target_env = \\\"msvc\\\")\"}],\"features\":{\"openssl-on-win32\":[\"openssl-sys\"],\"vendored-openssl\":[\"openssl-sys/vendored\"],\"zlib-ng-compat\":[\"libz-sys/zlib-ng\"]}}", "libz-sys_1.1.23": "{\"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.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\"]}}", "linked-hash-map_0.5.6": "{\"dependencies\":[{\"name\":\"heapsize\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"heapsize_impl\":[\"heapsize\"],\"nightly\":[],\"serde_impl\":[\"serde\"]}}", "linux-keyutils_0.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.11\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.132\"},{\"kind\":\"dev\",\"name\":\"zeroize\",\"req\":\"^1.5.7\"}],\"features\":{\"default\":[],\"std\":[\"bitflags/std\"]}}", "linux-raw-sys_0.11.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", + "linux-raw-sys_0.12.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"if_tun\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"vm_sockets\":[],\"xdp\":[]}}", "linux-raw-sys_0.4.15": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", "litemap_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\",\"alloc\"],\"testing\":[\"alloc\"],\"yoke\":[\"dep:yoke\"]}}", "local-waker_0.1.4": "{\"dependencies\":[],\"features\":{}}", @@ -1008,6 +1036,7 @@ "md-5_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"md5-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"md5-asm\"],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "md5_0.8.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "memchr_2.7.6": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", + "memchr_2.8.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", "memoffset_0.6.5": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[]}}", "memoffset_0.9.1": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[],\"unstable_offset_of\":[]}}", "mime_0.3.17": "{\"dependencies\":[],\"features\":{}}", @@ -1015,6 +1044,7 @@ "minimal-lexical_0.2.1": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"compact\":[],\"default\":[\"std\"],\"lint\":[],\"nightly\":[],\"std\":[]}}", "miniz_oxide_0.8.9": "{\"dependencies\":[{\"default_features\":false,\"name\":\"adler2\",\"req\":\"^2.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.3\"}],\"features\":{\"block-boundary\":[],\"default\":[\"with-alloc\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"adler2/rustc-dep-of-std\"],\"simd\":[\"simd-adler32\"],\"std\":[],\"with-alloc\":[]}}", "mio_1.1.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"wasi\",\"req\":\"^0.11.0\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"features\":[\"Wdk_Foundation\",\"Wdk_Storage_FileSystem\",\"Wdk_System_IO\",\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Storage_FileSystem\",\"Win32_Security\",\"Win32_System_IO\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"log\"],\"net\":[],\"os-ext\":[\"os-poll\",\"windows-sys/Win32_System_Pipes\",\"windows-sys/Win32_Security\"],\"os-poll\":[]}}", + "miow_0.6.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"socket2\",\"req\":\"^0.6.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_IO\",\"Win32_System_Pipes\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\">=0.60, <=0.61\"}],\"features\":{}}", "moka_0.12.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.8\"},{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.19\"},{\"name\":\"async-lock\",\"optional\":true,\"req\":\"^3.3\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-epoch\",\"req\":\"^0.9.18\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.0\"},{\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"name\":\"event-listener\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(moka_loom)\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.7\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"portable-atomic\",\"req\":\"^1.6\"},{\"name\":\"quanta\",\"optional\":true,\"req\":\"^0.12.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"default_features\":false,\"features\":[\"rustls-tls\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"name\":\"smallvec\",\"req\":\"^1.8\"},{\"name\":\"tagptr\",\"req\":\"^0.2\"},{\"features\":[\"fs\",\"io-util\",\"macros\",\"rt-multi-thread\",\"sync\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\",\"target\":\"cfg(trybuild)\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"req\":\"^1.1\"}],\"features\":{\"atomic64\":[],\"default\":[],\"future\":[\"async-lock\",\"event-listener\",\"futures-util\"],\"logging\":[\"log\"],\"quanta\":[\"dep:quanta\"],\"sync\":[],\"unstable-debug-counters\":[\"future\"]}}", "moxcms_0.7.11": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"pxfm\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"avx\":[],\"avx512\":[],\"default\":[\"avx\",\"sse\",\"neon\"],\"neon\":[],\"options\":[],\"sse\":[]}}", "multimap_0.10.1": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"serde_impl\"],\"serde_impl\":[\"serde\"]}}", @@ -1068,6 +1098,7 @@ "oboe_0.6.1": "{\"dependencies\":[{\"name\":\"jni\",\"optional\":true,\"req\":\"^0.21\"},{\"default_features\":false,\"name\":\"ndk\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"ndk-context\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"num-derive\",\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"oboe-sys\",\"req\":\"^0.6\"}],\"features\":{\"doc-cfg\":[],\"fetch-prebuilt\":[\"oboe-sys/fetch-prebuilt\"],\"generate-bindings\":[\"oboe-sys/generate-bindings\"],\"java-interface\":[\"ndk\",\"ndk-context\",\"jni\"],\"shared-link\":[\"oboe-sys/shared-link\"],\"shared-stdcxx\":[\"oboe-sys/shared-stdcxx\"]}}", "oid-registry_0.8.1": "{\"dependencies\":[{\"name\":\"asn1-rs\",\"req\":\"^0.7\"}],\"features\":{\"crypto\":[\"kdf\",\"pkcs1\",\"pkcs7\",\"pkcs9\",\"pkcs12\",\"nist_algs\",\"x962\"],\"default\":[\"registry\"],\"kdf\":[],\"ms_spc\":[],\"nist_algs\":[],\"pkcs1\":[],\"pkcs12\":[],\"pkcs7\":[],\"pkcs9\":[],\"registry\":[],\"x500\":[],\"x509\":[],\"x962\":[]}}", "once_cell_1.21.3": "{\"dependencies\":[{\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.1.3\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"parking_lot_core\",\"optional\":true,\"req\":\"^0.9.10\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"}],\"features\":{\"alloc\":[\"race\"],\"atomic-polyfill\":[\"critical-section\"],\"critical-section\":[\"dep:critical-section\",\"portable-atomic\"],\"default\":[\"std\"],\"parking_lot\":[\"dep:parking_lot_core\"],\"portable-atomic\":[\"dep:portable-atomic\"],\"race\":[],\"std\":[\"alloc\"],\"unstable\":[]}}", + "once_cell_1.21.4": "{\"dependencies\":[{\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.1.3\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"parking_lot_core\",\"optional\":true,\"req\":\"^0.9.10\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"}],\"features\":{\"alloc\":[\"race\"],\"atomic-polyfill\":[\"critical-section\"],\"critical-section\":[\"dep:critical-section\",\"portable-atomic\"],\"default\":[\"std\"],\"parking_lot\":[\"dep:parking_lot_core\"],\"portable-atomic\":[\"dep:portable-atomic\"],\"race\":[],\"std\":[\"alloc\"],\"unstable\":[]}}", "once_cell_polyfill_1.70.2": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "onig_6.5.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(windows)\"},{\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"onig_sys\",\"req\":\"^69.9.1\"}],\"features\":{\"default\":[\"generate\"],\"generate\":[\"onig_sys/generate\"],\"posix-api\":[\"onig_sys/posix-api\"],\"print-debug\":[\"onig_sys/print-debug\"],\"std-pattern\":[]}}", "onig_sys_69.9.1": "{\"dependencies\":[{\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.71\"},{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.16\"}],\"features\":{\"default\":[\"generate\"],\"generate\":[\"bindgen\"],\"posix-api\":[],\"print-debug\":[]}}", @@ -1077,6 +1108,7 @@ "openssl-probe_0.2.1": "{\"dependencies\":[],\"features\":{}}", "openssl-src_300.5.5+3.5.5": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.0.79\"}],\"features\":{\"camellia\":[],\"default\":[],\"force-engine\":[],\"idea\":[],\"ktls\":[],\"legacy\":[],\"no-dso\":[],\"seed\":[],\"ssl3\":[],\"weak-crypto\":[]}}", "openssl-sys_0.9.111": "{\"dependencies\":[{\"features\":[\"ssl\",\"bindgen\"],\"name\":\"aws-lc-fips-sys\",\"optional\":true,\"req\":\"^0.13\"},{\"features\":[\"ssl\"],\"name\":\"aws-lc-sys\",\"optional\":true,\"req\":\"^0.27\"},{\"features\":[\"experimental\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72.0\"},{\"name\":\"bssl-sys\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.61\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"features\":[\"legacy\"],\"kind\":\"build\",\"name\":\"openssl-src\",\"optional\":true,\"req\":\"^300.2.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.8\"}],\"features\":{\"aws-lc\":[\"dep:aws-lc-sys\"],\"aws-lc-fips\":[\"dep:aws-lc-fips-sys\"],\"unstable_boringssl\":[\"bssl-sys\"],\"vendored\":[\"openssl-src\"]}}", + "openssl-sys_0.9.112": "{\"dependencies\":[{\"features\":[\"ssl\",\"bindgen\"],\"name\":\"aws-lc-fips-sys\",\"optional\":true,\"req\":\"^0.13\"},{\"features\":[\"ssl\"],\"name\":\"aws-lc-sys\",\"optional\":true,\"req\":\"^0.38\"},{\"features\":[\"experimental\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72.0\"},{\"name\":\"bssl-sys\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.61\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"features\":[\"legacy\"],\"kind\":\"build\",\"name\":\"openssl-src\",\"optional\":true,\"req\":\"^300.2.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.8\"}],\"features\":{\"aws-lc\":[\"dep:aws-lc-sys\"],\"aws-lc-fips\":[\"dep:aws-lc-fips-sys\"],\"unstable_boringssl\":[\"bssl-sys\"],\"vendored\":[\"openssl-src\"]}}", "openssl_0.10.75": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.2.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"ffi\",\"package\":\"openssl-sys\",\"req\":\"^0.9.111\"},{\"name\":\"foreign-types\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.5.2\"},{\"name\":\"openssl-macros\",\"req\":\"^0.1.1\"}],\"features\":{\"aws-lc\":[\"ffi/aws-lc\"],\"aws-lc-fips\":[\"ffi/aws-lc-fips\"],\"bindgen\":[\"ffi/bindgen\"],\"default\":[],\"unstable_boringssl\":[\"ffi/unstable_boringssl\"],\"v101\":[],\"v102\":[],\"v110\":[],\"v111\":[],\"vendored\":[\"ffi/vendored\"]}}", "opentelemetry-appender-tracing_0.31.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"features\":[\"logs\"],\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"logs\"],\"kind\":\"dev\",\"name\":\"opentelemetry-stdout\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"logs\",\"testing\",\"internal-logs\"],\"kind\":\"dev\",\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.14\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\">=0.1.40\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\">=0.1.40\"},{\"default_features\":false,\"name\":\"tracing-core\",\"req\":\">=0.1.33\"},{\"name\":\"tracing-log\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"tracing-log\",\"req\":\"^0.2\"},{\"name\":\"tracing-opentelemetry\",\"optional\":true,\"req\":\"^0.32\"},{\"default_features\":false,\"features\":[\"registry\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"env-filter\",\"registry\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"experimental_metadata_attributes\":[\"dep:tracing-log\"],\"experimental_use_tracing_span_context\":[\"tracing-opentelemetry\"],\"spec_unstable_logs_enabled\":[\"opentelemetry/spec_unstable_logs_enabled\"]}}", "opentelemetry-http_0.31.0": "{\"dependencies\":[{\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"hyper\",\"optional\":true,\"req\":\"^1.3\"},{\"features\":[\"client-legacy\",\"http1\",\"http2\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"time\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[\"internal-logs\"],\"hyper\":[\"dep:http-body-util\",\"dep:hyper\",\"dep:hyper-util\",\"dep:tokio\"],\"internal-logs\":[\"opentelemetry/internal-logs\"],\"reqwest\":[\"dep:reqwest\"],\"reqwest-blocking\":[\"dep:reqwest\",\"reqwest/blocking\"],\"reqwest-rustls\":[\"dep:reqwest\",\"reqwest/rustls-tls-native-roots\"],\"reqwest-rustls-webpki-roots\":[\"dep:reqwest\",\"reqwest/rustls-tls-webpki-roots\"]}}", @@ -1107,12 +1139,14 @@ "phf_shared_0.11.3": "{\"dependencies\":[{\"name\":\"siphasher\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"uncased\",\"optional\":true,\"req\":\"^0.9.9\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "pin-project-internal_1.1.10": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1.0.25\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\",\"full\",\"visit-mut\"],\"name\":\"syn\",\"req\":\"^2.0.1\"}],\"features\":{}}", "pin-project-lite_0.2.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"}],\"features\":{}}", + "pin-project-lite_0.2.17": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"}],\"features\":{}}", "pin-project_1.1.10": "{\"dependencies\":[{\"name\":\"pin-project-internal\",\"req\":\"=1.1.10\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"}],\"features\":{}}", "pin-utils_0.1.0": "{\"dependencies\":[],\"features\":{}}", "piper_0.2.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-channel\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"async-executor\",\"req\":\"^1.5.1\"},{\"kind\":\"dev\",\"name\":\"async-io\",\"req\":\"^2.0.0\"},{\"name\":\"atomic-waker\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.2.0\"},{\"default_features\":false,\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.28\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"features\":[\"alloc\"],\"name\":\"portable-atomic-util\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"portable_atomic_crate\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.2.0\"}],\"features\":{\"default\":[\"std\"],\"portable-atomic\":[\"atomic-waker/portable-atomic\",\"portable_atomic_crate\",\"portable-atomic-util\"],\"std\":[\"fastrand/std\",\"futures-io\"]}}", "pkcs1_0.7.5": "{\"dependencies\":[{\"features\":[\"db\"],\"kind\":\"dev\",\"name\":\"const-oid\",\"req\":\"^0.9\"},{\"features\":[\"oid\"],\"name\":\"der\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"pkcs8\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"spki\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"alloc\":[\"der/alloc\",\"zeroize\",\"pkcs8?/alloc\"],\"pem\":[\"alloc\",\"der/pem\",\"pkcs8?/pem\"],\"std\":[\"der/std\",\"alloc\"],\"zeroize\":[\"der/zeroize\"]}}", "pkcs8_0.10.2": "{\"dependencies\":[{\"features\":[\"oid\"],\"name\":\"der\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"name\":\"pkcs5\",\"optional\":true,\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"spki\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"name\":\"subtle\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"3des\":[\"encryption\",\"pkcs5/3des\"],\"alloc\":[\"der/alloc\",\"der/zeroize\",\"spki/alloc\"],\"des-insecure\":[\"encryption\",\"pkcs5/des-insecure\"],\"encryption\":[\"alloc\",\"pkcs5/alloc\",\"pkcs5/pbes2\",\"rand_core\"],\"getrandom\":[\"rand_core/getrandom\"],\"pem\":[\"alloc\",\"der/pem\",\"spki/pem\"],\"sha1-insecure\":[\"encryption\",\"pkcs5/sha1-insecure\"],\"std\":[\"alloc\",\"der/std\",\"spki/std\"]}}", "pkg-config_0.3.32": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"}],\"features\":{}}", + "plain_0.2.3": "{\"dependencies\":[],\"features\":{}}", "plist_1.8.0": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22.0\"},{\"name\":\"indexmap\",\"req\":\"^2.1.0\"},{\"name\":\"quick_xml\",\"package\":\"quick-xml\",\"req\":\"^0.38.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.8.21\"},{\"features\":[\"parsing\",\"formatting\"],\"name\":\"time\",\"req\":\"^0.3.30\"}],\"features\":{\"default\":[\"serde\"],\"enable_unstable_features_that_may_break_with_minor_version_bumps\":[]}}", "png_0.18.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"approx\",\"req\":\"^0.5.1\"},{\"name\":\"bitflags\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"byteorder\",\"req\":\"^1.5.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.0\"},{\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"fdeflate\",\"req\":\"^0.3.3\"},{\"name\":\"flate2\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"features\":[\"simd\"],\"name\":\"miniz_oxide\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.2\"}],\"features\":{\"benchmarks\":[],\"unstable\":[\"crc32fast/nightly\"],\"zlib-rs\":[\"flate2/zlib-rs\"]}}", "polling_3.11.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"concurrent-queue\",\"req\":\"^2.2.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.1.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"hermit-abi\",\"req\":\"^0.5.0\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.9\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"features\":[\"event\",\"fs\",\"pipe\",\"process\",\"std\",\"time\"],\"name\":\"rustix\",\"req\":\"^1.0.5\",\"target\":\"cfg(any(unix, target_os = \\\"fuchsia\\\", target_os = \\\"vxworks\\\"))\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"^0.3.17\",\"target\":\"cfg(all(unix, not(target_os=\\\"vita\\\")))\"},{\"kind\":\"dev\",\"name\":\"socket2\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.37\"},{\"features\":[\"Wdk_Foundation\",\"Wdk_Storage_FileSystem\",\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_IO\",\"Win32_System_LibraryLoader\",\"Win32_System_Threading\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", @@ -1148,7 +1182,9 @@ "quinn-udp_0.5.14": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"async_tokio\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"name\":\"libc\",\"req\":\"^0.2.158\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.19\",\"target\":\"cfg(windows)\"},{\"name\":\"socket2\",\"req\":\">=0.5, <0.7\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"features\":[\"sync\",\"rt\",\"rt-multi-thread\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.10\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_IO\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.60\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"tracing\",\"log\"],\"direct-log\":[\"dep:log\"],\"fast-apple-datapath\":[],\"log\":[\"tracing/log\"]}}", "quinn_0.11.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.22\"},{\"name\":\"async-io\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.11\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"crc\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"directories-next\",\"req\":\"^2\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.19\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"proto\",\"package\":\"quinn-proto\",\"req\":\"^0.11.12\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.5\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"smol\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"socket2\",\"req\":\">=0.5, <0.7\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"thiserror\",\"req\":\"^2.0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"features\":[\"sync\",\"rt\",\"rt-multi-thread\",\"time\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.10\"},{\"default_features\":false,\"features\":[\"std-future\"],\"kind\":\"dev\",\"name\":\"tracing-futures\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\",\"ansi\",\"time\",\"local-time\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"tracing\"],\"name\":\"udp\",\"package\":\"quinn-udp\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2\"},{\"name\":\"web-time\",\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"}],\"features\":{\"aws-lc-rs\":[\"proto/aws-lc-rs\"],\"aws-lc-rs-fips\":[\"proto/aws-lc-rs-fips\"],\"bloom\":[\"proto/bloom\"],\"default\":[\"log\",\"platform-verifier\",\"runtime-tokio\",\"rustls-ring\",\"bloom\"],\"lock_tracking\":[],\"log\":[\"tracing/log\",\"proto/log\",\"udp/log\"],\"platform-verifier\":[\"proto/platform-verifier\"],\"qlog\":[\"proto/qlog\"],\"ring\":[\"proto/ring\"],\"runtime-async-std\":[\"async-io\",\"async-std\"],\"runtime-smol\":[\"async-io\",\"smol\"],\"runtime-tokio\":[\"tokio/time\",\"tokio/rt\",\"tokio/net\"],\"rustls\":[\"rustls-ring\"],\"rustls-aws-lc-rs\":[\"dep:rustls\",\"aws-lc-rs\",\"proto/rustls-aws-lc-rs\",\"proto/aws-lc-rs\"],\"rustls-aws-lc-rs-fips\":[\"dep:rustls\",\"aws-lc-rs-fips\",\"proto/rustls-aws-lc-rs-fips\",\"proto/aws-lc-rs-fips\"],\"rustls-log\":[\"rustls?/logging\"],\"rustls-ring\":[\"dep:rustls\",\"ring\",\"proto/rustls-ring\",\"proto/ring\"]}}", "quote_1.0.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\"]}}", + "quote_1.0.45": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\"]}}", "r-efi_5.3.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"efiapi\":[],\"examples\":[\"native\"],\"native\":[],\"rustc-dep-of-std\":[\"core\"]}}", + "r-efi_6.0.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"native\":[],\"rustc-dep-of-std\":[\"core\"]}}", "radix_trie_0.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"endian-type\",\"req\":\"^0.1.2\"},{\"name\":\"nibble_vec\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{}}", "radix_trie_0.3.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"endian-type\",\"req\":\"^0.2.0\"},{\"name\":\"nibble_vec\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{}}", "rama-core_0.3.0-alpha.4": "{\"dependencies\":[{\"name\":\"ahash\",\"req\":\"^0.8\"},{\"name\":\"asynk-strim\",\"req\":\"^0.1\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"optional\":true,\"req\":\"^0.31\"},{\"features\":[\"semconv_experimental\"],\"name\":\"opentelemetry-semantic-conventions\",\"optional\":true,\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"trace\",\"rt-tokio\"],\"name\":\"opentelemetry_sdk\",\"optional\":true,\"req\":\"^0.31\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"name\":\"rama-error\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-macros\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-utils\",\"req\":\"^0.3.0-alpha.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"macros\",\"fs\",\"io-std\"],\"name\":\"tokio\",\"req\":\"^1.48\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.48\"},{\"name\":\"tokio-graceful\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"features\":[\"codec\",\"io\",\"io-util\"],\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"tracing-opentelemetry\",\"optional\":true,\"req\":\"^0.32\"}],\"features\":{\"default\":[],\"opentelemetry\":[\"dep:opentelemetry\",\"dep:opentelemetry-semantic-conventions\",\"dep:opentelemetry_sdk\",\"dep:tracing-opentelemetry\"]}}", @@ -1180,13 +1216,16 @@ "rcgen_0.14.7": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.13.3\"},{\"kind\":\"dev\",\"name\":\"openssl\",\"req\":\"^0.10\",\"target\":\"cfg(unix)\"},{\"name\":\"pem\",\"optional\":true,\"req\":\"^3.0.2\"},{\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.4.1\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"default_features\":false,\"name\":\"time\",\"req\":\"^0.3.6\"},{\"name\":\"x509-parser\",\"optional\":true,\"req\":\"^0.18\"},{\"features\":[\"time\",\"std\"],\"name\":\"yasna\",\"req\":\"^0.5.2\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.2\"}],\"features\":{\"aws_lc_rs\":[\"crypto\",\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"x509-parser?/verify-aws\"],\"aws_lc_rs_unstable\":[\"aws_lc_rs\",\"aws-lc-rs/unstable\",\"x509-parser?/verify-aws\"],\"crypto\":[],\"default\":[\"crypto\",\"pem\",\"ring\"],\"fips\":[\"crypto\",\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"ring\":[\"crypto\",\"dep:ring\",\"x509-parser?/verify\"]}}", "redox_syscall_0.5.18": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", "redox_syscall_0.7.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", + "redox_syscall_0.7.3": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", "redox_users_0.4.6": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\",\"call\"],\"name\":\"libredox\",\"req\":\"^0.1.3\"},{\"name\":\"rust-argon2\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"auth\":[\"rust-argon2\",\"zeroize\"],\"default\":[\"auth\"]}}", "redox_users_0.5.2": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\",\"call\"],\"name\":\"libredox\",\"req\":\"^0.1.3\"},{\"name\":\"rust-argon2\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"auth\":[\"rust-argon2\",\"zeroize\"],\"default\":[\"auth\"]}}", "ref-cast-impl_1.0.25": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", "ref-cast_1.0.25": "{\"dependencies\":[{\"name\":\"ref-cast-impl\",\"req\":\"=1.0.25\"},{\"kind\":\"dev\",\"name\":\"ref-cast-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{}}", "regex-automata_0.4.13": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.14\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"syntax\",\"perf\",\"unicode\",\"meta\",\"nfa\",\"dfa\",\"hybrid\"],\"dfa\":[\"dfa-build\",\"dfa-search\",\"dfa-onepass\"],\"dfa-build\":[\"nfa-thompson\",\"dfa-search\"],\"dfa-onepass\":[\"nfa-thompson\"],\"dfa-search\":[],\"hybrid\":[\"alloc\",\"nfa-thompson\"],\"internal-instrument\":[\"internal-instrument-pikevm\"],\"internal-instrument-pikevm\":[\"logging\",\"std\"],\"logging\":[\"dep:log\",\"aho-corasick?/logging\",\"memchr?/logging\"],\"meta\":[\"syntax\",\"nfa-pikevm\"],\"nfa\":[\"nfa-thompson\",\"nfa-pikevm\",\"nfa-backtrack\"],\"nfa-backtrack\":[\"nfa-thompson\"],\"nfa-pikevm\":[\"nfa-thompson\"],\"nfa-thompson\":[\"alloc\"],\"perf\":[\"perf-inline\",\"perf-literal\"],\"perf-inline\":[],\"perf-literal\":[\"perf-literal-substring\",\"perf-literal-multisubstring\"],\"perf-literal-multisubstring\":[\"dep:aho-corasick\"],\"perf-literal-substring\":[\"aho-corasick?/perf-literal\",\"dep:memchr\"],\"std\":[\"regex-syntax?/std\",\"memchr?/std\",\"aho-corasick?/std\",\"alloc\"],\"syntax\":[\"dep:regex-syntax\",\"alloc\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"unicode-word-boundary\",\"regex-syntax?/unicode\"],\"unicode-age\":[\"regex-syntax?/unicode-age\"],\"unicode-bool\":[\"regex-syntax?/unicode-bool\"],\"unicode-case\":[\"regex-syntax?/unicode-case\"],\"unicode-gencat\":[\"regex-syntax?/unicode-gencat\"],\"unicode-perl\":[\"regex-syntax?/unicode-perl\"],\"unicode-script\":[\"regex-syntax?/unicode-script\"],\"unicode-segment\":[\"regex-syntax?/unicode-segment\"],\"unicode-word-boundary\":[]}}", + "regex-automata_0.4.14": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.14\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"syntax\",\"perf\",\"unicode\",\"meta\",\"nfa\",\"dfa\",\"hybrid\"],\"dfa\":[\"dfa-build\",\"dfa-search\",\"dfa-onepass\"],\"dfa-build\":[\"nfa-thompson\",\"dfa-search\"],\"dfa-onepass\":[\"nfa-thompson\"],\"dfa-search\":[],\"hybrid\":[\"alloc\",\"nfa-thompson\"],\"internal-instrument\":[\"internal-instrument-pikevm\"],\"internal-instrument-pikevm\":[\"logging\",\"std\"],\"logging\":[\"dep:log\",\"aho-corasick?/logging\",\"memchr?/logging\"],\"meta\":[\"syntax\",\"nfa-pikevm\"],\"nfa\":[\"nfa-thompson\",\"nfa-pikevm\",\"nfa-backtrack\"],\"nfa-backtrack\":[\"nfa-thompson\"],\"nfa-pikevm\":[\"nfa-thompson\"],\"nfa-thompson\":[\"alloc\"],\"perf\":[\"perf-inline\",\"perf-literal\"],\"perf-inline\":[],\"perf-literal\":[\"perf-literal-substring\",\"perf-literal-multisubstring\"],\"perf-literal-multisubstring\":[\"dep:aho-corasick\"],\"perf-literal-substring\":[\"aho-corasick?/perf-literal\",\"dep:memchr\"],\"std\":[\"regex-syntax?/std\",\"memchr?/std\",\"aho-corasick?/std\",\"alloc\"],\"syntax\":[\"dep:regex-syntax\",\"alloc\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"unicode-word-boundary\",\"regex-syntax?/unicode\"],\"unicode-age\":[\"regex-syntax?/unicode-age\"],\"unicode-bool\":[\"regex-syntax?/unicode-bool\"],\"unicode-case\":[\"regex-syntax?/unicode-case\"],\"unicode-gencat\":[\"regex-syntax?/unicode-gencat\"],\"unicode-perl\":[\"regex-syntax?/unicode-perl\"],\"unicode-script\":[\"regex-syntax?/unicode-script\"],\"unicode-segment\":[\"regex-syntax?/unicode-segment\"],\"unicode-word-boundary\":[]}}", "regex-lite_0.1.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\",\"string\"],\"std\":[],\"string\":[]}}", "regex-syntax_0.6.29": "{\"dependencies\":[],\"features\":{\"default\":[\"unicode\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", + "regex-syntax_0.8.10": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\",\"unicode\"],\"std\":[],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", "regex-syntax_0.8.8": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\",\"unicode\"],\"std\":[],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", "regex_1.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"syntax\",\"meta\",\"nfa-pikevm\"],\"name\":\"regex-automata\",\"req\":\"^0.4.12\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\",\"perf\",\"unicode\",\"regex-syntax/default\"],\"logging\":[\"aho-corasick?/logging\",\"memchr?/logging\",\"regex-automata/logging\"],\"pattern\":[],\"perf\":[\"perf-cache\",\"perf-dfa\",\"perf-onepass\",\"perf-backtrack\",\"perf-inline\",\"perf-literal\"],\"perf-backtrack\":[\"regex-automata/nfa-backtrack\"],\"perf-cache\":[],\"perf-dfa\":[\"regex-automata/hybrid\"],\"perf-dfa-full\":[\"regex-automata/dfa-build\",\"regex-automata/dfa-search\"],\"perf-inline\":[\"regex-automata/perf-inline\"],\"perf-literal\":[\"dep:aho-corasick\",\"dep:memchr\",\"regex-automata/perf-literal\"],\"perf-onepass\":[\"regex-automata/dfa-onepass\"],\"std\":[\"aho-corasick?/std\",\"memchr?/std\",\"regex-automata/std\",\"regex-syntax/std\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"regex-automata/unicode\",\"regex-syntax/unicode\"],\"unicode-age\":[\"regex-automata/unicode-age\",\"regex-syntax/unicode-age\"],\"unicode-bool\":[\"regex-automata/unicode-bool\",\"regex-syntax/unicode-bool\"],\"unicode-case\":[\"regex-automata/unicode-case\",\"regex-syntax/unicode-case\"],\"unicode-gencat\":[\"regex-automata/unicode-gencat\",\"regex-syntax/unicode-gencat\"],\"unicode-perl\":[\"regex-automata/unicode-perl\",\"regex-automata/unicode-word-boundary\",\"regex-syntax/unicode-perl\"],\"unicode-script\":[\"regex-automata/unicode-script\",\"regex-syntax/unicode-script\"],\"unicode-segment\":[\"regex-automata/unicode-segment\",\"regex-syntax/unicode-segment\"],\"unstable\":[\"pattern\"],\"use_std\":[\"std\"]}}", "reqwest_0.12.28": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.18.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.22.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.28\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.28\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\"],\"name\":\"hyper\",\"req\":\"^1.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"http2\",\"client\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"tls12\"],\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.27.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\",\"client-legacy\",\"client-proxy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"http2\",\"client\",\"client-legacy\",\"server-auto\",\"server-graceful\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.77\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0\"},{\"name\":\"log\",\"req\":\"^0.4.17\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.18\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"rustls\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.9.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"features\":[\"futures\"],\"name\":\"sync_wrapper\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.9\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"retry\",\"timeout\",\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"features\":[\"follow-redirect\"],\"name\":\"tower-http\",\"req\":\"^0.6.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"url\",\"req\":\"^2.4\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\",\"RequestCache\"],\"name\":\"web-sys\",\"req\":\"^0.3.28\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"zstd_crate\",\"package\":\"zstd\",\"req\":\"^0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"}],\"features\":{\"__rustls\":[\"dep:hyper-rustls\",\"dep:tokio-rustls\",\"dep:rustls\",\"__tls\"],\"__rustls-ring\":[\"hyper-rustls?/ring\",\"tokio-rustls?/ring\",\"rustls?/ring\",\"quinn?/ring\"],\"__tls\":[\"dep:rustls-pki-types\",\"tokio/io-util\"],\"blocking\":[\"dep:futures-channel\",\"futures-channel?/sink\",\"dep:futures-util\",\"futures-util?/io\",\"futures-util?/sink\",\"tokio/sync\"],\"brotli\":[\"tower-http/decompression-br\"],\"charset\":[\"dep:encoding_rs\",\"dep:mime\"],\"cookies\":[\"dep:cookie_crate\",\"dep:cookie_store\"],\"default\":[\"default-tls\",\"charset\",\"http2\",\"system-proxy\"],\"default-tls\":[\"dep:hyper-tls\",\"dep:native-tls-crate\",\"__tls\",\"dep:tokio-native-tls\"],\"deflate\":[\"tower-http/decompression-deflate\"],\"gzip\":[\"tower-http/decompression-gzip\"],\"hickory-dns\":[\"dep:hickory-resolver\",\"dep:once_cell\"],\"http2\":[\"h2\",\"hyper/http2\",\"hyper-util/http2\",\"hyper-rustls?/http2\"],\"http3\":[\"rustls-tls-manual-roots\",\"dep:h3\",\"dep:h3-quinn\",\"dep:quinn\",\"tokio/macros\"],\"json\":[\"dep:serde_json\"],\"macos-system-configuration\":[\"system-proxy\"],\"multipart\":[\"dep:mime_guess\",\"dep:futures-util\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate?/alpn\",\"hyper-tls?/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate?/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"rustls-tls-manual-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-manual-roots-no-provider\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-tls-native-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-native-roots-no-provider\":[\"dep:rustls-native-certs\",\"hyper-rustls?/native-tokio\",\"__rustls\"],\"rustls-tls-no-provider\":[\"rustls-tls-manual-roots-no-provider\"],\"rustls-tls-webpki-roots\":[\"rustls-tls-webpki-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-webpki-roots-no-provider\":[\"dep:webpki-roots\",\"hyper-rustls?/webpki-tokio\",\"__rustls\"],\"socks\":[],\"stream\":[\"tokio/fs\",\"dep:futures-util\",\"dep:tokio-util\",\"dep:wasm-streams\"],\"system-proxy\":[\"hyper-util/client-proxy-system\"],\"trust-dns\":[],\"zstd\":[\"tower-http/decompression-zstd\"]}}", @@ -1203,10 +1242,13 @@ "rustc-demangle_0.1.27": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"compiler_builtins\":[],\"rustc-dep-of-std\":[\"core\"],\"std\":[]}}", "rustc-hash_1.1.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "rustc-hash_2.1.1": "{\"dependencies\":[{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"default\":[\"std\"],\"nightly\":[],\"rand\":[\"dep:rand\",\"std\"],\"std\":[]}}", + "rustc_apfloat_0.2.3+llvm-462a31f5a5ab": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6.0\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"features\":[\"const_generics\",\"union\"],\"name\":\"smallvec\",\"req\":\"^1.11.0\"}],\"features\":{}}", "rustc_version_0.4.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"semver\",\"req\":\"^1.0\"}],\"features\":{}}", + "rustfix_0.8.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.5.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.204\"},{\"name\":\"serde_json\",\"req\":\"^1.0.120\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2.6.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"name\":\"thiserror\",\"req\":\"^1.0.63\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.40\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.18\"}],\"features\":{}}", "rusticata-macros_4.1.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7.0\"}],\"features\":{}}", "rustix_0.38.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.161\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.5.2\",\"target\":\"cfg(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"))\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_NetworkManagement_IpHelper\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"procfs\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"cc\":[],\"default\":[\"std\",\"use-libc-auxv\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"linux-raw-sys/io_uring\"],\"libc-extra-traits\":[\"libc?/extra_traits\"],\"linux_4_11\":[],\"linux_latest\":[\"linux_4_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[\"fs\"],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"procfs\":[\"once_cell\",\"itoa\",\"fs\"],\"pty\":[\"itoa\",\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"compiler_builtins\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\",\"compiler_builtins?/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\",\"libc-extra-traits\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\",\"libc-extra-traits\"],\"use-libc-auxv\":[]}}", "rustix_1.1.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", + "rustix_1.1.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.182\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.182\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.12\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.12\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", "rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}", "rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", "rustls-webpki_0.103.10": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18.1\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}", @@ -1319,6 +1361,7 @@ "supports-color_3.0.2": "{\"dependencies\":[{\"name\":\"is_ci\",\"req\":\"^1.2.0\"}],\"features\":{}}", "syn_1.0.109": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.46\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1.0\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.1\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", "syn_2.0.114": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.13\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1\"},{\"name\":\"unicode-ident\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\",\"target\":\"cfg(not(miri))\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"dep:quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote?/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", + "syn_2.0.117": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.13\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1\"},{\"name\":\"unicode-ident\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\",\"target\":\"cfg(not(miri))\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"dep:quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote?/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", "sync_wrapper_1.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"}],\"features\":{\"futures\":[\"futures-core\"]}}", "synstructure_0.13.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"visit\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"synstructure_test_traits\",\"req\":\"^0.1\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"syn/proc-macro\",\"quote/proc-macro\"]}}", "syntect_5.3.0": "{\"dependencies\":[{\"name\":\"bincode\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"fancy-regex\",\"optional\":true,\"req\":\"^0.16.2\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"fnv\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"getopts\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.8\"},{\"default_features\":false,\"name\":\"onig\",\"optional\":true,\"req\":\"^6.5.1\"},{\"name\":\"plist\",\"optional\":true,\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"public-api\",\"req\":\"^0.50.1\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rustdoc-json\",\"req\":\"^0.9.7\"},{\"kind\":\"dev\",\"name\":\"rustup-toolchain\",\"req\":\"^0.1.5\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"name\":\"walkdir\",\"req\":\"^2.0\"},{\"name\":\"yaml-rust\",\"optional\":true,\"req\":\"^0.4.5\"}],\"features\":{\"default\":[\"default-onig\"],\"default-fancy\":[\"parsing\",\"default-syntaxes\",\"default-themes\",\"html\",\"plist-load\",\"yaml-load\",\"dump-load\",\"dump-create\",\"regex-fancy\"],\"default-onig\":[\"parsing\",\"default-syntaxes\",\"default-themes\",\"html\",\"plist-load\",\"yaml-load\",\"dump-load\",\"dump-create\",\"regex-onig\"],\"default-syntaxes\":[\"parsing\",\"dump-load\"],\"default-themes\":[\"dump-load\"],\"dump-create\":[\"flate2\",\"bincode\"],\"dump-load\":[\"flate2\",\"bincode\"],\"html\":[\"parsing\"],\"metadata\":[\"parsing\",\"plist-load\",\"dep:serde_json\"],\"parsing\":[\"regex-syntax\",\"fnv\",\"dump-create\",\"dump-load\"],\"plist-load\":[\"plist\",\"dep:serde_json\"],\"regex-fancy\":[\"fancy-regex\"],\"regex-onig\":[\"onig\"],\"yaml-load\":[\"yaml-rust\",\"parsing\"]}}", @@ -1326,8 +1369,9 @@ "system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}", "system-configuration_0.6.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"features\":{}}", "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", - "tar_0.4.45": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"astral-tokio-tar\",\"req\":\"^0.5\"},{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}", + "tar_0.4.44": "{\"dependencies\":[{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}", "tempfile_3.24.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.3\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", + "tempfile_3.27.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\">=0.3.0, <0.5\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.4\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", "temporal_capi_0.1.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"diplomat\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"name\":\"diplomat-runtime\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"unstable\"],\"name\":\"icu_calendar\",\"req\":\"^2.1.0\"},{\"name\":\"icu_locale\",\"req\":\"^2.1.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.19\"},{\"default_features\":false,\"name\":\"temporal_rs\",\"req\":\"^0.1.2\"},{\"name\":\"timezone_provider\",\"req\":\"^0.1.2\"},{\"name\":\"writeable\",\"req\":\"^0.6.0\"},{\"name\":\"zoneinfo64\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"compiled_data\":[\"temporal_rs/compiled_data\"],\"zoneinfo64\":[\"dep:zoneinfo64\",\"timezone_provider/zoneinfo64\"]}}", "temporal_rs_0.1.2": "{\"dependencies\":[{\"name\":\"core_maths\",\"req\":\"^0.1.1\"},{\"name\":\"iana-time-zone\",\"optional\":true,\"req\":\"^0.1.64\"},{\"default_features\":false,\"features\":[\"unstable\",\"compiled_data\"],\"name\":\"icu_calendar\",\"req\":\"^2.1.0\"},{\"name\":\"icu_locale\",\"req\":\"^2.1.0\"},{\"features\":[\"duration\"],\"name\":\"ixdtf\",\"req\":\"^0.6.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.19\"},{\"name\":\"timezone_provider\",\"req\":\"^0.1.2\"},{\"name\":\"tinystr\",\"req\":\"^0.8.0\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1.0\"},{\"name\":\"writeable\",\"req\":\"^0.6.0\"}],\"features\":{\"compiled_data\":[\"tzdb\"],\"default\":[\"sys\"],\"float64_representable_durations\":[],\"log\":[\"dep:log\"],\"std\":[],\"sys\":[\"std\",\"compiled_data\",\"dep:web-time\",\"dep:iana-time-zone\"],\"tzdb\":[\"std\",\"timezone_provider/tzif\"]}}", "term_0.7.0": "{\"dependencies\":[{\"name\":\"dirs-next\",\"req\":\"^2\"},{\"name\":\"rustversion\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"features\":[\"consoleapi\",\"wincon\",\"handleapi\",\"fileapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[]}}", @@ -1339,6 +1383,7 @@ "test-case_3.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.11\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.5\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.5\"},{\"default_features\":false,\"name\":\"test-case-macros\",\"req\":\"^3.2.1\"}],\"features\":{\"with-regex\":[\"regex\",\"test-case-macros/with-regex\"]}}", "test-log-macros_0.2.19": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.32\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"full\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"log\":[],\"trace\":[],\"unstable\":[]}}", "test-log_0.2.19": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"_lazy_static_unused\",\"package\":\"lazy_static\",\"req\":\"^1.0.2\"},{\"default_features\":false,\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"logging\",\"package\":\"log\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"test-case\",\"req\":\"^3.1\"},{\"name\":\"test-log-macros\",\"req\":\"=0.2.19\"},{\"default_features\":false,\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\"],\"name\":\"tracing-subscriber\",\"optional\":true,\"req\":\"^0.3.20\"}],\"features\":{\"color\":[\"env_logger?/auto-color\",\"tracing-subscriber?/ansi\"],\"default\":[\"log\",\"color\"],\"log\":[\"dep:env_logger\",\"test-log-macros/log\",\"tracing-subscriber?/tracing-log\"],\"trace\":[\"dep:tracing-subscriber\",\"test-log-macros/trace\"],\"unstable\":[\"test-log-macros/unstable\"]}}", + "tester_0.9.1": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"getopts\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"name\":\"num_cpus\",\"req\":\"^1.13.0\"},{\"name\":\"term\",\"req\":\"^0.7\"}],\"features\":{\"asm_black_box\":[],\"capture\":[]}}", "textwrap_0.11.0": "{\"dependencies\":[{\"features\":[\"embed_all\"],\"name\":\"hyphenation\",\"optional\":true,\"req\":\"^0.7.1\"},{\"kind\":\"dev\",\"name\":\"lipsum\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.1\"},{\"name\":\"term_size\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"unicode-width\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.6\"}],\"features\":{}}", "textwrap_0.16.2": "{\"dependencies\":[{\"features\":[\"embed_en-us\"],\"name\":\"hyphenation\",\"optional\":true,\"req\":\"^0.8.4\"},{\"name\":\"smawk\",\"optional\":true,\"req\":\"^0.3.2\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"termion\",\"req\":\"^4.0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"unic-emoji-char\",\"req\":\"^0.9.0\"},{\"name\":\"unicode-linebreak\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9.5\"}],\"features\":{\"default\":[\"unicode-linebreak\",\"unicode-width\",\"smawk\"]}}", "thiserror-impl_1.0.69": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"syn\",\"req\":\"^2.0.87\"}],\"features\":{}}", @@ -1366,10 +1411,12 @@ "tokio_1.49.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-stream\",\"req\":\"^0.3\"},{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.58\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1.2.1\"},{\"features\":[\"async-await\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"futures-concurrency\",\"req\":\"^7.6.3\"},{\"kind\":\"dev\",\"name\":\"futures-test\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"io-uring\",\"optional\":true,\"req\":\"^0.7.6\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.168\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.168\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.168\",\"target\":\"cfg(unix)\"},{\"features\":[\"futures\",\"checkpoint\"],\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0.1\"},{\"default_features\":false,\"features\":[\"os-poll\",\"os-ext\"],\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0.1\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"mio-aio\",\"req\":\"^1\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"kind\":\"dev\",\"name\":\"mockall\",\"req\":\"^0.13.0\"},{\"default_features\":false,\"features\":[\"aio\",\"fs\",\"socket\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.29.0\",\"target\":\"cfg(unix)\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"signal-hook-registry\",\"optional\":true,\"req\":\"^1.1.1\",\"target\":\"cfg(unix)\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4.9\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\"^0.6.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"socket2\",\"req\":\"^0.6.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"name\":\"tokio-macros\",\"optional\":true,\"req\":\"~2.6.0\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4.0\"},{\"features\":[\"rt\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.29\",\"target\":\"cfg(tokio_unstable)\"},{\"kind\":\"dev\",\"name\":\"tracing-mock\",\"req\":\"=0.1.0-beta.1\",\"target\":\"cfg(all(tokio_unstable, target_has_atomic = \\\"64\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.0\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(target_os = \\\"wasi\\\")))\"},{\"name\":\"windows-sys\",\"optional\":true,\"req\":\"^0.61\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Authorization\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"fs\":[],\"full\":[\"fs\",\"io-util\",\"io-std\",\"macros\",\"net\",\"parking_lot\",\"process\",\"rt\",\"rt-multi-thread\",\"signal\",\"sync\",\"time\"],\"io-std\":[],\"io-uring\":[\"dep:io-uring\",\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"dep:slab\"],\"io-util\":[\"bytes\"],\"macros\":[\"tokio-macros\"],\"net\":[\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"mio/net\",\"socket2\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_Security\",\"windows-sys/Win32_Storage_FileSystem\",\"windows-sys/Win32_System_Pipes\",\"windows-sys/Win32_System_SystemServices\"],\"process\":[\"bytes\",\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"mio/net\",\"signal-hook-registry\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_System_Threading\",\"windows-sys/Win32_System_WindowsProgramming\"],\"rt\":[],\"rt-multi-thread\":[\"rt\"],\"signal\":[\"libc\",\"mio/os-poll\",\"mio/net\",\"mio/os-ext\",\"signal-hook-registry\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_System_Console\"],\"sync\":[],\"taskdump\":[\"dep:backtrace\"],\"test-util\":[\"rt\",\"sync\",\"time\"],\"time\":[]}}", "toml_0.5.11": "{\"dependencies\":[{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0.97\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[],\"preserve_order\":[\"indexmap\"]}}", "toml_0.9.11+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", + "toml_0.9.12+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", "toml_datetime_0.7.5+spec-1.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"alloc\",\"serde_core?/std\"]}}", "toml_edit_0.23.10+spec-1.0.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.4\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.5\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", "toml_edit_0.24.0+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", "toml_parser_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^0.7.13\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", + "toml_parser_1.0.9+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^0.7.13\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", "toml_writer_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml_old\",\"package\":\"toml\",\"req\":\"^0.5.11\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "tonic-prost_0.14.3": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"prost\",\"req\":\"^0.14\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"tonic\",\"req\":\"^0.14.0\"}],\"features\":{}}", "tonic_0.14.3": "{\"dependencies\":[{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"http\",\"req\":\"^1.1.0\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"http2\"],\"name\":\"hyper\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"hyper-timeout\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"tokio\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1.11\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1\"},{\"name\":\"pin-project\",\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.0\"},{\"name\":\"sync_wrapper\",\"req\":\"^1.0.2\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"logging\",\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26.1\"},{\"default_features\":false,\"name\":\"tokio-stream\",\"req\":\"^0.1.16\"},{\"default_features\":false,\"name\":\"tower\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"load-shed\",\"timeout\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5\"},{\"name\":\"tower-layer\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13.0\"}],\"features\":{\"_tls-any\":[\"dep:tokio\",\"tokio?/rt\",\"tokio?/macros\",\"tls-connect-info\"],\"channel\":[\"dep:hyper\",\"hyper?/client\",\"dep:hyper-util\",\"hyper-util?/client-legacy\",\"dep:tower\",\"tower?/balance\",\"tower?/buffer\",\"tower?/discover\",\"tower?/limit\",\"tower?/load-shed\",\"tower?/util\",\"dep:tokio\",\"tokio?/time\",\"dep:hyper-timeout\"],\"codegen\":[\"dep:async-trait\"],\"default\":[\"router\",\"transport\",\"codegen\"],\"deflate\":[\"dep:flate2\"],\"gzip\":[\"dep:flate2\"],\"router\":[\"dep:axum\",\"dep:tower\",\"tower?/util\"],\"server\":[\"dep:h2\",\"dep:hyper\",\"hyper?/server\",\"dep:hyper-util\",\"hyper-util?/service\",\"hyper-util?/server-auto\",\"dep:socket2\",\"dep:tokio\",\"tokio?/macros\",\"tokio?/net\",\"tokio?/time\",\"tokio-stream/net\",\"dep:tower\",\"tower?/util\",\"tower?/limit\",\"tower?/load-shed\"],\"tls-aws-lc\":[\"_tls-any\",\"tokio-rustls/aws-lc-rs\"],\"tls-connect-info\":[\"dep:tokio-rustls\"],\"tls-native-roots\":[\"_tls-any\",\"channel\",\"dep:rustls-native-certs\"],\"tls-ring\":[\"_tls-any\",\"tokio-rustls/ring\"],\"tls-webpki-roots\":[\"_tls-any\",\"channel\",\"dep:webpki-roots\"],\"transport\":[\"server\",\"channel\"],\"zstd\":[\"dep:zstd\"]}}", @@ -1406,6 +1453,7 @@ "unicase_2.9.0": "{\"dependencies\":[],\"features\":{\"nightly\":[]}}", "unicode-bidi_0.3.18": "{\"dependencies\":[{\"name\":\"flame\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"flamer\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\">=0.8, <2.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\">=0.8, <2.0\"},{\"features\":[\"union\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\">=1.13\"}],\"features\":{\"bench_it\":[],\"default\":[\"std\",\"hardcoded-data\"],\"flame_it\":[\"flame\",\"flamer\"],\"hardcoded-data\":[],\"std\":[],\"unstable\":[],\"with_serde\":[\"serde\"]}}", "unicode-ident_1.0.22": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.11\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", + "unicode-ident_1.0.24": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.11\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", "unicode-linebreak_0.1.5": "{\"dependencies\":[],\"features\":{}}", "unicode-normalization_0.1.25": "{\"dependencies\":[{\"features\":[\"alloc\"],\"name\":\"tinyvec\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "unicode-properties_0.1.4": "{\"dependencies\":[],\"features\":{\"default\":[\"general-category\",\"emoji\"],\"emoji\":[],\"general-category\":[]}}", @@ -1413,6 +1461,7 @@ "unicode-truncate_1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"itertools\",\"req\":\"^0.13\"},{\"default_features\":false,\"name\":\"unicode-segmentation\",\"req\":\"^1\"},{\"name\":\"unicode-width\",\"req\":\"^0.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "unicode-width_0.1.14": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"}],\"features\":{\"cjk\":[],\"default\":[\"cjk\"],\"no_std\":[],\"rustc-dep-of-std\":[\"std\",\"core\",\"compiler_builtins\"]}}", "unicode-width_0.2.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"}],\"features\":{\"cjk\":[],\"default\":[\"cjk\"],\"no_std\":[],\"rustc-dep-of-std\":[\"std\",\"core\"]}}", + "unicode-width_0.2.2": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"}],\"features\":{\"cjk\":[],\"default\":[\"cjk\"],\"no_std\":[],\"rustc-dep-of-std\":[\"std\",\"core\"]}}", "unicode-xid_0.2.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"}],\"features\":{\"bench\":[],\"default\":[],\"no_std\":[]}}", "universal-hash_0.5.1": "{\"dependencies\":[{\"name\":\"crypto-common\",\"req\":\"^0.1.6\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.4\"}],\"features\":{\"std\":[\"crypto-common/std\"]}}", "unsafe-libyaml_0.2.11": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0\"}],\"features\":{}}", @@ -1437,13 +1486,17 @@ "want_0.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"tokio-executor\",\"req\":\"^0.2.0-alpha.2\"},{\"kind\":\"dev\",\"name\":\"tokio-sync\",\"req\":\"^0.2.0-alpha.2\"},{\"name\":\"try-lock\",\"req\":\"^0.2.4\"}],\"features\":{}}", "wasi_0.11.1+wasi-snapshot-preview1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\"],\"std\":[]}}", "wasip2_1.0.2+wasi-0.2.9": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"wit-bindgen\",\"req\":\"^0.51.0\"}],\"features\":{\"bitflags\":[\"wit-bindgen/bitflags\"],\"default\":[\"std\",\"bitflags\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"wit-bindgen/rustc-dep-of-std\"],\"std\":[]}}", + "wasip3_0.4.0+wasi-0.3.0-rc-2026-01-06": "{\"dependencies\":[{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1.10.1\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.31\"},{\"name\":\"http\",\"optional\":true,\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^1.3.1\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1.0.1\"},{\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2.0.17\"},{\"default_features\":false,\"features\":[\"async\"],\"name\":\"wit-bindgen\",\"req\":\"^0.51.0\"},{\"default_features\":false,\"features\":[\"async-spawn\"],\"kind\":\"dev\",\"name\":\"wit-bindgen\",\"req\":\"^0.51.0\"}],\"features\":{\"http-compat\":[\"dep:bytes\",\"dep:http-body\",\"dep:http\",\"dep:thiserror\",\"wit-bindgen/async-spawn\"]}}", "wasite_0.1.0": "{\"dependencies\":[],\"features\":{}}", "wasm-bindgen-futures_0.4.58": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.8\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"js-sys\",\"req\":\"=0.3.85\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.108\"},{\"default_features\":false,\"features\":[\"MessageEvent\",\"Worker\"],\"name\":\"web-sys\",\"req\":\"=0.3.85\",\"target\":\"cfg(target_feature = \\\"atomics\\\")\"}],\"features\":{\"default\":[\"std\"],\"futures-core-03-stream\":[\"futures-core\"],\"std\":[\"wasm-bindgen/std\",\"js-sys/std\",\"web-sys/std\",\"futures-util\"]}}", "wasm-bindgen-macro-support_0.2.108": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.0.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"visit\",\"visit-mut\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.108\"}],\"features\":{\"extra-traits\":[\"syn/extra-traits\"],\"strict-macro\":[]}}", "wasm-bindgen-macro_0.2.108": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro-support\",\"req\":\"=0.2.108\"}],\"features\":{\"strict-macro\":[\"wasm-bindgen-macro-support/strict-macro\"]}}", "wasm-bindgen-shared_0.2.108": "{\"dependencies\":[{\"name\":\"unicode-ident\",\"req\":\"^1.0.5\"}],\"features\":{}}", "wasm-bindgen_0.2.108": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"build\",\"name\":\"rustversion-compat\",\"package\":\"rustversion\",\"req\":\"^1.0.6\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro\",\"req\":\"=0.2.108\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.108\"}],\"features\":{\"default\":[\"std\"],\"enable-interning\":[\"std\"],\"gg-alloc\":[],\"msrv\":[],\"rustversion\":[],\"serde-serialize\":[\"serde\",\"serde_json\",\"std\"],\"spans\":[],\"std\":[],\"strict-macro\":[\"wasm-bindgen-macro/strict-macro\"],\"xxx_debug_only_print_generated_code\":[]}}", + "wasm-encoder_0.244.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.58\"},{\"default_features\":false,\"name\":\"leb128fmt\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.2.0\"},{\"default_features\":false,\"features\":[\"simd\",\"simd\"],\"name\":\"wasmparser\",\"optional\":true,\"req\":\"^0.244.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"wasmprinter\",\"req\":\"^0.244.0\"}],\"features\":{\"component-model\":[\"wasmparser?/component-model\"],\"default\":[\"std\",\"component-model\"],\"std\":[\"wasmparser?/std\"]}}", + "wasm-metadata_0.244.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.58\"},{\"name\":\"auditable-serde\",\"optional\":true,\"req\":\"^0.8.0\"},{\"features\":[\"derive\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.0.0\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"indexmap\",\"req\":\"^2.7.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.166\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0.166\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"spdx\",\"optional\":true,\"req\":\"^0.10.1\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"std\",\"component-model\"],\"name\":\"wasm-encoder\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"simd\",\"std\",\"component-model\",\"hash-collections\"],\"name\":\"wasmparser\",\"req\":\"^0.244.0\"}],\"features\":{\"default\":[\"oci\",\"serde\"],\"oci\":[\"dep:auditable-serde\",\"dep:flate2\",\"dep:url\",\"dep:spdx\",\"dep:serde_json\",\"serde\"],\"serde\":[\"dep:serde_derive\",\"dep:serde\"]}}", "wasm-streams_0.4.2": "{\"dependencies\":[{\"features\":[\"io\",\"sink\"],\"name\":\"futures-util\",\"req\":\"^0.3.31\"},{\"features\":[\"futures\"],\"kind\":\"dev\",\"name\":\"gloo-timers\",\"req\":\"^0.3.0\"},{\"name\":\"js-sys\",\"req\":\"^0.3.72\"},{\"kind\":\"dev\",\"name\":\"pin-project\",\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.95\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.45\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.45\"},{\"features\":[\"AbortSignal\",\"QueuingStrategy\",\"ReadableStream\",\"ReadableStreamType\",\"ReadableWritablePair\",\"ReadableStreamByobReader\",\"ReadableStreamReaderMode\",\"ReadableStreamReadResult\",\"ReadableStreamByobRequest\",\"ReadableStreamDefaultReader\",\"ReadableByteStreamController\",\"ReadableStreamGetReaderOptions\",\"ReadableStreamDefaultController\",\"StreamPipeOptions\",\"TransformStream\",\"TransformStreamDefaultController\",\"Transformer\",\"UnderlyingSink\",\"UnderlyingSource\",\"WritableStream\",\"WritableStreamDefaultController\",\"WritableStreamDefaultWriter\"],\"name\":\"web-sys\",\"req\":\"^0.3.72\"},{\"features\":[\"console\",\"AbortSignal\",\"ErrorEvent\",\"PromiseRejectionEvent\",\"Response\",\"ReadableStream\",\"Window\"],\"kind\":\"dev\",\"name\":\"web-sys\",\"req\":\"^0.3.72\"}],\"features\":{}}", + "wasmparser_0.244.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.58\"},{\"name\":\"bitflags\",\"req\":\"^2.4.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15.2\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.7.0\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.13.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.3\"},{\"default_features\":false,\"name\":\"semver\",\"optional\":true,\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.166\"}],\"features\":{\"component-model\":[\"dep:semver\"],\"default\":[\"std\",\"validate\",\"serde\",\"features\",\"component-model\",\"hash-collections\",\"simd\"],\"features\":[],\"hash-collections\":[\"dep:hashbrown\",\"dep:indexmap\"],\"prefer-btree-collections\":[],\"serde\":[\"dep:serde\",\"indexmap?/serde\",\"hashbrown?/serde\"],\"simd\":[],\"std\":[\"indexmap?/std\"],\"validate\":[]}}", "wayland-backend_0.3.12": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"concat-idents\",\"req\":\"^1.1\"},{\"name\":\"downcast-rs\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"raw-window-handle\",\"optional\":true,\"req\":\"^0.5.0\"},{\"features\":[\"event\",\"fs\",\"net\",\"process\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"name\":\"rwh_06\",\"optional\":true,\"package\":\"raw-window-handle\",\"req\":\"^0.6.0\"},{\"name\":\"scoped-tls\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"scoped-tls\",\"req\":\"^1.0\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.9\"},{\"name\":\"wayland-sys\",\"req\":\"^0.31.8\"}],\"features\":{\"client_system\":[\"wayland-sys/client\",\"dep:scoped-tls\"],\"dlopen\":[\"wayland-sys/dlopen\"],\"server_system\":[\"wayland-sys/server\",\"dep:scoped-tls\"]}}", "wayland-client_0.31.12": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3.16\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"event\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.12\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.8\"}],\"features\":{}}", "wayland-protocols-wlr_0.3.10": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.12\"},{\"name\":\"wayland-client\",\"optional\":true,\"req\":\"^0.31.12\"},{\"name\":\"wayland-protocols\",\"req\":\"^0.32.10\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.8\"},{\"name\":\"wayland-server\",\"optional\":true,\"req\":\"^0.31.11\"}],\"features\":{\"client\":[\"wayland-client\",\"wayland-protocols/client\"],\"server\":[\"wayland-server\",\"wayland-protocols/server\"]}}", @@ -1529,13 +1582,19 @@ "windows_x86_64_msvc_0.52.6": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_msvc_0.53.1": "{\"dependencies\":[],\"features\":{}}", "winnow_0.7.14": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", + "winnow_0.7.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", "winreg_0.10.1": "{\"dependencies\":[{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"impl-default\",\"impl-debug\",\"minwindef\",\"minwinbase\",\"timezoneapi\",\"winerror\",\"winnt\",\"winreg\",\"handleapi\"],\"name\":\"winapi\",\"req\":\"^0.3.9\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[\"winapi/ktmw32\"]}}", "winreg_0.50.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Time\",\"Win32_System_Registry\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\"^0.48.0\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[]}}", "winres_0.1.12": "{\"dependencies\":[{\"name\":\"toml\",\"req\":\"^0.5\"},{\"features\":[\"winnt\"],\"kind\":\"dev\",\"name\":\"winapi\",\"req\":\"^0.3\"}],\"features\":{}}", "winsafe_0.0.19": "{\"dependencies\":[],\"features\":{\"comctl\":[\"ole\"],\"dshow\":[\"oleaut\"],\"dwm\":[\"uxtheme\"],\"dxgi\":[\"ole\"],\"gdi\":[\"user\"],\"gui\":[\"comctl\",\"shell\",\"uxtheme\"],\"kernel\":[],\"mf\":[\"oleaut\"],\"ole\":[\"user\"],\"oleaut\":[\"ole\"],\"shell\":[\"oleaut\"],\"taskschd\":[\"oleaut\"],\"user\":[\"kernel\"],\"uxtheme\":[\"gdi\",\"ole\"],\"version\":[\"kernel\"]}}", "winsplit_0.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "wiremock_0.6.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.10.0\"},{\"name\":\"assert-json-diff\",\"req\":\"^2.0.2\"},{\"features\":[\"attributes\",\"tokio1\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.13.2\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"deadpool\",\"req\":\"^0.12.2\"},{\"name\":\"futures\",\"req\":\"^0.3.31\"},{\"name\":\"http\",\"req\":\"^1.3\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"full\"],\"name\":\"hyper\",\"req\":\"^1.7\"},{\"features\":[\"tokio\",\"server\",\"http1\",\"http2\"],\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"regex\",\"req\":\"^1\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12.23\"},{\"name\":\"serde\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"rt\",\"macros\",\"net\"],\"name\":\"tokio\",\"req\":\"^1.47.1\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.47.1\"},{\"name\":\"url\",\"req\":\"^2.5\"}],\"features\":{}}", + "wit-bindgen-core_0.51.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.72\"},{\"features\":[\"derive\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.3.19\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.218\"},{\"name\":\"wit-parser\",\"req\":\"^0.244.0\"}],\"features\":{\"clap\":[\"dep:clap\"],\"serde\":[\"dep:serde\"]}}", + "wit-bindgen-rust-macro_0.51.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.72\"},{\"name\":\"prettyplease\",\"req\":\"^0.2.20\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.89\"},{\"name\":\"wit-bindgen-core\",\"req\":\"^0.51.0\"},{\"name\":\"wit-bindgen-rust\",\"req\":\"^0.51.0\"}],\"features\":{\"async\":[]}}", + "wit-bindgen-rust_0.51.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.72\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.3.19\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.31\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"indexmap\",\"req\":\"^2.0.0\"},{\"name\":\"prettyplease\",\"req\":\"^0.2.20\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.218\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.89\"},{\"default_features\":false,\"name\":\"wasm-metadata\",\"req\":\"^0.244.0\"},{\"name\":\"wit-bindgen-core\",\"req\":\"^0.51.0\"},{\"name\":\"wit-component\",\"req\":\"^0.244.0\"}],\"features\":{\"clap\":[\"dep:clap\",\"wit-bindgen-core/clap\"],\"serde\":[\"dep:serde\",\"wit-bindgen-core/serde\"]}}", "wit-bindgen_0.51.0": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0\"},{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.3.3\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"wit-bindgen-rust-macro\",\"optional\":true,\"req\":\"^0.51.0\"}],\"features\":{\"async\":[\"std\",\"wit-bindgen-rust-macro?/async\"],\"async-spawn\":[\"async\",\"dep:futures\"],\"bitflags\":[\"dep:bitflags\"],\"default\":[\"macros\",\"realloc\",\"async\",\"std\",\"bitflags\"],\"inter-task-wakeup\":[\"async\"],\"macros\":[\"dep:wit-bindgen-rust-macro\"],\"realloc\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[]}}", + "wit-component_0.244.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.58\"},{\"name\":\"bitflags\",\"req\":\"^2.3.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.7.0\"},{\"kind\":\"dev\",\"name\":\"libtest-mimic\",\"req\":\"^0.8.1\"},{\"name\":\"log\",\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.3.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"req\":\"^1.0.166\"},{\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\",\"wasmparser\"],\"name\":\"wasm-encoder\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"name\":\"wasm-metadata\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"oci\"],\"kind\":\"dev\",\"name\":\"wasm-metadata\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"simd\",\"std\",\"component-model\",\"simd\"],\"name\":\"wasmparser\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"simd\",\"std\",\"component-model\",\"features\"],\"kind\":\"dev\",\"name\":\"wasmparser\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"component-model\"],\"kind\":\"dev\",\"name\":\"wasmprinter\",\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"cranelift\",\"component-model\",\"runtime\",\"gc-drc\"],\"kind\":\"dev\",\"name\":\"wasmtime\",\"req\":\"^34.0.1\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"default_features\":false,\"name\":\"wast\",\"optional\":true,\"req\":\"^244.0.0\"},{\"default_features\":false,\"name\":\"wat\",\"optional\":true,\"req\":\"^1.244.0\"},{\"default_features\":false,\"features\":[\"component-model\"],\"kind\":\"dev\",\"name\":\"wat\",\"req\":\"^1.244.0\"},{\"features\":[\"decoding\",\"serde\"],\"name\":\"wit-parser\",\"req\":\"^0.244.0\"}],\"features\":{\"dummy-module\":[\"dep:wat\"],\"semver-check\":[\"dummy-module\"],\"wat\":[\"dep:wast\",\"dep:wat\"]}}", + "wit-parser_0.244.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.58\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"id-arena\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.7.0\"},{\"kind\":\"dev\",\"name\":\"libtest-mimic\",\"req\":\"^0.8.1\"},{\"name\":\"log\",\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.3.0\"},{\"default_features\":false,\"name\":\"semver\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.166\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0.166\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"name\":\"unicode-xid\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"simd\",\"std\",\"validate\",\"component-model\",\"features\"],\"name\":\"wasmparser\",\"optional\":true,\"req\":\"^0.244.0\"},{\"default_features\":false,\"features\":[\"component-model\"],\"name\":\"wat\",\"optional\":true,\"req\":\"^1.244.0\"}],\"features\":{\"decoding\":[\"dep:wasmparser\"],\"default\":[\"serde\",\"decoding\"],\"serde\":[\"dep:serde\",\"dep:serde_derive\",\"indexmap/serde\",\"serde_json\"],\"wat\":[\"decoding\",\"dep:wat\"]}}", "wl-clipboard-rs_0.9.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.168\"},{\"name\":\"log\",\"req\":\"^0.4.11\"},{\"features\":[\"io_safety\"],\"name\":\"os_pipe\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.7\"},{\"features\":[\"fs\",\"event\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"name\":\"tree_magic_mini\",\"req\":\"^3\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.11\"},{\"name\":\"wayland-client\",\"req\":\"^0.31.11\"},{\"features\":[\"client\",\"staging\"],\"name\":\"wayland-protocols\",\"req\":\"^0.32.9\"},{\"features\":[\"server\",\"staging\"],\"kind\":\"dev\",\"name\":\"wayland-protocols\",\"req\":\"^0.32.9\"},{\"features\":[\"client\"],\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.9\"},{\"features\":[\"server\"],\"kind\":\"dev\",\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.9\"},{\"kind\":\"dev\",\"name\":\"wayland-server\",\"req\":\"^0.31.10\"}],\"features\":{\"dlopen\":[\"native_lib\",\"wayland-backend/dlopen\",\"wayland-backend/dlopen\"],\"native_lib\":[\"wayland-backend/client_system\",\"wayland-backend/server_system\"]}}", "writeable_0.6.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"either\",\"optional\":true,\"req\":\"^1.9.0\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\"],\"either\":[\"dep:either\"]}}", "x11rb-protocol_0.13.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"all-extensions\":[\"composite\",\"damage\",\"dbe\",\"dpms\",\"dri2\",\"dri3\",\"glx\",\"present\",\"randr\",\"record\",\"render\",\"res\",\"screensaver\",\"shape\",\"shm\",\"sync\",\"xevie\",\"xf86dri\",\"xf86vidmode\",\"xfixes\",\"xinerama\",\"xinput\",\"xkb\",\"xprint\",\"xselinux\",\"xtest\",\"xv\",\"xvmc\"],\"composite\":[\"xfixes\"],\"damage\":[\"xfixes\"],\"dbe\":[],\"default\":[\"std\"],\"dpms\":[],\"dri2\":[],\"dri3\":[],\"extra-traits\":[],\"glx\":[],\"present\":[\"randr\",\"xfixes\",\"sync\",\"dri3\"],\"randr\":[\"render\"],\"record\":[],\"render\":[],\"request-parsing\":[],\"res\":[],\"resource_manager\":[\"std\"],\"screensaver\":[],\"shape\":[],\"shm\":[],\"std\":[],\"sync\":[],\"xevie\":[],\"xf86dri\":[],\"xf86vidmode\":[],\"xfixes\":[\"render\",\"shape\"],\"xinerama\":[],\"xinput\":[\"xfixes\"],\"xkb\":[],\"xprint\":[],\"xselinux\":[],\"xtest\":[],\"xv\":[\"shm\"],\"xvmc\":[\"xv\"]}}", @@ -1564,6 +1623,7 @@ "zerovec_0.11.5": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"xxhash64\"],\"name\":\"twox-hash\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec-derive\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"databake\":[\"dep:databake\"],\"derive\":[\"dep:zerovec-derive\"],\"hashmap\":[\"dep:twox-hash\",\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"yoke\":[\"dep:yoke\"]}}", "zip_2.4.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"req\":\"^1.4.1\",\"target\":\"cfg(fuzzing)\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"=4.4.18\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crc32fast\",\"req\":\"^1.4\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"name\":\"deflate64\",\"optional\":true,\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"wasm_js\",\"std\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"features\":[\"wasm_js\",\"std\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3.1\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"lzma-rs\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"memchr\",\"req\":\"^2.7\"},{\"default_features\":false,\"name\":\"nt-time\",\"optional\":true,\"req\":\"^0.10.6\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.15\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.37\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5\"},{\"name\":\"xz2\",\"optional\":true,\"req\":\"^0.1.7\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"zopfli\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"_all-features\":[],\"_deflate-any\":[],\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\",\"getrandom\",\"zeroize\"],\"chrono\":[\"chrono/default\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate64\",\"deflate\",\"lzma\",\"time\",\"zstd\",\"xz\"],\"deflate\":[\"flate2/rust_backend\",\"deflate-zopfli\",\"deflate-flate2\"],\"deflate-flate2\":[\"_deflate-any\"],\"deflate-miniz\":[\"deflate\",\"deflate-flate2\"],\"deflate-zlib\":[\"flate2/zlib\",\"deflate-flate2\"],\"deflate-zlib-ng\":[\"flate2/zlib-ng\",\"deflate-flate2\"],\"deflate-zopfli\":[\"zopfli\",\"_deflate-any\"],\"lzma\":[\"lzma-rs/stream\"],\"nt-time\":[\"dep:nt-time\"],\"unreserved\":[],\"xz\":[\"dep:xz2\"]}}", "zmij_1.0.19": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", + "zmij_1.0.21": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", "zoneinfo64_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"calendrical_calculations\",\"req\":\"^0.2.3\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.4\"},{\"default_features\":false,\"name\":\"icu_locale_core\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"tzdb-bundle-always\",\"std\"],\"kind\":\"dev\",\"name\":\"jiff\",\"req\":\"^0.2.15\"},{\"default_features\":false,\"name\":\"potential_utf\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"resb\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.220\"}],\"features\":{\"chrono\":[\"dep:chrono\"]}}", "zopfli_0.8.3": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.19.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.5.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.7\"}],\"features\":{\"default\":[\"gzip\",\"std\",\"zlib\"],\"gzip\":[\"dep:crc32fast\"],\"nightly\":[\"crc32fast?/nightly\"],\"std\":[\"crc32fast?/std\",\"dep:log\",\"simd-adler32?/std\"],\"zlib\":[\"dep:simd-adler32\"]}}", "zstd-safe_7.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.15\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"fat-lto\":[\"zstd-sys/fat-lto\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"seekable\":[\"zstd-sys/seekable\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"thin-lto\":[\"zstd-sys/thin-lto\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", @@ -1579,15 +1639,19 @@ }, "@@rules_rs+//rs/experimental/toolchains:module_extension.bzl%toolchains": { "cargo-1.93.0-aarch64-apple-darwin.tar.xz": "6443909350322ad07f09bb5edfd9ff29268e6fe88c7d78bfba7a5e254248dc25", + "cargo-1.93.0-aarch64-pc-windows-gnullvm.tar.xz": "387832b989c8eb96c9ebd66402a87962167633bc6d91a49ffb8c7903c45f3476", "cargo-1.93.0-aarch64-pc-windows-msvc.tar.xz": "155bff7a16aa7054e7ed7c3a82e362d4b302b3882d751b823e06ff63ae3f103d", "cargo-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "5998940b8b97286bb67facb1a85535eeb3d4d7a61e36a85e386e5c0c5cfe5266", "cargo-1.93.0-x86_64-apple-darwin.tar.xz": "95a47c5ed797c35419908f04188d8b7de09946e71073c4b72632b16f5b10dfae", + "cargo-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "f19766837559f90476508140cb95cc708220012ec00a854fa9f99187b1f246b6", "cargo-1.93.0-x86_64-pc-windows-msvc.tar.xz": "e59c5e2baa9ec17261f2cda6676ebf7b68b21a860e3f7451c4d964728951da75", "cargo-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "c23de3ae709ff33eed5e4ae59d1f9bcd75fa4dbaa9fb92f7b06bfb534b8db880", "clippy-1.93.0-aarch64-apple-darwin.tar.xz": "0b6e943a8d12be0e68575acf59c9ea102daf795055fcbbf862b0bfd35ec40039", + "clippy-1.93.0-aarch64-pc-windows-gnullvm.tar.xz": "296949f49be2de77ce9d1c5023a5f0a58e28e329eec03642a3da0e175a67beeb", "clippy-1.93.0-aarch64-pc-windows-msvc.tar.xz": "07bcf2edb88cdf5ead2f02e4a8493e9b0ef935a31253fac6f9f3378d8023f113", "clippy-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "872ae6d68d625946d281b91d928332e6b74f6ab269b6af842338df4338805a60", "clippy-1.93.0-x86_64-apple-darwin.tar.xz": "e6d0b1afb9607c14a1172d09ee194a032bbb3e48af913d55c5a473e0559eddde", + "clippy-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "b6f1f7264ed6943c59dedfb9531fbadcc3c0fcf273c940a63d58898b14a1060f", "clippy-1.93.0-x86_64-pc-windows-msvc.tar.xz": "25fb103390bf392980b4689ac09b2ec2ab4beefb7022a983215b613ad05eab57", "clippy-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "793108977514b15c0f45ade28ae35c58b05370cb0f22e89bd98fdfa61eabf55d", "rust-std-1.93.0-aarch64-apple-darwin.tar.xz": "8603c63715349636ed85b4fe716c4e827a727918c840e54aff5b243cedadf19b", @@ -1656,15 +1720,19 @@ "rust-std-1.93.0-x86_64-unknown-none.tar.xz": "01dcca7ae4b7e82fbfa399adb5e160afaa13143e5a17e1e0737c38cf07365fb3", "rust-std-1.93.0-x86_64-unknown-uefi.tar.xz": "ec4e439d9485ce752b56999e8e41ed82373fc833a005cf2531c6f7ef7e785392", "rustc-1.93.0-aarch64-apple-darwin.tar.xz": "092be03c02b44c405dab1232541c84f32b2d9e8295747568c3d531dd137221dc", + "rustc-1.93.0-aarch64-pc-windows-gnullvm.tar.xz": "d3bc0cdaf157e20b1f23e510b5e3c4c6e9117d08f5284c04dee60aecff1bc851", "rustc-1.93.0-aarch64-pc-windows-msvc.tar.xz": "a3ac1a8e411de8470f71b366f89d187718c431526912b181692ed0a18c56c7ad", "rustc-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "1a9045695892ec08d8e9751bf7cf7db71fe27a6202dd12ce13aca48d0602dbde", "rustc-1.93.0-x86_64-apple-darwin.tar.xz": "594bb293f0a4f444656cf8dec2149fcb979c606260efee9e09bcf8c9c6ed6ae7", + "rustc-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "0cdaa8de66f5ce21d1ea73917efc5c64f408bda49f678ddde19465ced9d5ec63", "rustc-1.93.0-x86_64-pc-windows-msvc.tar.xz": "fa17677eee0d83eb055b309953184bf87ba634923d8897f860cda65d55c6e350", "rustc-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "00c6e6740ea6a795e33568cd7514855d58408a1180cd820284a7bbf7c46af715", "rustfmt-1.93.0-aarch64-apple-darwin.tar.xz": "0dd1faedf0768ef362f4aae4424b34e8266f2b9cf5e76ea4fcaf780220b363a0", + "rustfmt-1.93.0-aarch64-pc-windows-gnullvm.tar.xz": "5888827e7fbd7d59930870b4856fce8d6d8fca5e02f6535f8ae3d7ad0ccf2d4a", "rustfmt-1.93.0-aarch64-pc-windows-msvc.tar.xz": "24eed108489567133bbfe40c8eacda1567be55fae4c526911b39eb33eb27a6cb", "rustfmt-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "92e1acb45ae642136258b4dabb39302af2d53c83e56ebd5858bc969f9e5c141a", "rustfmt-1.93.0-x86_64-apple-darwin.tar.xz": "c8453b4c5758eb39423042ffa9c23ed6128cbed2b15b581e5e1192c9cc0b1d4e", + "rustfmt-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "47167e9e78db9be4503a060dee02f4df2cda252da32175dbf44331f965a747b9", "rustfmt-1.93.0-x86_64-pc-windows-msvc.tar.xz": "5becc7c2dba4b9ab5199012cad30829235a7f7fb5d85a238697e8f0e44cbd9af", "rustfmt-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "7f81f6c17d11a7fda5b4e1b111942fb3b23d30dcec767e13e340ebfb762a5e33" } diff --git a/codex-rs/BUILD.bazel b/codex-rs/BUILD.bazel index 47324dbdca..66c7ebcb61 100644 --- a/codex-rs/BUILD.bazel +++ b/codex-rs/BUILD.bazel @@ -1,3 +1,18 @@ exports_files([ + "clippy.toml", "node-version.txt", ]) + +filegroup( + name = "workspace-files", + srcs = glob( + [ + "*", + ".cargo/**", + ], + exclude = [ + "BUILD.bazel", + ], + ), + visibility = ["//visibility:public"], +) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 681187b27f..5335cc54c3 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1337,6 +1337,7 @@ checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" name = "codex-analytics" version = "0.0.0" dependencies = [ + "codex-app-server-protocol", "codex-git-utils", "codex-login", "codex-plugin", @@ -1639,7 +1640,6 @@ dependencies = [ "codex-stdio-to-uds", "codex-terminal-detection", "codex-tui", - "codex-tui-app-server", "codex-utils-cargo-bin", "codex-utils-cli", "codex-windows-sandbox", @@ -2227,6 +2227,7 @@ dependencies = [ "codex-keyring-store", "codex-protocol", "codex-terminal-detection", + "codex-utils-template", "core_test_support", "keyring", "once_cell", @@ -2361,26 +2362,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "codex-package-manager" -version = "0.0.0" -dependencies = [ - "fd-lock", - "flate2", - "pretty_assertions", - "reqwest", - "serde", - "serde_json", - "sha2", - "tar", - "tempfile", - "thiserror 2.0.18", - "tokio", - "url", - "wiremock", - "zip", -] - [[package]] name = "codex-plugin" version = "0.0.0" @@ -2408,6 +2389,7 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-image", "codex-utils-string", + "codex-utils-template", "icu_decimal", "icu_locale_core", "icu_provider", @@ -2635,6 +2617,9 @@ dependencies = [ name = "codex-tools" version = "0.0.0" dependencies = [ + "codex-app-server-protocol", + "codex-code-mode", + "codex-protocol", "pretty_assertions", "rmcp", "serde", @@ -2655,105 +2640,8 @@ dependencies = [ "codex-app-server-client", "codex-app-server-protocol", "codex-arg0", - "codex-backend-client", "codex-chatgpt", "codex-cli", - "codex-client", - "codex-cloud-requirements", - "codex-core", - "codex-exec-server", - "codex-features", - "codex-feedback", - "codex-file-search", - "codex-git-utils", - "codex-login", - "codex-otel", - "codex-protocol", - "codex-shell-command", - "codex-state", - "codex-terminal-detection", - "codex-tui-app-server", - "codex-utils-absolute-path", - "codex-utils-approval-presets", - "codex-utils-cargo-bin", - "codex-utils-cli", - "codex-utils-elapsed", - "codex-utils-fuzzy-match", - "codex-utils-oss", - "codex-utils-pty", - "codex-utils-sandbox-summary", - "codex-utils-sleep-inhibitor", - "codex-utils-string", - "codex-windows-sandbox", - "color-eyre", - "cpal", - "crossterm", - "derive_more 2.1.1", - "diffy", - "dirs", - "dunce", - "hound", - "image", - "insta", - "itertools 0.14.0", - "lazy_static", - "libc", - "pathdiff", - "pretty_assertions", - "pulldown-cmark", - "rand 0.9.2", - "ratatui", - "ratatui-macros", - "regex-lite", - "reqwest", - "rmcp", - "serde", - "serde_json", - "serial_test", - "shlex", - "strum 0.27.2", - "strum_macros 0.28.0", - "supports-color 3.0.2", - "syntect", - "tempfile", - "textwrap 0.16.2", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tokio-util", - "toml 0.9.11+spec-1.1.0", - "tracing", - "tracing-appender", - "tracing-subscriber", - "two-face", - "unicode-segmentation", - "unicode-width 0.2.1", - "url", - "uuid", - "vt100", - "webbrowser", - "which 8.0.0", - "windows-sys 0.52.0", - "winsplit", -] - -[[package]] -name = "codex-tui-app-server" -version = "0.0.0" -dependencies = [ - "anyhow", - "arboard", - "assert_matches", - "base64 0.22.1", - "chrono", - "clap", - "codex-ansi-escape", - "codex-app-server-client", - "codex-app-server-protocol", - "codex-arg0", - "codex-chatgpt", - "codex-cli", - "codex-client", "codex-cloud-requirements", "codex-core", "codex-features", @@ -2785,7 +2673,6 @@ dependencies = [ "diffy", "dirs", "dunce", - "hound", "image", "insta", "itertools 0.14.0", @@ -4291,17 +4178,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "filetime" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" -dependencies = [ - "cfg-if", - "libc", - "libredox", -] - [[package]] name = "find-crate" version = "0.6.3" @@ -4946,12 +4822,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "hound" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - [[package]] name = "http" version = "0.2.12" @@ -8218,7 +8088,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "native-tls", "percent-encoding", "pin-project-lite", @@ -9876,17 +9745,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "tar" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "tempfile" version = "3.24.0" @@ -12013,16 +11871,6 @@ dependencies = [ "time", ] -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix 1.1.3", -] - [[package]] name = "xdg-home" version = "1.3.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index f05355e393..d5a4baa4d5 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -50,7 +50,6 @@ members = [ "stdio-to-uds", "otel", "tui", - "tui_app_server", "tools", "v8-poc", "utils/absolute-path", @@ -81,7 +80,6 @@ members = [ "state", "terminal-detection", "codex-experimental-api-macros", - "package-manager", "plugin", ] resolver = "2" @@ -102,7 +100,6 @@ codex-ansi-escape = { path = "ansi-escape" } codex-analytics = { path = "analytics" } codex-api = { path = "codex-api" } codex-code-mode = { path = "code-mode" } -codex-package-manager = { path = "package-manager" } codex-app-server = { path = "app-server" } codex-app-server-client = { path = "app-server-client" } codex-app-server-protocol = { path = "app-server-protocol" } @@ -153,7 +150,6 @@ codex-stdio-to-uds = { path = "stdio-to-uds" } codex-terminal-detection = { path = "terminal-detection" } codex-tools = { path = "tools" } codex-tui = { path = "tui" } -codex-tui-app-server = { path = "tui_app_server" } codex-v8-poc = { path = "v8-poc" } codex-utils-absolute-path = { path = "utils/absolute-path" } codex-utils-approval-presets = { path = "utils/approval-presets" } @@ -213,11 +209,9 @@ dirs = "6" dotenvy = "0.15.7" dunce = "1.0.4" encoding_rs = "0.8.35" -fd-lock = "4.0.4" env-flags = "0.1.1" env_logger = "0.11.9" eventsource-stream = "0.2.3" -flate2 = "1.1.4" futures = { version = "0.3", default-features = false } gethostname = "1.1.0" globset = "0.4" @@ -309,7 +303,6 @@ supports-color = "3.0.2" syntect = "5" sys-locale = "0.3.2" tempfile = "3.23.0" -tar = "0.4.45" test-log = "0.2.19" textwrap = "0.16.2" thiserror = "2.0.17" @@ -395,7 +388,6 @@ unwrap_used = "deny" ignored = [ "icu_provider", "openssl-sys", - "codex-package-manager", "codex-utils-readiness", "codex-utils-template", "codex-v8-poc", diff --git a/codex-rs/README.md b/codex-rs/README.md index ad8a0506ff..6307668f39 100644 --- a/codex-rs/README.md +++ b/codex-rs/README.md @@ -50,7 +50,7 @@ You can enable notifications by configuring a script that is run whenever the ag ### `codex exec` to run Codex programmatically/non-interactively -To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on. +To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. If you provide both a prompt argument and piped stdin, Codex appends stdin as a `` block after the prompt so patterns like `echo "my output" | codex exec "Summarize this concisely"` work naturally. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on. Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk. ### Experimenting with the Codex Sandbox diff --git a/codex-rs/analytics/Cargo.toml b/codex-rs/analytics/Cargo.toml index 9633eebb14..63bf63c911 100644 --- a/codex-rs/analytics/Cargo.toml +++ b/codex-rs/analytics/Cargo.toml @@ -13,6 +13,7 @@ path = "src/lib.rs" workspace = true [dependencies] +codex-app-server-protocol = { workspace = true } codex-git-utils = { workspace = true } codex-login = { workspace = true } codex-plugin = { workspace = true } diff --git a/codex-rs/analytics/src/analytics_client.rs b/codex-rs/analytics/src/analytics_client.rs index ac8d10b6c1..24618168cc 100644 --- a/codex-rs/analytics/src/analytics_client.rs +++ b/codex-rs/analytics/src/analytics_client.rs @@ -1,3 +1,8 @@ +use codex_app_server_protocol::ClientRequest; +use codex_app_server_protocol::ClientResponse; +use codex_app_server_protocol::InitializeParams; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ServerNotification; use codex_git_utils::collect_git_info; use codex_git_utils::get_git_repo_root; use codex_login::AuthManager; @@ -56,9 +61,73 @@ pub struct AppInvocation { pub invocation_type: Option, } +pub enum AnalyticsFact { + Initialize { + connection_id: u64, + params: InitializeParams, + }, + Request { + connection_id: u64, + request_id: RequestId, + request: Box, + }, + Response { + connection_id: u64, + response: Box, + }, + Notification(Box), + // Facts that do not naturally exist on the app-server protocol surface, or + // would require non-trivial protocol reshaping on this branch. + Custom(CustomAnalyticsFact), +} + +pub enum CustomAnalyticsFact { + SkillInvoked(SkillInvokedInput), + AppMentioned(AppMentionedInput), + AppUsed(AppUsedInput), + PluginUsed(PluginUsedInput), + PluginStateChanged(PluginStateChangedInput), +} + +pub struct SkillInvokedInput { + pub tracking: TrackEventsContext, + pub invocations: Vec, +} + +pub struct AppMentionedInput { + pub tracking: TrackEventsContext, + pub mentions: Vec, +} + +pub struct AppUsedInput { + pub tracking: TrackEventsContext, + pub app: AppInvocation, +} + +pub struct PluginUsedInput { + pub tracking: TrackEventsContext, + pub plugin: PluginTelemetryMetadata, +} + +pub struct PluginStateChangedInput { + pub plugin: PluginTelemetryMetadata, + pub state: PluginState, +} + +#[derive(Clone, Copy)] +pub enum PluginState { + Installed, + Uninstalled, + Enabled, + Disabled, +} + +#[derive(Default)] +pub struct AnalyticsReducer; + #[derive(Clone)] pub(crate) struct AnalyticsEventsQueue { - sender: mpsc::Sender, + sender: mpsc::Sender, app_used_emitted_keys: Arc>>, plugin_used_emitted_keys: Arc>>, } @@ -73,33 +142,11 @@ impl AnalyticsEventsQueue { pub(crate) fn new(auth_manager: Arc, base_url: String) -> Self { let (sender, mut receiver) = mpsc::channel(ANALYTICS_EVENTS_QUEUE_SIZE); tokio::spawn(async move { - while let Some(job) = receiver.recv().await { - match job { - TrackEventsJob::SkillInvocations(job) => { - send_track_skill_invocations(&auth_manager, &base_url, job).await; - } - TrackEventsJob::AppMentioned(job) => { - send_track_app_mentioned(&auth_manager, &base_url, job).await; - } - TrackEventsJob::AppUsed(job) => { - send_track_app_used(&auth_manager, &base_url, job).await; - } - TrackEventsJob::PluginUsed(job) => { - send_track_plugin_used(&auth_manager, &base_url, job).await; - } - TrackEventsJob::PluginInstalled(job) => { - send_track_plugin_installed(&auth_manager, &base_url, job).await; - } - TrackEventsJob::PluginUninstalled(job) => { - send_track_plugin_uninstalled(&auth_manager, &base_url, job).await; - } - TrackEventsJob::PluginEnabled(job) => { - send_track_plugin_enabled(&auth_manager, &base_url, job).await; - } - TrackEventsJob::PluginDisabled(job) => { - send_track_plugin_disabled(&auth_manager, &base_url, job).await; - } - } + let mut reducer = AnalyticsReducer; + while let Some(input) = receiver.recv().await { + let mut events = Vec::new(); + reducer.ingest(input, &mut events).await; + send_track_events(&auth_manager, &base_url, events).await; } }); Self { @@ -109,8 +156,8 @@ impl AnalyticsEventsQueue { } } - fn try_send(&self, job: TrackEventsJob) { - if self.sender.try_send(job).is_err() { + fn try_send(&self, input: AnalyticsFact) { + if self.sender.try_send(input).is_err() { //TODO: add a metric for this tracing::warn!("dropping analytics events: queue is full"); } @@ -163,114 +210,86 @@ impl AnalyticsEventsClient { tracking: TrackEventsContext, invocations: Vec, ) { - track_skill_invocations( - &self.queue, - self.analytics_enabled, - Some(tracking), - invocations, - ); + if invocations.is_empty() { + return; + } + self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked( + SkillInvokedInput { + tracking, + invocations, + }, + ))); } pub fn track_app_mentioned(&self, tracking: TrackEventsContext, mentions: Vec) { - track_app_mentioned( - &self.queue, - self.analytics_enabled, - Some(tracking), - mentions, - ); + if mentions.is_empty() { + return; + } + self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppMentioned( + AppMentionedInput { tracking, mentions }, + ))); } pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) { - track_app_used(&self.queue, self.analytics_enabled, Some(tracking), app); + if !self.queue.should_enqueue_app_used(&tracking, &app) { + return; + } + self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed( + AppUsedInput { tracking, app }, + ))); } pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) { - track_plugin_used(&self.queue, self.analytics_enabled, Some(tracking), plugin); + if !self.queue.should_enqueue_plugin_used(&tracking, &plugin) { + return; + } + self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed( + PluginUsedInput { tracking, plugin }, + ))); } pub fn track_plugin_installed(&self, plugin: PluginTelemetryMetadata) { - track_plugin_management( - &self.queue, - self.analytics_enabled, - PluginManagementEventType::Installed, - plugin, - ); + self.record_fact(AnalyticsFact::Custom( + CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput { + plugin, + state: PluginState::Installed, + }), + )); } pub fn track_plugin_uninstalled(&self, plugin: PluginTelemetryMetadata) { - track_plugin_management( - &self.queue, - self.analytics_enabled, - PluginManagementEventType::Uninstalled, - plugin, - ); + self.record_fact(AnalyticsFact::Custom( + CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput { + plugin, + state: PluginState::Uninstalled, + }), + )); } pub fn track_plugin_enabled(&self, plugin: PluginTelemetryMetadata) { - track_plugin_management( - &self.queue, - self.analytics_enabled, - PluginManagementEventType::Enabled, - plugin, - ); + self.record_fact(AnalyticsFact::Custom( + CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput { + plugin, + state: PluginState::Enabled, + }), + )); } pub fn track_plugin_disabled(&self, plugin: PluginTelemetryMetadata) { - track_plugin_management( - &self.queue, - self.analytics_enabled, - PluginManagementEventType::Disabled, - plugin, - ); + self.record_fact(AnalyticsFact::Custom( + CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput { + plugin, + state: PluginState::Disabled, + }), + )); } -} -enum TrackEventsJob { - SkillInvocations(TrackSkillInvocationsJob), - AppMentioned(TrackAppMentionedJob), - AppUsed(TrackAppUsedJob), - PluginUsed(TrackPluginUsedJob), - PluginInstalled(TrackPluginManagementJob), - PluginUninstalled(TrackPluginManagementJob), - PluginEnabled(TrackPluginManagementJob), - PluginDisabled(TrackPluginManagementJob), -} - -struct TrackSkillInvocationsJob { - analytics_enabled: Option, - tracking: TrackEventsContext, - invocations: Vec, -} - -struct TrackAppMentionedJob { - analytics_enabled: Option, - tracking: TrackEventsContext, - mentions: Vec, -} - -struct TrackAppUsedJob { - analytics_enabled: Option, - tracking: TrackEventsContext, - app: AppInvocation, -} - -struct TrackPluginUsedJob { - analytics_enabled: Option, - tracking: TrackEventsContext, - plugin: PluginTelemetryMetadata, -} - -struct TrackPluginManagementJob { - analytics_enabled: Option, - plugin: PluginTelemetryMetadata, -} - -#[derive(Clone, Copy)] -enum PluginManagementEventType { - Installed, - Uninstalled, - Enabled, - Disabled, + fn record_fact(&self, input: AnalyticsFact) { + if self.analytics_enabled == Some(false) { + return; + } + self.queue.try_send(input); + } } const ANALYTICS_EVENTS_QUEUE_SIZE: usize = 256; @@ -368,286 +387,145 @@ struct CodexPluginUsedEventRequest { event_params: CodexPluginUsedMetadata, } -pub(crate) fn track_skill_invocations( - queue: &AnalyticsEventsQueue, - analytics_enabled: Option, - tracking: Option, - invocations: Vec, -) { - if analytics_enabled == Some(false) { - return; - } - let Some(tracking) = tracking else { - return; - }; - if invocations.is_empty() { - return; - } - let job = TrackEventsJob::SkillInvocations(TrackSkillInvocationsJob { - analytics_enabled, - tracking, - invocations, - }); - queue.try_send(job); -} - -pub(crate) fn track_app_mentioned( - queue: &AnalyticsEventsQueue, - analytics_enabled: Option, - tracking: Option, - mentions: Vec, -) { - if analytics_enabled == Some(false) { - return; - } - let Some(tracking) = tracking else { - return; - }; - if mentions.is_empty() { - return; - } - let job = TrackEventsJob::AppMentioned(TrackAppMentionedJob { - analytics_enabled, - tracking, - mentions, - }); - queue.try_send(job); -} - -pub(crate) fn track_app_used( - queue: &AnalyticsEventsQueue, - analytics_enabled: Option, - tracking: Option, - app: AppInvocation, -) { - if analytics_enabled == Some(false) { - return; - } - let Some(tracking) = tracking else { - return; - }; - if !queue.should_enqueue_app_used(&tracking, &app) { - return; - } - let job = TrackEventsJob::AppUsed(TrackAppUsedJob { - analytics_enabled, - tracking, - app, - }); - queue.try_send(job); -} - -pub(crate) fn track_plugin_used( - queue: &AnalyticsEventsQueue, - analytics_enabled: Option, - tracking: Option, - plugin: PluginTelemetryMetadata, -) { - if analytics_enabled == Some(false) { - return; - } - let Some(tracking) = tracking else { - return; - }; - if !queue.should_enqueue_plugin_used(&tracking, &plugin) { - return; - } - let job = TrackEventsJob::PluginUsed(TrackPluginUsedJob { - analytics_enabled, - tracking, - plugin, - }); - queue.try_send(job); -} - -fn track_plugin_management( - queue: &AnalyticsEventsQueue, - analytics_enabled: Option, - event_type: PluginManagementEventType, - plugin: PluginTelemetryMetadata, -) { - if analytics_enabled == Some(false) { - return; - } - let job = TrackPluginManagementJob { - analytics_enabled, - plugin, - }; - let job = match event_type { - PluginManagementEventType::Installed => TrackEventsJob::PluginInstalled(job), - PluginManagementEventType::Uninstalled => TrackEventsJob::PluginUninstalled(job), - PluginManagementEventType::Enabled => TrackEventsJob::PluginEnabled(job), - PluginManagementEventType::Disabled => TrackEventsJob::PluginDisabled(job), - }; - queue.try_send(job); -} - -async fn send_track_skill_invocations( - auth_manager: &AuthManager, - base_url: &str, - job: TrackSkillInvocationsJob, -) { - let TrackSkillInvocationsJob { - analytics_enabled, - tracking, - invocations, - } = job; - let mut events = Vec::with_capacity(invocations.len()); - for invocation in invocations { - let skill_scope = match invocation.skill_scope { - SkillScope::User => "user", - SkillScope::Repo => "repo", - SkillScope::System => "system", - SkillScope::Admin => "admin", - }; - let repo_root = get_git_repo_root(invocation.skill_path.as_path()); - let repo_url = if let Some(root) = repo_root.as_ref() { - collect_git_info(root) - .await - .and_then(|info| info.repository_url) - } else { - None - }; - let skill_id = skill_id_for_local_skill( - repo_url.as_deref(), - repo_root.as_deref(), - invocation.skill_path.as_path(), - invocation.skill_name.as_str(), - ); - events.push(TrackEventRequest::SkillInvocation( - SkillInvocationEventRequest { - event_type: "skill_invocation", - skill_id, - skill_name: invocation.skill_name.clone(), - event_params: SkillInvocationEventParams { - thread_id: Some(tracking.thread_id.clone()), - invoke_type: Some(invocation.invocation_type), - model_slug: Some(tracking.model_slug.clone()), - product_client_id: Some(originator().value), - repo_url, - skill_scope: Some(skill_scope.to_string()), - }, +impl AnalyticsReducer { + async fn ingest(&mut self, input: AnalyticsFact, out: &mut Vec) { + match input { + AnalyticsFact::Initialize { + connection_id: _connection_id, + params: _params, + } => {} + AnalyticsFact::Request { + connection_id: _connection_id, + request_id: _request_id, + request: _request, + } => {} + AnalyticsFact::Response { + connection_id: _connection_id, + response: _response, + } => {} + AnalyticsFact::Notification(_notification) => {} + AnalyticsFact::Custom(input) => match input { + CustomAnalyticsFact::SkillInvoked(input) => { + self.ingest_skill_invoked(input, out).await; + } + CustomAnalyticsFact::AppMentioned(input) => { + self.ingest_app_mentioned(input, out); + } + CustomAnalyticsFact::AppUsed(input) => { + self.ingest_app_used(input, out); + } + CustomAnalyticsFact::PluginUsed(input) => { + self.ingest_plugin_used(input, out); + } + CustomAnalyticsFact::PluginStateChanged(input) => { + self.ingest_plugin_state_changed(input, out); + } }, - )); + } } - send_track_events(auth_manager, analytics_enabled, base_url, events).await; -} + async fn ingest_skill_invoked( + &mut self, + input: SkillInvokedInput, + out: &mut Vec, + ) { + let SkillInvokedInput { + tracking, + invocations, + } = input; + for invocation in invocations { + let skill_scope = match invocation.skill_scope { + SkillScope::User => "user", + SkillScope::Repo => "repo", + SkillScope::System => "system", + SkillScope::Admin => "admin", + }; + let repo_root = get_git_repo_root(invocation.skill_path.as_path()); + let repo_url = if let Some(root) = repo_root.as_ref() { + collect_git_info(root) + .await + .and_then(|info| info.repository_url) + } else { + None + }; + let skill_id = skill_id_for_local_skill( + repo_url.as_deref(), + repo_root.as_deref(), + invocation.skill_path.as_path(), + invocation.skill_name.as_str(), + ); + out.push(TrackEventRequest::SkillInvocation( + SkillInvocationEventRequest { + event_type: "skill_invocation", + skill_id, + skill_name: invocation.skill_name.clone(), + event_params: SkillInvocationEventParams { + thread_id: Some(tracking.thread_id.clone()), + invoke_type: Some(invocation.invocation_type), + model_slug: Some(tracking.model_slug.clone()), + product_client_id: Some(originator().value), + repo_url, + skill_scope: Some(skill_scope.to_string()), + }, + }, + )); + } + } -async fn send_track_app_mentioned( - auth_manager: &AuthManager, - base_url: &str, - job: TrackAppMentionedJob, -) { - let TrackAppMentionedJob { - analytics_enabled, - tracking, - mentions, - } = job; - let events = mentions - .into_iter() - .map(|mention| { + fn ingest_app_mentioned(&mut self, input: AppMentionedInput, out: &mut Vec) { + let AppMentionedInput { tracking, mentions } = input; + out.extend(mentions.into_iter().map(|mention| { let event_params = codex_app_metadata(&tracking, mention); TrackEventRequest::AppMentioned(CodexAppMentionedEventRequest { event_type: "codex_app_mentioned", event_params, }) - }) - .collect::>(); + })); + } - send_track_events(auth_manager, analytics_enabled, base_url, events).await; + fn ingest_app_used(&mut self, input: AppUsedInput, out: &mut Vec) { + let AppUsedInput { tracking, app } = input; + let event_params = codex_app_metadata(&tracking, app); + out.push(TrackEventRequest::AppUsed(CodexAppUsedEventRequest { + event_type: "codex_app_used", + event_params, + })); + } + + fn ingest_plugin_used(&mut self, input: PluginUsedInput, out: &mut Vec) { + let PluginUsedInput { tracking, plugin } = input; + out.push(TrackEventRequest::PluginUsed(CodexPluginUsedEventRequest { + event_type: "codex_plugin_used", + event_params: codex_plugin_used_metadata(&tracking, plugin), + })); + } + + fn ingest_plugin_state_changed( + &mut self, + input: PluginStateChangedInput, + out: &mut Vec, + ) { + let PluginStateChangedInput { plugin, state } = input; + let event = CodexPluginEventRequest { + event_type: plugin_state_event_type(state), + event_params: codex_plugin_metadata(plugin), + }; + out.push(match state { + PluginState::Installed => TrackEventRequest::PluginInstalled(event), + PluginState::Uninstalled => TrackEventRequest::PluginUninstalled(event), + PluginState::Enabled => TrackEventRequest::PluginEnabled(event), + PluginState::Disabled => TrackEventRequest::PluginDisabled(event), + }); + } } -async fn send_track_app_used(auth_manager: &AuthManager, base_url: &str, job: TrackAppUsedJob) { - let TrackAppUsedJob { - analytics_enabled, - tracking, - app, - } = job; - let event_params = codex_app_metadata(&tracking, app); - let events = vec![TrackEventRequest::AppUsed(CodexAppUsedEventRequest { - event_type: "codex_app_used", - event_params, - })]; - - send_track_events(auth_manager, analytics_enabled, base_url, events).await; -} - -async fn send_track_plugin_used( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginUsedJob, -) { - let TrackPluginUsedJob { - analytics_enabled, - tracking, - plugin, - } = job; - let events = vec![TrackEventRequest::PluginUsed(CodexPluginUsedEventRequest { - event_type: "codex_plugin_used", - event_params: codex_plugin_used_metadata(&tracking, plugin), - })]; - - send_track_events(auth_manager, analytics_enabled, base_url, events).await; -} - -async fn send_track_plugin_installed( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginManagementJob, -) { - send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_installed").await; -} - -async fn send_track_plugin_uninstalled( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginManagementJob, -) { - send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_uninstalled") - .await; -} - -async fn send_track_plugin_enabled( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginManagementJob, -) { - send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_enabled").await; -} - -async fn send_track_plugin_disabled( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginManagementJob, -) { - send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_disabled").await; -} - -async fn send_track_plugin_management_event( - auth_manager: &AuthManager, - base_url: &str, - job: TrackPluginManagementJob, - event_type: &'static str, -) { - let TrackPluginManagementJob { - analytics_enabled, - plugin, - } = job; - let event_params = codex_plugin_metadata(plugin); - let event = CodexPluginEventRequest { - event_type, - event_params, - }; - let events = vec![match event_type { - "codex_plugin_installed" => TrackEventRequest::PluginInstalled(event), - "codex_plugin_uninstalled" => TrackEventRequest::PluginUninstalled(event), - "codex_plugin_enabled" => TrackEventRequest::PluginEnabled(event), - "codex_plugin_disabled" => TrackEventRequest::PluginDisabled(event), - _ => unreachable!("unknown plugin management event type"), - }]; - - send_track_events(auth_manager, analytics_enabled, base_url, events).await; +fn plugin_state_event_type(state: PluginState) -> &'static str { + match state { + PluginState::Installed => "codex_plugin_installed", + PluginState::Uninstalled => "codex_plugin_uninstalled", + PluginState::Enabled => "codex_plugin_enabled", + PluginState::Disabled => "codex_plugin_disabled", + } } fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> CodexAppMetadata { @@ -699,13 +577,9 @@ fn codex_plugin_used_metadata( async fn send_track_events( auth_manager: &AuthManager, - analytics_enabled: Option, base_url: &str, events: Vec, ) { - if analytics_enabled == Some(false) { - return; - } if events.is_empty() { return; } diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index f64da8f589..991f977160 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -1,10 +1,20 @@ use super::AnalyticsEventsQueue; +use super::AnalyticsFact; +use super::AnalyticsReducer; use super::AppInvocation; +use super::AppMentionedInput; +use super::AppUsedInput; use super::CodexAppMentionedEventRequest; use super::CodexAppUsedEventRequest; use super::CodexPluginEventRequest; use super::CodexPluginUsedEventRequest; +use super::CustomAnalyticsFact; use super::InvocationType; +use super::PluginState; +use super::PluginStateChangedInput; +use super::PluginUsedInput; +use super::SkillInvocation; +use super::SkillInvokedInput; use super::TrackEventRequest; use super::TrackEventsContext; use super::codex_app_metadata; @@ -49,7 +59,11 @@ fn normalize_path_for_skill_id_repo_scoped_uses_relative_path() { fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() { let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md"); - let path = normalize_path_for_skill_id(None, None, skill_path.as_path()); + let path = normalize_path_for_skill_id( + /*repo_url*/ None, + /*repo_root*/ None, + skill_path.as_path(), + ); let expected = expected_absolute_path(&skill_path); assert_eq!(path, expected); @@ -59,7 +73,11 @@ fn normalize_path_for_skill_id_user_scoped_uses_absolute_path() { fn normalize_path_for_skill_id_admin_scoped_uses_absolute_path() { let skill_path = PathBuf::from("/etc/codex/skills/doc/SKILL.md"); - let path = normalize_path_for_skill_id(None, None, skill_path.as_path()); + let path = normalize_path_for_skill_id( + /*repo_url*/ None, + /*repo_root*/ None, + skill_path.as_path(), + ); let expected = expected_absolute_path(&skill_path); assert_eq!(path, expected); @@ -272,6 +290,145 @@ fn plugin_used_dedupe_is_keyed_by_turn_and_plugin() { assert_eq!(queue.should_enqueue_plugin_used(&turn_2, &plugin), true); } +#[tokio::test] +async fn reducer_ingests_skill_invoked_fact() { + let mut reducer = AnalyticsReducer; + let mut events = Vec::new(); + let tracking = TrackEventsContext { + model_slug: "gpt-5".to_string(), + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + }; + let skill_path = PathBuf::from("/Users/abc/.codex/skills/doc/SKILL.md"); + let expected_skill_id = super::skill_id_for_local_skill( + /*repo_url*/ None, + /*repo_root*/ None, + skill_path.as_path(), + "doc", + ); + + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked(SkillInvokedInput { + tracking, + invocations: vec![SkillInvocation { + skill_name: "doc".to_string(), + skill_scope: codex_protocol::protocol::SkillScope::User, + skill_path, + invocation_type: InvocationType::Explicit, + }], + })), + &mut events, + ) + .await; + + let payload = serde_json::to_value(&events).expect("serialize events"); + assert_eq!( + payload, + json!([{ + "event_type": "skill_invocation", + "skill_id": expected_skill_id, + "skill_name": "doc", + "event_params": { + "product_client_id": originator().value, + "skill_scope": "user", + "repo_url": null, + "thread_id": "thread-1", + "invoke_type": "explicit", + "model_slug": "gpt-5" + } + }]) + ); +} + +#[tokio::test] +async fn reducer_ingests_app_and_plugin_facts() { + let mut reducer = AnalyticsReducer; + let mut events = Vec::new(); + let tracking = TrackEventsContext { + model_slug: "gpt-5".to_string(), + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + }; + + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::AppMentioned(AppMentionedInput { + tracking: tracking.clone(), + mentions: vec![AppInvocation { + connector_id: Some("calendar".to_string()), + app_name: Some("Calendar".to_string()), + invocation_type: Some(InvocationType::Explicit), + }], + })), + &mut events, + ) + .await; + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(AppUsedInput { + tracking: tracking.clone(), + app: AppInvocation { + connector_id: Some("drive".to_string()), + app_name: Some("Drive".to_string()), + invocation_type: Some(InvocationType::Implicit), + }, + })), + &mut events, + ) + .await; + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed(PluginUsedInput { + tracking, + plugin: sample_plugin_metadata(), + })), + &mut events, + ) + .await; + + let payload = serde_json::to_value(&events).expect("serialize events"); + assert_eq!(payload.as_array().expect("events array").len(), 3); + assert_eq!(payload[0]["event_type"], "codex_app_mentioned"); + assert_eq!(payload[1]["event_type"], "codex_app_used"); + assert_eq!(payload[2]["event_type"], "codex_plugin_used"); +} + +#[tokio::test] +async fn reducer_ingests_plugin_state_changed_fact() { + let mut reducer = AnalyticsReducer; + let mut events = Vec::new(); + + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::PluginStateChanged( + PluginStateChangedInput { + plugin: sample_plugin_metadata(), + state: PluginState::Disabled, + }, + )), + &mut events, + ) + .await; + + let payload = serde_json::to_value(&events).expect("serialize events"); + assert_eq!( + payload, + json!([{ + "event_type": "codex_plugin_disabled", + "event_params": { + "plugin_id": "sample@test", + "plugin_name": "sample", + "marketplace_name": "test", + "has_skills": true, + "mcp_server_count": 2, + "connector_ids": ["calendar", "drive"], + "product_client_id": originator().value + } + }]) + ); +} + fn sample_plugin_metadata() -> PluginTelemetryMetadata { PluginTelemetryMetadata { plugin_id: PluginId::parse("sample@test").expect("valid plugin id"), diff --git a/codex-rs/analytics/src/lib.rs b/codex-rs/analytics/src/lib.rs index e36f5d71b5..5b15d2830a 100644 --- a/codex-rs/analytics/src/lib.rs +++ b/codex-rs/analytics/src/lib.rs @@ -1,8 +1,17 @@ mod analytics_client; pub use analytics_client::AnalyticsEventsClient; +pub use analytics_client::AnalyticsFact; +pub use analytics_client::AnalyticsReducer; pub use analytics_client::AppInvocation; +pub use analytics_client::AppMentionedInput; +pub use analytics_client::AppUsedInput; +pub use analytics_client::CustomAnalyticsFact; pub use analytics_client::InvocationType; +pub use analytics_client::PluginState; +pub use analytics_client::PluginStateChangedInput; +pub use analytics_client::PluginUsedInput; pub use analytics_client::SkillInvocation; +pub use analytics_client::SkillInvokedInput; pub use analytics_client::TrackEventsContext; pub use analytics_client::build_track_events_context; diff --git a/codex-rs/app-server-client/src/lib.rs b/codex-rs/app-server-client/src/lib.rs index ad9eca65e7..39768820de 100644 --- a/codex-rs/app-server-client/src/lib.rs +++ b/codex-rs/app-server-client/src/lib.rs @@ -917,7 +917,7 @@ mod tests { + 'static, Fut: std::future::Future + Send + 'static, { - start_test_remote_server_with_auth(None, handler).await + start_test_remote_server_with_auth(/*expected_auth_token*/ None, handler).await } async fn start_test_remote_server_with_auth( @@ -1164,7 +1164,8 @@ mod tests { #[tokio::test] async fn tiny_channel_capacity_still_supports_request_roundtrip() { - let client = start_test_client_with_capacity(SessionSource::Exec, 1).await; + let client = + start_test_client_with_capacity(SessionSource::Exec, /*channel_capacity*/ 1).await; let _response: ConfigRequirementsReadResponse = client .request_typed(ClientRequest::ConfigRequirementsRead { request_id: RequestId::Integer(1), diff --git a/codex-rs/app-server-client/src/remote.rs b/codex-rs/app-server-client/src/remote.rs index a82b924e45..c3b22c575d 100644 --- a/codex-rs/app-server-client/src/remote.rs +++ b/codex-rs/app-server-client/src/remote.rs @@ -4,9 +4,8 @@ This module implements the websocket-backed app-server client transport. It owns the remote connection lifecycle, including the initialize/initialized handshake, JSON-RPC request/response routing, server-request resolution, and notification streaming. The rest of the crate uses the same `AppServerEvent` -surface for both in-process and remote transports, so callers such as -`tui_app_server` can switch between them without changing their higher-level -session logic. +surface for both in-process and remote transports, so callers such as the TUI +can switch between them without changing their higher-level session logic. */ use std::collections::HashMap; diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 9227a97d55..7c94419844 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1156,6 +1156,22 @@ "title": "ChatgptLoginAccountParams", "type": "object" }, + { + "properties": { + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodeLoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptDeviceCodeLoginAccountParams", + "type": "object" + }, { "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 8ca93137c6..e1a7cd3f3d 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -1777,7 +1777,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index f52faf5b15..084da013e4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -8569,6 +8569,22 @@ "title": "Chatgptv2::LoginAccountParams", "type": "object" }, + { + "properties": { + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParams", + "type": "object" + }, { "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", "properties": { @@ -8650,6 +8666,36 @@ "title": "Chatgptv2::LoginAccountResponse", "type": "object" }, + { + "properties": { + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponseType", + "type": "string" + }, + "userCode": { + "description": "One-time code the user must enter after signing in.", + "type": "string" + }, + "verificationUrl": { + "description": "URL the client should open in a browser to complete device code authorization.", + "type": "string" + } + }, + "required": [ + "loginId", + "type", + "userCode", + "verificationUrl" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponse", + "type": "object" + }, { "properties": { "type": { @@ -9203,6 +9249,13 @@ ], "type": "string" }, + "NetworkDomainPermission": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, "NetworkRequirements": { "properties": { "allowLocalBinding": { @@ -9212,6 +9265,7 @@ ] }, "allowUnixSockets": { + "description": "Legacy compatibility view derived from `unix_sockets`.", "items": { "type": "string" }, @@ -9227,6 +9281,7 @@ ] }, "allowedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -9248,6 +9303,7 @@ ] }, "deniedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -9256,6 +9312,16 @@ "null" ] }, + "domains": { + "additionalProperties": { + "$ref": "#/definitions/v2/NetworkDomainPermission" + }, + "description": "Canonical network permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] + }, "enabled": { "type": [ "boolean", @@ -9270,6 +9336,13 @@ "null" ] }, + "managedAllowedDomainsOnly": { + "description": "When true, only managed allowlist entries are respected while managed network enforcement is active.", + "type": [ + "boolean", + "null" + ] + }, "socksPort": { "format": "uint16", "minimum": 0.0, @@ -9277,10 +9350,27 @@ "integer", "null" ] + }, + "unixSockets": { + "additionalProperties": { + "$ref": "#/definitions/v2/NetworkUnixSocketPermission" + }, + "description": "Canonical unix socket permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] } }, "type": "object" }, + "NetworkUnixSocketPermission": { + "enum": [ + "allow", + "none" + ], + "type": "string" + }, "NonSteerableTurnKind": { "enum": [ "review", @@ -9413,7 +9503,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 5d053604f5..59dab32b19 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -5383,6 +5383,22 @@ "title": "Chatgptv2::LoginAccountParams", "type": "object" }, + { + "properties": { + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParams", + "type": "object" + }, { "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", "properties": { @@ -5464,6 +5480,36 @@ "title": "Chatgptv2::LoginAccountResponse", "type": "object" }, + { + "properties": { + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponseType", + "type": "string" + }, + "userCode": { + "description": "One-time code the user must enter after signing in.", + "type": "string" + }, + "verificationUrl": { + "description": "URL the client should open in a browser to complete device code authorization.", + "type": "string" + } + }, + "required": [ + "loginId", + "type", + "userCode", + "verificationUrl" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponse", + "type": "object" + }, { "properties": { "type": { @@ -6017,6 +6063,13 @@ ], "type": "string" }, + "NetworkDomainPermission": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, "NetworkRequirements": { "properties": { "allowLocalBinding": { @@ -6026,6 +6079,7 @@ ] }, "allowUnixSockets": { + "description": "Legacy compatibility view derived from `unix_sockets`.", "items": { "type": "string" }, @@ -6041,6 +6095,7 @@ ] }, "allowedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -6062,6 +6117,7 @@ ] }, "deniedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -6070,6 +6126,16 @@ "null" ] }, + "domains": { + "additionalProperties": { + "$ref": "#/definitions/NetworkDomainPermission" + }, + "description": "Canonical network permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] + }, "enabled": { "type": [ "boolean", @@ -6084,6 +6150,13 @@ "null" ] }, + "managedAllowedDomainsOnly": { + "description": "When true, only managed allowlist entries are respected while managed network enforcement is active.", + "type": [ + "boolean", + "null" + ] + }, "socksPort": { "format": "uint16", "minimum": 0.0, @@ -6091,10 +6164,27 @@ "integer", "null" ] + }, + "unixSockets": { + "additionalProperties": { + "$ref": "#/definitions/NetworkUnixSocketPermission" + }, + "description": "Canonical unix socket permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] } }, "type": "object" }, + "NetworkUnixSocketPermission": { + "enum": [ + "allow", + "none" + ], + "type": "string" + }, "NonSteerableTurnKind": { "enum": [ "review", @@ -6227,7 +6317,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json index 91879645de..d1812f069f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json @@ -29,7 +29,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json index 8348b774ca..f2cf7cb3ab 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json @@ -34,7 +34,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json index e0c8304c1f..de5eca1d30 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -102,6 +102,13 @@ }, "type": "object" }, + "NetworkDomainPermission": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, "NetworkRequirements": { "properties": { "allowLocalBinding": { @@ -111,6 +118,7 @@ ] }, "allowUnixSockets": { + "description": "Legacy compatibility view derived from `unix_sockets`.", "items": { "type": "string" }, @@ -126,6 +134,7 @@ ] }, "allowedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -147,6 +156,7 @@ ] }, "deniedDomains": { + "description": "Legacy compatibility view derived from `domains`.", "items": { "type": "string" }, @@ -155,6 +165,16 @@ "null" ] }, + "domains": { + "additionalProperties": { + "$ref": "#/definitions/NetworkDomainPermission" + }, + "description": "Canonical network permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] + }, "enabled": { "type": [ "boolean", @@ -169,6 +189,13 @@ "null" ] }, + "managedAllowedDomainsOnly": { + "description": "When true, only managed allowlist entries are respected while managed network enforcement is active.", + "type": [ + "boolean", + "null" + ] + }, "socksPort": { "format": "uint16", "minimum": 0.0, @@ -176,10 +203,27 @@ "integer", "null" ] + }, + "unixSockets": { + "additionalProperties": { + "$ref": "#/definitions/NetworkUnixSocketPermission" + }, + "description": "Canonical unix socket permission map for `experimental_network`.", + "type": [ + "object", + "null" + ] } }, "type": "object" }, + "NetworkUnixSocketPermission": { + "enum": [ + "allow", + "none" + ], + "type": "string" + }, "ResidencyRequirement": { "enum": [ "us" diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json index 244156269d..23f7d3cfdc 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json @@ -29,7 +29,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json index 6646bd8c97..acaa779184 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json @@ -52,7 +52,9 @@ "plus", "pro", "team", + "self_serve_business_usage_based", "business", + "enterprise_cbp_usage_based", "enterprise", "edu", "unknown" diff --git a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json index ce6bdd4a3f..a933b71a83 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountParams.json @@ -37,6 +37,22 @@ "title": "Chatgptv2::LoginAccountParams", "type": "object" }, + { + "properties": { + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParams", + "type": "object" + }, { "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json index e2697ea44e..a800bffccd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/LoginAccountResponse.json @@ -42,6 +42,36 @@ "title": "Chatgptv2::LoginAccountResponse", "type": "object" }, + { + "properties": { + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponseType", + "type": "string" + }, + "userCode": { + "description": "One-time code the user must enter after signing in.", + "type": "string" + }, + "verificationUrl": { + "description": "URL the client should open in a browser to complete device code authorization.", + "type": "string" + } + }, + "required": [ + "loginId", + "type", + "userCode", + "verificationUrl" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponse", + "type": "object" + }, { "properties": { "type": { diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanType.ts b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts index 9f622d0f1b..ad5bcab380 100644 --- a/codex-rs/app-server-protocol/schema/typescript/PlanType.ts +++ b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type PlanType = "free" | "go" | "plus" | "pro" | "team" | "business" | "enterprise" | "edu" | "unknown"; +export type PlanType = "free" | "go" | "plus" | "pro" | "team" | "self_serve_business_usage_based" | "business" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "unknown"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts index ef668f9c1a..9fcb01f97c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountParams.ts @@ -2,7 +2,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt" } | { "type": "chatgptAuthTokens", +export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type": "chatgpt" } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens", /** * Access token (JWT) supplied by the client. * This token is used for backend API requests and email extraction. diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts index cd79f6c83f..651f171e31 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/LoginAccountResponse.ts @@ -6,4 +6,12 @@ export type LoginAccountResponse = { "type": "apiKey", } | { "type": "chatgpt", /** * URL the client should open in a browser to initiate the OAuth flow. */ -authUrl: string, } | { "type": "chatgptAuthTokens", }; +authUrl: string, } | { "type": "chatgptDeviceCode", loginId: string, +/** + * URL the client should open in a browser to complete device code authorization. + */ +verificationUrl: string, +/** + * One-time code the user must enter after signing in. + */ +userCode: string, } | { "type": "chatgptAuthTokens", }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkDomainPermission.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkDomainPermission.ts new file mode 100644 index 0000000000..2ea44392de --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkDomainPermission.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NetworkDomainPermission = "allow" | "deny"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts index 1f1653c277..5fc942bef5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts @@ -1,5 +1,32 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NetworkDomainPermission } from "./NetworkDomainPermission"; +import type { NetworkUnixSocketPermission } from "./NetworkUnixSocketPermission"; -export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array | null, deniedDomains: Array | null, allowUnixSockets: Array | null, allowLocalBinding: boolean | null, }; +export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, +/** + * Canonical network permission map for `experimental_network`. + */ +domains: { [key in string]?: NetworkDomainPermission } | null, +/** + * When true, only managed allowlist entries are respected while managed + * network enforcement is active. + */ +managedAllowedDomainsOnly: boolean | null, +/** + * Legacy compatibility view derived from `domains`. + */ +allowedDomains: Array | null, +/** + * Legacy compatibility view derived from `domains`. + */ +deniedDomains: Array | null, +/** + * Canonical unix socket permission map for `experimental_network`. + */ +unixSockets: { [key in string]?: NetworkUnixSocketPermission } | null, +/** + * Legacy compatibility view derived from `unix_sockets`. + */ +allowUnixSockets: Array | null, allowLocalBinding: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkUnixSocketPermission.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkUnixSocketPermission.ts new file mode 100644 index 0000000000..466c6e5f8f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkUnixSocketPermission.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NetworkUnixSocketPermission = "allow" | "none"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 73fe05eaeb..d0687e5f1d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -197,9 +197,11 @@ export type { ModelUpgradeInfo } from "./ModelUpgradeInfo"; export type { NetworkAccess } from "./NetworkAccess"; export type { NetworkApprovalContext } from "./NetworkApprovalContext"; export type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol"; +export type { NetworkDomainPermission } from "./NetworkDomainPermission"; export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction"; export type { NetworkRequirements } from "./NetworkRequirements"; +export type { NetworkUnixSocketPermission } from "./NetworkUnixSocketPermission"; export type { NonSteerableTurnKind } from "./NonSteerableTurnKind"; export type { OverriddenMetadata } from "./OverriddenMetadata"; export type { PatchApplyStatus } from "./PatchApplyStatus"; diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 033f605973..358bb5c2df 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -2690,7 +2690,7 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k fn generate_json_filters_experimental_fields_and_methods() -> Result<()> { let output_dir = std::env::temp_dir().join(format!("codex_schema_{}", Uuid::now_v7())); fs::create_dir(&output_dir)?; - generate_json_with_experimental(&output_dir, false)?; + generate_json_with_experimental(&output_dir, /*experimental_api*/ false)?; let thread_start_json = fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.json"))?; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index f3e56f4ffe..30061c716e 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -120,6 +120,41 @@ macro_rules! client_request_definitions { } } + /// Typed response from the server to the client. + #[derive(Serialize, Deserialize, Debug, Clone)] + #[serde(tag = "method", rename_all = "camelCase")] + pub enum ClientResponse { + $( + $(#[doc = $variant_doc])* + $(#[serde(rename = $wire)])? + $variant { + #[serde(rename = "id")] + request_id: RequestId, + response: $response, + }, + )* + } + + impl ClientResponse { + pub fn id(&self) -> &RequestId { + match self { + $(Self::$variant { request_id, .. } => request_id,)* + } + } + + pub fn method(&self) -> String { + serde_json::to_value(self) + .ok() + .and_then(|value| { + value + .get("method") + .and_then(serde_json::Value::as_str) + .map(str::to_owned) + }) + .unwrap_or_else(|| "".to_string()) + } + } + impl crate::experimental_api::ExperimentalApi for ClientRequest { fn experimental_reason(&self) -> Option<&'static str> { match self { @@ -1265,6 +1300,84 @@ mod tests { Ok(()) } + #[test] + fn serialize_client_response() -> Result<()> { + let response = ClientResponse::ThreadStart { + request_id: RequestId::Integer(7), + response: v2::ThreadStartResponse { + thread: v2::Thread { + id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), + preview: "first prompt".to_string(), + ephemeral: true, + model_provider: "openai".to_string(), + created_at: 1, + updated_at: 2, + status: v2::ThreadStatus::Idle, + path: None, + cwd: PathBuf::from("/tmp"), + cli_version: "0.0.0".to_string(), + source: v2::SessionSource::Exec, + agent_nickname: None, + agent_role: None, + git_info: None, + name: None, + turns: Vec::new(), + }, + model: "gpt-5".to_string(), + model_provider: "openai".to_string(), + service_tier: None, + cwd: PathBuf::from("/tmp"), + approval_policy: v2::AskForApproval::OnFailure, + approvals_reviewer: v2::ApprovalsReviewer::User, + sandbox: v2::SandboxPolicy::DangerFullAccess, + reasoning_effort: None, + }, + }; + + assert_eq!(response.id(), &RequestId::Integer(7)); + assert_eq!(response.method(), "thread/start"); + assert_eq!( + json!({ + "method": "thread/start", + "id": 7, + "response": { + "thread": { + "id": "67e55044-10b1-426f-9247-bb680e5fe0c8", + "preview": "first prompt", + "ephemeral": true, + "modelProvider": "openai", + "createdAt": 1, + "updatedAt": 2, + "status": { + "type": "idle" + }, + "path": null, + "cwd": "/tmp", + "cliVersion": "0.0.0", + "source": "exec", + "agentNickname": null, + "agentRole": null, + "gitInfo": null, + "name": null, + "turns": [] + }, + "model": "gpt-5", + "modelProvider": "openai", + "serviceTier": null, + "cwd": "/tmp", + "approvalPolicy": "on-failure", + "approvalsReviewer": "user", + "sandbox": { + "type": "dangerFullAccess" + }, + "reasoningEffort": null + } + }), + serde_json::to_value(&response)?, + ); + Ok(()) + } + #[test] fn serialize_config_requirements_read() -> Result<()> { let request = ClientRequest::ConfigRequirementsRead { @@ -1322,16 +1435,35 @@ mod tests { Ok(()) } + #[test] + fn serialize_account_login_chatgpt_device_code() -> Result<()> { + let request = ClientRequest::LoginAccount { + request_id: RequestId::Integer(4), + params: v2::LoginAccountParams::ChatgptDeviceCode, + }; + assert_eq!( + json!({ + "method": "account/login/start", + "id": 4, + "params": { + "type": "chatgptDeviceCode" + } + }), + serde_json::to_value(&request)?, + ); + Ok(()) + } + #[test] fn serialize_account_logout() -> Result<()> { let request = ClientRequest::LogoutAccount { - request_id: RequestId::Integer(4), + request_id: RequestId::Integer(5), params: None, }; assert_eq!( json!({ "method": "account/logout", - "id": 4, + "id": 5, }), serde_json::to_value(&request)?, ); @@ -1341,7 +1473,7 @@ mod tests { #[test] fn serialize_account_login_chatgpt_auth_tokens() -> Result<()> { let request = ClientRequest::LoginAccount { - request_id: RequestId::Integer(5), + request_id: RequestId::Integer(6), params: v2::LoginAccountParams::ChatgptAuthTokens { access_token: "access-token".to_string(), chatgpt_account_id: "org-123".to_string(), @@ -1351,7 +1483,7 @@ mod tests { assert_eq!( json!({ "method": "account/login/start", - "id": 5, + "id": 6, "params": { "type": "chatgptAuthTokens", "accessToken": "access-token", diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 2211e7aa15..9373c852d8 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -866,12 +866,38 @@ pub struct NetworkRequirements { pub allow_upstream_proxy: Option, pub dangerously_allow_non_loopback_proxy: Option, pub dangerously_allow_all_unix_sockets: Option, + /// Canonical network permission map for `experimental_network`. + pub domains: Option>, + /// When true, only managed allowlist entries are respected while managed + /// network enforcement is active. + pub managed_allowed_domains_only: Option, + /// Legacy compatibility view derived from `domains`. pub allowed_domains: Option>, + /// Legacy compatibility view derived from `domains`. pub denied_domains: Option>, + /// Canonical unix socket permission map for `experimental_network`. + pub unix_sockets: Option>, + /// Legacy compatibility view derived from `unix_sockets`. pub allow_unix_sockets: Option>, pub allow_local_binding: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[ts(export_to = "v2/")] +pub enum NetworkDomainPermission { + Allow, + Deny, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[ts(export_to = "v2/")] +pub enum NetworkUnixSocketPermission { + Allow, + None, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -1563,6 +1589,9 @@ pub enum LoginAccountParams { #[serde(rename = "chatgpt")] #[ts(rename = "chatgpt")] Chatgpt, + #[serde(rename = "chatgptDeviceCode")] + #[ts(rename = "chatgptDeviceCode")] + ChatgptDeviceCode, /// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. /// The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have. #[experimental("account/login/start.chatgptAuthTokens")] @@ -1600,6 +1629,17 @@ pub enum LoginAccountResponse { /// URL the client should open in a browser to initiate the OAuth flow. auth_url: String, }, + #[serde(rename = "chatgptDeviceCode", rename_all = "camelCase")] + #[ts(rename = "chatgptDeviceCode", rename_all = "camelCase")] + ChatgptDeviceCode { + // Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types. + // Convert to/from UUIDs at the application layer as needed. + login_id: String, + /// URL the client should open in a browser to complete device code authorization. + verification_url: String, + /// One-time code the user must enter after signing in. + user_code: String, + }, #[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")] #[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")] ChatgptAuthTokens {}, @@ -7487,6 +7527,94 @@ mod tests { ); } + #[test] + fn network_requirements_deserializes_legacy_fields() { + let requirements: NetworkRequirements = serde_json::from_value(json!({ + "allowedDomains": ["api.openai.com"], + "deniedDomains": ["blocked.example.com"], + "allowUnixSockets": ["/tmp/proxy.sock"] + })) + .expect("legacy network requirements should deserialize"); + + assert_eq!( + requirements, + NetworkRequirements { + enabled: None, + http_port: None, + socks_port: None, + allow_upstream_proxy: None, + dangerously_allow_non_loopback_proxy: None, + dangerously_allow_all_unix_sockets: None, + domains: None, + managed_allowed_domains_only: None, + allowed_domains: Some(vec!["api.openai.com".to_string()]), + denied_domains: Some(vec!["blocked.example.com".to_string()]), + unix_sockets: None, + allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), + allow_local_binding: None, + } + ); + } + + #[test] + fn network_requirements_serializes_canonical_and_legacy_fields() { + let requirements = NetworkRequirements { + enabled: Some(true), + http_port: Some(8080), + socks_port: Some(1080), + allow_upstream_proxy: Some(false), + dangerously_allow_non_loopback_proxy: Some(false), + dangerously_allow_all_unix_sockets: Some(true), + domains: Some(BTreeMap::from([ + ("api.openai.com".to_string(), NetworkDomainPermission::Allow), + ( + "blocked.example.com".to_string(), + NetworkDomainPermission::Deny, + ), + ])), + managed_allowed_domains_only: Some(true), + allowed_domains: Some(vec!["api.openai.com".to_string()]), + denied_domains: Some(vec!["blocked.example.com".to_string()]), + unix_sockets: Some(BTreeMap::from([ + ( + "/tmp/proxy.sock".to_string(), + NetworkUnixSocketPermission::Allow, + ), + ( + "/tmp/ignored.sock".to_string(), + NetworkUnixSocketPermission::None, + ), + ])), + allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), + allow_local_binding: Some(true), + }; + + assert_eq!( + serde_json::to_value(requirements).expect("network requirements should serialize"), + json!({ + "enabled": true, + "httpPort": 8080, + "socksPort": 1080, + "allowUpstreamProxy": false, + "dangerouslyAllowNonLoopbackProxy": false, + "dangerouslyAllowAllUnixSockets": true, + "domains": { + "api.openai.com": "allow", + "blocked.example.com": "deny" + }, + "managedAllowedDomainsOnly": true, + "allowedDomains": ["api.openai.com"], + "deniedDomains": ["blocked.example.com"], + "unixSockets": { + "/tmp/ignored.sock": "none", + "/tmp/proxy.sock": "allow" + }, + "allowUnixSockets": ["/tmp/proxy.sock"], + "allowLocalBinding": true + }) + ); + } + #[test] fn core_turn_item_into_thread_item_converts_supported_variants() { let user_item = TurnItem::UserMessage(UserMessageItem { diff --git a/codex-rs/app-server-protocol/tests/schema_fixtures.rs b/codex-rs/app-server-protocol/tests/schema_fixtures.rs index fbe1fd4408..20823466e7 100644 --- a/codex-rs/app-server-protocol/tests/schema_fixtures.rs +++ b/codex-rs/app-server-protocol/tests/schema_fixtures.rs @@ -23,7 +23,7 @@ fn typescript_schema_fixtures_match_generated() -> Result<()> { #[test] fn json_schema_fixtures_match_generated() -> Result<()> { assert_schema_fixtures_match_generated("json", |output_dir| { - generate_json_with_experimental(output_dir, false) + generate_json_with_experimental(output_dir, /*experimental_api*/ false) }) } diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs index 82954d3cb6..dd548012a0 100644 --- a/codex-rs/app-server-test-client/src/lib.rs +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -225,7 +225,11 @@ enum CliCommand { abort_on: Option, }, /// Trigger the ChatGPT login flow and wait for completion. - TestLogin, + TestLogin { + /// Use the device-code login flow instead of the browser callback flow. + #[arg(long, default_value_t = false)] + device_code: bool, + }, /// Fetch the current account rate limits from the Codex app-server. GetAccountRateLimits, /// List the available models from the Codex app-server. @@ -372,10 +376,10 @@ pub async fn run() -> Result<()> { ) .await } - CliCommand::TestLogin => { + CliCommand::TestLogin { device_code } => { ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; let endpoint = resolve_endpoint(codex_bin, url)?; - test_login(&endpoint, &config_overrides).await + test_login(&endpoint, &config_overrides, device_code).await } CliCommand::GetAccountRateLimits => { ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; @@ -1028,17 +1032,38 @@ async fn send_follow_up_v2( .await } -async fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> { +async fn test_login( + endpoint: &Endpoint, + config_overrides: &[String], + device_code: bool, +) -> Result<()> { with_client("test-login", endpoint, config_overrides, |client| { let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let login_response = client.login_account_chatgpt()?; - println!("< account/login/start response: {login_response:?}"); - let LoginAccountResponse::Chatgpt { login_id, auth_url } = login_response else { - bail!("expected chatgpt login response"); + let login_response = if device_code { + client.login_account_chatgpt_device_code()? + } else { + client.login_account_chatgpt()? + }; + println!("< account/login/start response: {login_response:?}"); + let login_id = match login_response { + LoginAccountResponse::Chatgpt { login_id, auth_url } => { + println!("Open the following URL in your browser to continue:\n{auth_url}"); + login_id + } + LoginAccountResponse::ChatgptDeviceCode { + login_id, + verification_url, + user_code, + } => { + println!( + "Open the following URL and enter the code to continue:\n{verification_url}\n\nCode: {user_code}" + ); + login_id + } + _ => bail!("expected chatgpt login response"), }; - println!("Open the following URL in your browser to continue:\n{auth_url}"); let completion = client.wait_for_account_login_completion(&login_id)?; println!("< account/login/completed notification: {completion:?}"); @@ -1590,6 +1615,16 @@ impl CodexClient { self.send_request(request, request_id, "account/login/start") } + fn login_account_chatgpt_device_code(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::LoginAccount { + request_id: request_id.clone(), + params: codex_app_server_protocol::LoginAccountParams::ChatgptDeviceCode, + }; + + self.send_request(request, request_id, "account/login/start") + } + fn get_account_rate_limits(&mut self) -> Result { let request_id = self.request_id(); let request = ClientRequest::GetAccountRateLimits { diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 4fed3052c8..46d8963bc6 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -195,7 +195,7 @@ Example with notification opt-out: - `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home). - `config/value/write` — write a single config key/value to the user's config.toml on disk. - `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads. -- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints. +- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly`. ### Example: Start or resume a thread @@ -1310,14 +1310,14 @@ The JSON-RPC auth/account surface exposes request/response methods plus server-i Codex supports these authentication modes. The current mode is surfaced in `account/updated` (`authMode`), which also includes the current ChatGPT `planType` when available, and can be inferred from `account/read`. - **API key (`apiKey`)**: Caller supplies an OpenAI API key via `account/login/start` with `type: "apiKey"`. The API key is saved and used for API requests. -- **ChatGPT managed (`chatgpt`)** (recommended): Codex owns the ChatGPT OAuth flow and refresh tokens. Start via `account/login/start` with `type: "chatgpt"`; Codex persists tokens to disk and refreshes them automatically. +- **ChatGPT managed (`chatgpt`)** (recommended): Codex owns the ChatGPT OAuth flow and refresh tokens. Start via `account/login/start` with `type: "chatgpt"` for the browser flow or `type: "chatgptDeviceCode"` for device code; Codex persists tokens to disk and refreshes them automatically. ### API Overview - `account/read` — fetch current account info; optionally refresh tokens. -- `account/login/start` — begin login (`apiKey`, `chatgpt`). +- `account/login/start` — begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`). - `account/login/completed` (notify) — emitted when a login attempt finishes (success or error). -- `account/login/cancel` — cancel a pending ChatGPT login by `loginId`. +- `account/login/cancel` — cancel a pending managed ChatGPT login by `loginId`. - `account/logout` — sign out; triggers `account/updated`. - `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, or `null`) and includes the current ChatGPT `planType` when available. - `account/rateLimits/read` — fetch ChatGPT rate limits; updates arrive via `account/rateLimits/updated` (notify). @@ -1381,26 +1381,40 @@ Field notes: { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus" } } ``` -### 4) Cancel a ChatGPT login +### 4) Log in with ChatGPT (device code flow) + +1. Start: + ```json + { "method": "account/login/start", "id": 4, "params": { "type": "chatgptDeviceCode" } } + { "id": 4, "result": { "type": "chatgptDeviceCode", "loginId": "", "verificationUrl": "https://auth.openai.com/codex/device", "userCode": "ABCD-1234" } } + ``` +2. Show `verificationUrl` and `userCode` to the user; the frontend owns the UX. +3. Wait for notifications: + ```json + { "method": "account/login/completed", "params": { "loginId": "", "success": true, "error": null } } + { "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus" } } + ``` + +### 5) Cancel a ChatGPT login ```json -{ "method": "account/login/cancel", "id": 4, "params": { "loginId": "" } } +{ "method": "account/login/cancel", "id": 5, "params": { "loginId": "" } } { "method": "account/login/completed", "params": { "loginId": "", "success": false, "error": "…" } } ``` -### 5) Logout +### 6) Logout ```json -{ "method": "account/logout", "id": 5 } -{ "id": 5, "result": {} } +{ "method": "account/logout", "id": 6 } +{ "id": 6, "result": {} } { "method": "account/updated", "params": { "authMode": null, "planType": null } } ``` -### 6) Rate limits (ChatGPT) +### 7) Rate limits (ChatGPT) ```json -{ "method": "account/rateLimits/read", "id": 6 } -{ "id": 6, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null } } } +{ "method": "account/rateLimits/read", "id": 7 } +{ "id": 7, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null } } } { "method": "account/rateLimits/updated", "params": { "rateLimits": { … } } } ``` diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 875eb2b87f..2798dcb92e 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -249,6 +249,8 @@ use codex_git_utils::git_diff_to_remote; use codex_login::ServerOptions as LoginServerOptions; use codex_login::ShutdownHandle; use codex_login::auth::login_with_chatgpt_auth_tokens; +use codex_login::complete_device_code_login; +use codex_login::request_device_code; use codex_login::run_login_server; use codex_protocol::ThreadId; use codex_protocol::config_types::CollaborationMode; @@ -339,12 +341,39 @@ struct ThreadListFilters { search_term: Option, } -// Duration before a ChatGPT login attempt is abandoned. +// Duration before a browser ChatGPT login attempt is abandoned. const LOGIN_CHATGPT_TIMEOUT: Duration = Duration::from_secs(10 * 60); +const LOGIN_ISSUER_OVERRIDE_ENV_VAR: &str = "CODEX_APP_SERVER_LOGIN_ISSUER"; const APP_LIST_LOAD_TIMEOUT: Duration = Duration::from_secs(90); -struct ActiveLogin { - shutdown_handle: ShutdownHandle, - login_id: Uuid, + +enum ActiveLogin { + Browser { + shutdown_handle: ShutdownHandle, + login_id: Uuid, + }, + DeviceCode { + cancel: CancellationToken, + login_id: Uuid, + }, +} + +impl ActiveLogin { + fn login_id(&self) -> Uuid { + match self { + ActiveLogin::Browser { login_id, .. } | ActiveLogin::DeviceCode { login_id, .. } => { + *login_id + } + } + } + + fn cancel(&self) { + match self { + ActiveLogin::Browser { + shutdown_handle, .. + } => shutdown_handle.shutdown(), + ActiveLogin::DeviceCode { cancel, .. } => cancel.cancel(), + } + } } #[derive(Clone, Copy, Debug)] @@ -365,7 +394,7 @@ enum ThreadShutdownResult { impl Drop for ActiveLogin { fn drop(&mut self) { - self.shutdown_handle.shutdown(); + self.cancel(); } } @@ -954,6 +983,9 @@ impl CodexMessageProcessor { LoginAccountParams::Chatgpt => { self.login_chatgpt_v2(request_id).await; } + LoginAccountParams::ChatgptDeviceCode => { + self.login_chatgpt_device_code_v2(request_id).await; + } LoginAccountParams::ChatgptAuthTokens { access_token, chatgpt_account_id, @@ -1074,7 +1106,7 @@ impl CodexMessageProcessor { }); } - Ok(LoginServerOptions { + let mut opts = LoginServerOptions { open_browser: false, ..LoginServerOptions::new( config.codex_home.clone(), @@ -1082,7 +1114,32 @@ impl CodexMessageProcessor { config.forced_chatgpt_workspace_id.clone(), config.cli_auth_credentials_store_mode, ) - }) + }; + #[cfg(debug_assertions)] + if let Ok(issuer) = std::env::var(LOGIN_ISSUER_OVERRIDE_ENV_VAR) + && !issuer.trim().is_empty() + { + opts.issuer = issuer; + } + + Ok(opts) + } + + fn login_chatgpt_device_code_start_error(err: IoError) -> JSONRPCErrorError { + let is_not_found = err.kind() == std::io::ErrorKind::NotFound; + JSONRPCErrorError { + code: if is_not_found { + INVALID_REQUEST_ERROR_CODE + } else { + INTERNAL_ERROR_CODE + }, + message: if is_not_found { + err.to_string() + } else { + format!("failed to request device code: {err}") + }, + data: None, + } } async fn login_chatgpt_v2(&mut self, request_id: ConnectionRequestId) { @@ -1098,7 +1155,7 @@ impl CodexMessageProcessor { if let Some(existing) = guard.take() { drop(existing); } - *guard = Some(ActiveLogin { + *guard = Some(ActiveLogin::Browser { shutdown_handle: shutdown_handle.clone(), login_id, }); @@ -1168,7 +1225,7 @@ impl CodexMessageProcessor { // Clear the active login if it matches this attempt. It may have been replaced or cancelled. let mut guard = active_login.lock().await; - if guard.as_ref().map(|l| l.login_id) == Some(login_id) { + if guard.as_ref().map(ActiveLogin::login_id) == Some(login_id) { *guard = None; } }); @@ -1194,12 +1251,114 @@ impl CodexMessageProcessor { } } + async fn login_chatgpt_device_code_v2(&mut self, request_id: ConnectionRequestId) { + match self.login_chatgpt_common().await { + Ok(opts) => match request_device_code(&opts).await { + Ok(device_code) => { + let login_id = Uuid::new_v4(); + let cancel = CancellationToken::new(); + + { + let mut guard = self.active_login.lock().await; + if let Some(existing) = guard.take() { + drop(existing); + } + *guard = Some(ActiveLogin::DeviceCode { + cancel: cancel.clone(), + login_id, + }); + } + + let verification_url = device_code.verification_url.clone(); + let user_code = device_code.user_code.clone(); + let response = + codex_app_server_protocol::LoginAccountResponse::ChatgptDeviceCode { + login_id: login_id.to_string(), + verification_url, + user_code, + }; + self.outgoing.send_response(request_id, response).await; + + let outgoing_clone = self.outgoing.clone(); + let active_login = self.active_login.clone(); + let auth_manager = self.auth_manager.clone(); + let cloud_requirements = self.cloud_requirements.clone(); + let chatgpt_base_url = self.config.chatgpt_base_url.clone(); + let codex_home = self.config.codex_home.clone(); + let cli_overrides = self.current_cli_overrides(); + tokio::spawn(async move { + let (success, error_msg) = tokio::select! { + _ = cancel.cancelled() => { + (false, Some("Login was not completed".to_string())) + } + r = complete_device_code_login(opts, device_code) => { + match r { + Ok(()) => (true, None), + Err(err) => (false, Some(err.to_string())), + } + } + }; + + let payload_v2 = AccountLoginCompletedNotification { + login_id: Some(login_id.to_string()), + success, + error: error_msg, + }; + outgoing_clone + .send_server_notification(ServerNotification::AccountLoginCompleted( + payload_v2, + )) + .await; + + if success { + auth_manager.reload(); + replace_cloud_requirements_loader( + cloud_requirements.as_ref(), + auth_manager.clone(), + chatgpt_base_url, + codex_home, + ); + sync_default_client_residency_requirement( + &cli_overrides, + cloud_requirements.as_ref(), + ) + .await; + + let auth = auth_manager.auth_cached(); + let payload_v2 = AccountUpdatedNotification { + auth_mode: auth.as_ref().map(CodexAuth::api_auth_mode), + plan_type: auth.as_ref().and_then(CodexAuth::account_plan_type), + }; + outgoing_clone + .send_server_notification(ServerNotification::AccountUpdated( + payload_v2, + )) + .await; + } + + let mut guard = active_login.lock().await; + if guard.as_ref().map(ActiveLogin::login_id) == Some(login_id) { + *guard = None; + } + }); + } + Err(err) => { + let error = Self::login_chatgpt_device_code_start_error(err); + self.outgoing.send_error(request_id, error).await; + } + }, + Err(err) => { + self.outgoing.send_error(request_id, err).await; + } + } + } + async fn cancel_login_chatgpt_common( &mut self, login_id: Uuid, ) -> std::result::Result<(), CancelLoginError> { let mut guard = self.active_login.lock().await; - if guard.as_ref().map(|l| l.login_id) == Some(login_id) { + if guard.as_ref().map(ActiveLogin::login_id) == Some(login_id) { if let Some(active) = guard.take() { drop(active); } @@ -8588,7 +8747,7 @@ mod tests { fn config_load_error_marks_non_auth_cloud_requirements_failures_without_relogin() { let err = std::io::Error::other(CloudRequirementsLoadError::new( CloudRequirementsLoadErrorCode::RequestFailed, - None, + /*status_code*/ None, "failed to load your workspace-managed config", )); @@ -8802,7 +8961,8 @@ mod tests { fn merge_persisted_resume_metadata_skips_missing_values() -> Result<()> { let mut request_overrides = None; let mut typesafe_overrides = ConfigOverrides::default(); - let persisted_metadata = test_thread_metadata(None, None)?; + let persisted_metadata = + test_thread_metadata(/*model*/ None, /*reasoning_effort*/ None)?; merge_persisted_resume_metadata( &mut request_overrides, @@ -8854,7 +9014,7 @@ mod tests { path.clone(), &head, &session_meta, - None, + /*git*/ None, "test-provider", timestamp.clone(), ) @@ -9064,9 +9224,9 @@ mod tests { source, Some("atlas".to_string()), Some("explorer".to_string()), - None, - None, - None, + /*git_sha*/ None, + /*git_branch*/ None, + /*git_origin_url*/ None, ); let thread = summary_to_thread(summary); @@ -9085,7 +9245,9 @@ mod tests { manager.connection_initialized(connection).await; manager - .try_ensure_connection_subscribed(thread_id, connection, false) + .try_ensure_connection_subscribed( + thread_id, connection, /*experimental_raw_events*/ false, + ) .await .expect("connection should be live"); { @@ -9129,11 +9291,19 @@ mod tests { manager.connection_initialized(connection_a).await; manager.connection_initialized(connection_b).await; manager - .try_ensure_connection_subscribed(thread_id, connection_a, false) + .try_ensure_connection_subscribed( + thread_id, + connection_a, + /*experimental_raw_events*/ false, + ) .await .expect("connection_a should be live"); manager - .try_ensure_connection_subscribed(thread_id, connection_b, false) + .try_ensure_connection_subscribed( + thread_id, + connection_b, + /*experimental_raw_events*/ false, + ) .await .expect("connection_b should be live"); { @@ -9166,7 +9336,9 @@ mod tests { assert!( manager - .try_ensure_connection_subscribed(thread_id, connection, false) + .try_ensure_connection_subscribed( + thread_id, connection, /*experimental_raw_events*/ false + ) .await .is_none() ); diff --git a/codex-rs/app-server/src/codex_message_processor/plugin_app_helpers.rs b/codex-rs/app-server/src/codex_message_processor/plugin_app_helpers.rs index faeca5b2e0..f2ba96d43a 100644 --- a/codex-rs/app-server/src/codex_message_processor/plugin_app_helpers.rs +++ b/codex-rs/app-server/src/codex_message_processor/plugin_app_helpers.rs @@ -137,7 +137,7 @@ mod tests { &all_connectors, &[], &[AppConnectorId("alpha".to_string())], - false, + /*codex_apps_ready*/ false, ), Vec::new() ); diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 7f6acc54d1..d138d2f5eb 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -12,7 +12,9 @@ use codex_app_server_protocol::ConfigWriteResponse; use codex_app_server_protocol::ExperimentalFeatureEnablementSetParams; use codex_app_server_protocol::ExperimentalFeatureEnablementSetResponse; use codex_app_server_protocol::JSONRPCErrorError; +use codex_app_server_protocol::NetworkDomainPermission; use codex_app_server_protocol::NetworkRequirements; +use codex_app_server_protocol::NetworkUnixSocketPermission; use codex_app_server_protocol::SandboxMode; use codex_core::AnalyticsEventsClient; use codex_core::ThreadManager; @@ -410,6 +412,20 @@ fn map_residency_requirement_to_api( fn map_network_requirements_to_api( network: codex_core::config_loader::NetworkRequirementsToml, ) -> NetworkRequirements { + let allowed_domains = network + .domains + .as_ref() + .and_then(codex_core::config_loader::NetworkDomainPermissionsToml::allowed_domains); + let denied_domains = network + .domains + .as_ref() + .and_then(codex_core::config_loader::NetworkDomainPermissionsToml::denied_domains); + let allow_unix_sockets = network + .unix_sockets + .as_ref() + .map(codex_core::config_loader::NetworkUnixSocketPermissionsToml::allow_unix_sockets) + .filter(|entries| !entries.is_empty()); + NetworkRequirements { enabled: network.enabled, http_port: network.http_port, @@ -417,13 +433,58 @@ fn map_network_requirements_to_api( allow_upstream_proxy: network.allow_upstream_proxy, dangerously_allow_non_loopback_proxy: network.dangerously_allow_non_loopback_proxy, dangerously_allow_all_unix_sockets: network.dangerously_allow_all_unix_sockets, - allowed_domains: network.allowed_domains, - denied_domains: network.denied_domains, - allow_unix_sockets: network.allow_unix_sockets, + domains: network.domains.map(|domains| { + domains + .entries + .into_iter() + .map(|(pattern, permission)| { + (pattern, map_network_domain_permission_to_api(permission)) + }) + .collect() + }), + managed_allowed_domains_only: network.managed_allowed_domains_only, + allowed_domains, + denied_domains, + unix_sockets: network.unix_sockets.map(|unix_sockets| { + unix_sockets + .entries + .into_iter() + .map(|(path, permission)| { + (path, map_network_unix_socket_permission_to_api(permission)) + }) + .collect() + }), + allow_unix_sockets, allow_local_binding: network.allow_local_binding, } } +fn map_network_domain_permission_to_api( + permission: codex_core::config_loader::NetworkDomainPermissionToml, +) -> NetworkDomainPermission { + match permission { + codex_core::config_loader::NetworkDomainPermissionToml::Allow => { + NetworkDomainPermission::Allow + } + codex_core::config_loader::NetworkDomainPermissionToml::Deny => { + NetworkDomainPermission::Deny + } + } +} + +fn map_network_unix_socket_permission_to_api( + permission: codex_core::config_loader::NetworkUnixSocketPermissionToml, +) -> NetworkUnixSocketPermission { + match permission { + codex_core::config_loader::NetworkUnixSocketPermissionToml::Allow => { + NetworkUnixSocketPermission::Allow + } + codex_core::config_loader::NetworkUnixSocketPermissionToml::None => { + NetworkUnixSocketPermission::None + } + } +} + fn map_error(err: ConfigServiceError) -> JSONRPCErrorError { if let Some(code) = err.write_error_code() { return config_write_error(code, err.to_string()); @@ -452,7 +513,11 @@ mod tests { use codex_core::AnalyticsEventsClient; use codex_core::AuthManager; use codex_core::CodexAuth; + use codex_core::config_loader::NetworkDomainPermissionToml as CoreNetworkDomainPermissionToml; + use codex_core::config_loader::NetworkDomainPermissionsToml as CoreNetworkDomainPermissionsToml; use codex_core::config_loader::NetworkRequirementsToml as CoreNetworkRequirementsToml; + use codex_core::config_loader::NetworkUnixSocketPermissionToml as CoreNetworkUnixSocketPermissionToml; + use codex_core::config_loader::NetworkUnixSocketPermissionsToml as CoreNetworkUnixSocketPermissionsToml; use codex_features::Feature; use codex_protocol::protocol::AskForApproval as CoreAskForApproval; use pretty_assertions::assert_eq; @@ -505,10 +570,25 @@ mod tests { allow_upstream_proxy: Some(false), dangerously_allow_non_loopback_proxy: Some(false), dangerously_allow_all_unix_sockets: Some(true), - allowed_domains: Some(vec!["api.openai.com".to_string()]), + domains: Some(CoreNetworkDomainPermissionsToml { + entries: std::collections::BTreeMap::from([ + ( + "api.openai.com".to_string(), + CoreNetworkDomainPermissionToml::Allow, + ), + ( + "example.com".to_string(), + CoreNetworkDomainPermissionToml::Deny, + ), + ]), + }), managed_allowed_domains_only: Some(false), - denied_domains: Some(vec!["example.com".to_string()]), - allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), + unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml { + entries: std::collections::BTreeMap::from([( + "/tmp/proxy.sock".to_string(), + CoreNetworkUnixSocketPermissionToml::Allow, + )]), + }), allow_local_binding: Some(true), }), }; @@ -550,14 +630,79 @@ mod tests { allow_upstream_proxy: Some(false), dangerously_allow_non_loopback_proxy: Some(false), dangerously_allow_all_unix_sockets: Some(true), + domains: Some(std::collections::BTreeMap::from([ + ("api.openai.com".to_string(), NetworkDomainPermission::Allow,), + ("example.com".to_string(), NetworkDomainPermission::Deny), + ])), + managed_allowed_domains_only: Some(false), allowed_domains: Some(vec!["api.openai.com".to_string()]), denied_domains: Some(vec!["example.com".to_string()]), + unix_sockets: Some(std::collections::BTreeMap::from([( + "/tmp/proxy.sock".to_string(), + NetworkUnixSocketPermission::Allow, + )])), allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), allow_local_binding: Some(true), }), ); } + #[test] + fn map_requirements_toml_to_api_omits_unix_socket_none_entries_from_legacy_network_fields() { + let requirements = ConfigRequirementsToml { + allowed_approval_policies: None, + allowed_sandbox_modes: None, + allowed_web_search_modes: None, + guardian_developer_instructions: None, + feature_requirements: None, + mcp_servers: None, + apps: None, + rules: None, + enforce_residency: None, + network: Some(CoreNetworkRequirementsToml { + enabled: None, + http_port: None, + socks_port: None, + allow_upstream_proxy: None, + dangerously_allow_non_loopback_proxy: None, + dangerously_allow_all_unix_sockets: None, + domains: None, + managed_allowed_domains_only: None, + unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml { + entries: std::collections::BTreeMap::from([( + "/tmp/ignored.sock".to_string(), + CoreNetworkUnixSocketPermissionToml::None, + )]), + }), + allow_local_binding: None, + }), + }; + + let mapped = map_requirements_toml_to_api(requirements); + + assert_eq!( + mapped.network, + Some(NetworkRequirements { + enabled: None, + http_port: None, + socks_port: None, + allow_upstream_proxy: None, + dangerously_allow_non_loopback_proxy: None, + dangerously_allow_all_unix_sockets: None, + domains: None, + managed_allowed_domains_only: None, + allowed_domains: None, + denied_domains: None, + unix_sockets: Some(std::collections::BTreeMap::from([( + "/tmp/ignored.sock".to_string(), + NetworkUnixSocketPermission::None, + )])), + allow_unix_sockets: None, + allow_local_binding: None, + }), + ); + } + #[test] fn map_requirements_toml_to_api_normalizes_allowed_web_search_modes() { let requirements = ConfigRequirementsToml { diff --git a/codex-rs/app-server/src/filters.rs b/codex-rs/app-server/src/filters.rs index 6d2b90dbae..20608d93cd 100644 --- a/codex-rs/app-server/src/filters.rs +++ b/codex-rs/app-server/src/filters.rs @@ -90,7 +90,7 @@ mod tests { #[test] fn compute_source_filters_defaults_to_interactive_sources() { - let (allowed_sources, filter) = compute_source_filters(None); + let (allowed_sources, filter) = compute_source_filters(/*source_kinds*/ None); assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec()); assert_eq!(filter, None); diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index 248ff85bad..4ad2c0ab1f 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -795,7 +795,8 @@ mod tests { #[tokio::test] async fn in_process_start_clamps_zero_channel_capacity() { - let client = start_test_client_with_capacity(SessionSource::Cli, 0).await; + let client = + start_test_client_with_capacity(SessionSource::Cli, /*channel_capacity*/ 0).await; let response = loop { match client .request(ClientRequest::ConfigRequirementsRead { diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 31c0a9fe73..bb80fdf7b4 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -888,7 +888,10 @@ mod tests { #[test] fn log_format_from_env_value_defaults_for_non_json_values() { - assert_eq!(LogFormat::from_env_value(None), LogFormat::Default); + assert_eq!( + LogFormat::from_env_value(/*value*/ None), + LogFormat::Default + ); assert_eq!(LogFormat::from_env_value(Some("")), LogFormat::Default); assert_eq!(LogFormat::from_env_value(Some("text")), LogFormat::Default); assert_eq!(LogFormat::from_env_value(Some("jsonl")), LogFormat::Default); diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 97d519fb79..8ac2cd7bb1 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -61,7 +61,6 @@ use codex_core::ThreadManager; use codex_core::config::Config; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::LoaderOverrides; -use codex_core::default_client::DEFAULT_ORIGINATOR; use codex_core::default_client::SetOriginatorError; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; @@ -88,7 +87,6 @@ use toml::Value as TomlValue; use tracing::Instrument; const EXTERNAL_AUTH_REFRESH_TIMEOUT: Duration = Duration::from_secs(10); -const TUI_APP_SERVER_CLIENT_NAME: &str = "codex-tui"; #[derive(Clone)] struct ExternalAuthRefreshBridge { @@ -563,13 +561,7 @@ impl MessageProcessor { } = params.client_info; session.app_server_client_name = Some(name.clone()); session.client_version = Some(version.clone()); - let originator = if name == TUI_APP_SERVER_CLIENT_NAME { - // TODO: Remove this temporary workaround once app-server clients no longer - // need to retain the legacy TUI `codex_cli_rs` originator behavior. - DEFAULT_ORIGINATOR.to_string() - } else { - name.clone() - }; + let originator = name.clone(); if let Err(error) = set_default_originator(originator) { match error { SetOriginatorError::InvalidHeaderValue => { diff --git a/codex-rs/app-server/src/message_processor/tracing_tests.rs b/codex-rs/app-server/src/message_processor/tracing_tests.rs index 4665653ef1..d777cd374c 100644 --- a/codex-rs/app-server/src/message_processor/tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor/tracing_tests.rs @@ -148,7 +148,7 @@ impl TracingHarness { }), }, }, - None, + /*trace*/ None, ) .await; assert!(harness.session.initialized); @@ -214,7 +214,7 @@ async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result .. } = RemoteTrace::new("00000000000000000000000000000011", "0000000000000022"); - let _: ThreadStartResponse = harness.start_thread(20_002, None).await; + let _: ThreadStartResponse = harness + .start_thread(/*request_id*/ 20_002, /*trace*/ None) + .await; let untraced_spans = wait_for_exported_spans(harness.tracing, |spans| { spans.iter().any(|span| { span.span_kind == SpanKind::Server @@ -546,10 +548,16 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> .span_context .trace_id(), ); - assert_has_internal_descendant_at_min_depth(&untraced_spans, untraced_server_span, 1); + assert_has_internal_descendant_at_min_depth( + &untraced_spans, + untraced_server_span, + /*min_depth*/ 1, + ); let baseline_len = untraced_spans.len(); - let _: ThreadStartResponse = harness.start_thread(20_003, Some(remote_trace)).await; + let _: ThreadStartResponse = harness + .start_thread(/*request_id*/ 20_003, Some(remote_trace)) + .await; let spans = wait_for_new_exported_spans(harness.tracing, baseline_len, |spans| { spans.iter().any(|span| { span.span_kind == SpanKind::Server @@ -569,8 +577,8 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> assert!(server_request_span.parent_span_is_remote); assert_eq!(server_request_span.span_context.trace_id(), remote_trace_id); assert_ne!(server_request_span.span_context.span_id(), SpanId::INVALID); - assert_has_internal_descendant_at_min_depth(&spans, server_request_span, 1); - assert_has_internal_descendant_at_min_depth(&spans, server_request_span, 2); + assert_has_internal_descendant_at_min_depth(&spans, server_request_span, /*min_depth*/ 1); + assert_has_internal_descendant_at_min_depth(&spans, server_request_span, /*min_depth*/ 2); harness.shutdown().await; Ok(()) @@ -580,7 +588,7 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> { let _guard = tracing_test_guard().lock().await; let mut harness = TracingHarness::new().await?; - let thread_start_response = harness.start_thread(2, None).await; + let thread_start_response = harness.start_thread(/*request_id*/ 2, /*trace*/ None).await; let thread_id = thread_start_response.thread.id.clone(); harness.reset_tracing(); diff --git a/codex-rs/app-server/src/outgoing_message.rs b/codex-rs/app-server/src/outgoing_message.rs index 946a819d79..d4e4bde063 100644 --- a/codex-rs/app-server/src/outgoing_message.rs +++ b/codex-rs/app-server/src/outgoing_message.rs @@ -886,7 +886,7 @@ mod tests { .register_request_context(RequestContext::new( request_id.clone(), tracing::info_span!("app_server.request", rpc.method = "thread/start"), - None, + /*parent_trace*/ None, )) .await; assert_eq!(outgoing.request_context_count().await, 1); @@ -997,14 +997,14 @@ mod tests { .register_request_context(RequestContext::new( closed_connection_request, tracing::info_span!("app_server.request", rpc.method = "turn/interrupt"), - None, + /*parent_trace*/ None, )) .await; outgoing .register_request_context(RequestContext::new( open_connection_request, tracing::info_span!("app_server.request", rpc.method = "turn/start"), - None, + /*parent_trace*/ None, )) .await; assert_eq!(outgoing.request_context_count().await, 2); diff --git a/codex-rs/app-server/src/thread_status.rs b/codex-rs/app-server/src/thread_status.rs index f4728616db..0f0b3eea75 100644 --- a/codex-rs/app-server/src/thread_status.rs +++ b/codex-rs/app-server/src/thread_status.rs @@ -530,7 +530,7 @@ mod tests { #[test] fn resolves_in_progress_turn_to_active_status() { - let status = resolve_thread_status(ThreadStatus::Idle, true); + let status = resolve_thread_status(ThreadStatus::Idle, /*has_in_progress_turn*/ true); assert_eq!( status, ThreadStatus::Active { @@ -538,7 +538,8 @@ mod tests { } ); - let status = resolve_thread_status(ThreadStatus::NotLoaded, true); + let status = + resolve_thread_status(ThreadStatus::NotLoaded, /*has_in_progress_turn*/ true); assert_eq!( status, ThreadStatus::Active { @@ -550,11 +551,14 @@ mod tests { #[test] fn keeps_status_when_no_in_progress_turn() { assert_eq!( - resolve_thread_status(ThreadStatus::Idle, false), + resolve_thread_status(ThreadStatus::Idle, /*has_in_progress_turn*/ false), ThreadStatus::Idle ); assert_eq!( - resolve_thread_status(ThreadStatus::SystemError, false), + resolve_thread_status( + ThreadStatus::SystemError, + /*has_in_progress_turn*/ false + ), ThreadStatus::SystemError ); } diff --git a/codex-rs/app-server/src/transport/auth.rs b/codex-rs/app-server/src/transport/auth.rs index 1c6080e748..a67c692b7d 100644 --- a/codex-rs/app-server/src/transport/auth.rs +++ b/codex-rs/app-server/src/transport/auth.rs @@ -506,8 +506,14 @@ mod tests { }), ); let tampered = token.replace(".eyJleHAi", ".eyJleHBi"); - let err = verify_signed_bearer_token(&tampered, shared_secret, None, None, 30) - .expect_err("tampered jwt should fail"); + let err = verify_signed_bearer_token( + &tampered, + shared_secret, + /*issuer*/ None, + /*audience*/ None, + /*max_clock_skew_seconds*/ 30, + ) + .expect_err("tampered jwt should fail"); assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED); } @@ -522,8 +528,14 @@ mod tests { "aud": "audience", }), ); - verify_signed_bearer_token(&token, shared_secret, Some("issuer"), Some("audience"), 30) - .expect("valid signed token should verify"); + verify_signed_bearer_token( + &token, + shared_secret, + Some("issuer"), + Some("audience"), + /*max_clock_skew_seconds*/ 30, + ) + .expect("valid signed token should verify"); } #[test] @@ -536,8 +548,14 @@ mod tests { "aud": ["other-audience", "audience"], }), ); - verify_signed_bearer_token(&token, shared_secret, None, Some("audience"), 30) - .expect("jwt audience arrays should verify"); + verify_signed_bearer_token( + &token, + shared_secret, + /*issuer*/ None, + Some("audience"), + /*max_clock_skew_seconds*/ 30, + ) + .expect("jwt audience arrays should verify"); } #[test] @@ -550,9 +568,14 @@ mod tests { ); let header_segment = URL_SAFE_NO_PAD.encode(br#"{"alg":"none","typ":"JWT"}"#); let token = format!("{header_segment}.{claims_segment}."); - let err = - verify_signed_bearer_token(&token, b"0123456789abcdef0123456789abcdef", None, None, 30) - .expect_err("alg=none jwt should be rejected"); + let err = verify_signed_bearer_token( + &token, + b"0123456789abcdef0123456789abcdef", + /*issuer*/ None, + /*audience*/ None, + /*max_clock_skew_seconds*/ 30, + ) + .expect_err("alg=none jwt should be rejected"); assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED); } @@ -565,8 +588,14 @@ mod tests { "iss": "issuer", }), ); - let err = verify_signed_bearer_token(&token, shared_secret, None, None, 30) - .expect_err("jwt without exp should be rejected"); + let err = verify_signed_bearer_token( + &token, + shared_secret, + /*issuer*/ None, + /*audience*/ None, + /*max_clock_skew_seconds*/ 30, + ) + .expect_err("jwt without exp should be rejected"); assert_eq!(err.status_code(), StatusCode::UNAUTHORIZED); } diff --git a/codex-rs/app-server/src/transport/mod.rs b/codex-rs/app-server/src/transport/mod.rs index 504a02f23c..7e1512a794 100644 --- a/codex-rs/app-server/src/transport/mod.rs +++ b/codex-rs/app-server/src/transport/mod.rs @@ -627,7 +627,7 @@ mod tests { initialized, Arc::new(AtomicBool::new(true)), opted_out_notification_methods, - None, + /*disconnect_sender*/ None, ), ); @@ -667,7 +667,7 @@ mod tests { Arc::new(AtomicBool::new(true)), Arc::new(AtomicBool::new(true)), Arc::new(RwLock::new(HashSet::from(["configWarning".to_string()]))), - None, + /*disconnect_sender*/ None, ), ); @@ -707,7 +707,7 @@ mod tests { Arc::new(AtomicBool::new(true)), Arc::new(AtomicBool::new(true)), Arc::new(RwLock::new(HashSet::new())), - None, + /*disconnect_sender*/ None, ), ); @@ -753,7 +753,7 @@ mod tests { Arc::new(AtomicBool::new(true)), Arc::new(AtomicBool::new(false)), Arc::new(RwLock::new(HashSet::new())), - None, + /*disconnect_sender*/ None, ), ); @@ -815,7 +815,7 @@ mod tests { Arc::new(AtomicBool::new(true)), Arc::new(AtomicBool::new(true)), Arc::new(RwLock::new(HashSet::new())), - None, + /*disconnect_sender*/ None, ), ); @@ -987,7 +987,7 @@ mod tests { Arc::new(AtomicBool::new(true)), Arc::new(AtomicBool::new(true)), Arc::new(RwLock::new(HashSet::new())), - None, + /*disconnect_sender*/ None, ), ); diff --git a/codex-rs/app-server/src/transport/remote_control/client_tracker.rs b/codex-rs/app-server/src/transport/remote_control/client_tracker.rs index be80ad523a..f255eba6fc 100644 --- a/codex-rs/app-server/src/transport/remote_control/client_tracker.rs +++ b/codex-rs/app-server/src/transport/remote_control/client_tracker.rs @@ -344,7 +344,7 @@ mod tests { use tokio::time::timeout; fn initialize_envelope(client_id: &str) -> ClientEnvelope { - initialize_envelope_with_stream_id(client_id, None) + initialize_envelope_with_stream_id(client_id, /*stream_id*/ None) } fn initialize_envelope_with_stream_id( diff --git a/codex-rs/app-server/src/transport/remote_control/enroll.rs b/codex-rs/app-server/src/transport/remote_control/enroll.rs index f74721beac..86cc196052 100644 --- a/codex-rs/app-server/src/transport/remote_control/enroll.rs +++ b/codex-rs/app-server/src/transport/remote_control/enroll.rs @@ -327,7 +327,7 @@ mod tests { Some(state_db.as_ref()), &first_target, Some("account-a"), - None, + /*enrollment*/ None, ) .await .expect("matching enrollment should clear"); diff --git a/codex-rs/app-server/src/transport/remote_control/websocket.rs b/codex-rs/app-server/src/transport/remote_control/websocket.rs index 52cc9c14de..fb2f877775 100644 --- a/codex-rs/app-server/src/transport/remote_control/websocket.rs +++ b/codex-rs/app-server/src/transport/remote_control/websocket.rs @@ -471,11 +471,12 @@ impl RemoteControlWebsocket { } if (matches!(&client_envelope.event, ClientEvent::ClientClosed) || remote_control_message_starts_connection(&client_envelope.event)) - && let Some(stream_id) = resolved_stream_id.as_ref() { - state - .outbound_buffer - .remove(&client_envelope.client_id, stream_id); - } + && let Some(stream_id) = resolved_stream_id.as_ref() + { + state + .outbound_buffer + .remove(&client_envelope.client_id, stream_id); + } drop(state); if client_tracker @@ -864,7 +865,7 @@ mod tests { &auth_manager, &mut auth_recovery, &mut enrollment, - None, + /*subscribe_cursor*/ None, ) .await { @@ -925,7 +926,7 @@ mod tests { &auth_manager, &mut auth_recovery, &mut enrollment, - None, + /*subscribe_cursor*/ None, ) .await .expect_err("unauthorized response should fail the websocket connect"); @@ -991,7 +992,7 @@ mod tests { &auth_manager, &mut auth_recovery, &mut enrollment, - None, + /*subscribe_cursor*/ None, ) .await .expect_err("unauthorized enrollment should fail the websocket connect"); @@ -1032,7 +1033,7 @@ mod tests { async move { RemoteControlWebsocket::new( remote_control_target, - None, + /*state_db*/ None, remote_control_auth_manager(), transport_event_tx, shutdown_token, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index fe21cdafff..0345028d88 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -840,6 +840,14 @@ impl McpProcess { self.send_request("account/login/start", Some(params)).await } + /// Send an `account/login/start` JSON-RPC request for ChatGPT device code login. + pub async fn send_login_account_chatgpt_device_code_request(&mut self) -> anyhow::Result { + let params = serde_json::json!({ + "type": "chatgptDeviceCode" + }); + self.send_request("account/login/start", Some(params)).await + } + /// Send an `account/login/cancel` JSON-RPC request. pub async fn send_cancel_login_account_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/auth.rs b/codex-rs/app-server/tests/suite/auth.rs index ff50cd744a..769d73b82d 100644 --- a/codex-rs/app-server/tests/suite/auth.rs +++ b/codex-rs/app-server/tests/suite/auth.rs @@ -160,7 +160,7 @@ async fn get_auth_status_with_api_key() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_auth_status_with_api_key_when_auth_not_required() -> Result<()> { let codex_home = TempDir::new()?; - create_config_toml_custom_provider(codex_home.path(), false)?; + create_config_toml_custom_provider(codex_home.path(), /*requires_openai_auth*/ false)?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; diff --git a/codex-rs/app-server/tests/suite/conversation_summary.rs b/codex-rs/app-server/tests/suite/conversation_summary.rs index 31108a8455..9e292d602f 100644 --- a/codex-rs/app-server/tests/suite/conversation_summary.rs +++ b/codex-rs/app-server/tests/suite/conversation_summary.rs @@ -45,7 +45,7 @@ async fn get_conversation_summary_by_thread_id_reads_rollout() -> Result<()> { META_RFC3339, PREVIEW, Some(MODEL_PROVIDER), - None, + /*git_info*/ None, )?; let thread_id = ThreadId::from_string(&conversation_id)?; let expected = expected_summary( @@ -86,7 +86,7 @@ async fn get_conversation_summary_by_relative_rollout_path_resolves_from_codex_h META_RFC3339, PREVIEW, Some(MODEL_PROVIDER), - None, + /*git_info*/ None, )?; let thread_id = ThreadId::from_string(&conversation_id)?; let rollout_path = rollout_path(codex_home.path(), FILENAME_TS, &conversation_id); diff --git a/codex-rs/app-server/tests/suite/fuzzy_file_search.rs b/codex-rs/app-server/tests/suite/fuzzy_file_search.rs index 692304c6f6..5a34fa4d39 100644 --- a/codex-rs/app-server/tests/suite/fuzzy_file_search.rs +++ b/codex-rs/app-server/tests/suite/fuzzy_file_search.rs @@ -237,7 +237,11 @@ async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> { let root_path = root.path().to_string_lossy().to_string(); // Send fuzzyFileSearch request. let request_id = mcp - .send_fuzzy_file_search_request("abe", vec![root_path.clone()], None) + .send_fuzzy_file_search_request( + "abe", + vec![root_path.clone()], + /*cancellation_token*/ None, + ) .await?; // Read response and verify shape and ordering. @@ -298,7 +302,11 @@ async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> { let root_path = root.path().to_string_lossy().to_string(); let request_id = mcp - .send_fuzzy_file_search_request("alp", vec![root_path.clone()], None) + .send_fuzzy_file_search_request( + "alp", + vec![root_path.clone()], + /*cancellation_token*/ None, + ) .await?; let request_id_2 = mcp diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index 514a6542a0..1505060a99 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -39,10 +39,14 @@ use std::path::Path; use std::time::Duration; use tempfile::TempDir; use tokio::time::timeout; +use wiremock::Mock; use wiremock::MockServer; use wiremock::ResponseTemplate; +use wiremock::matchers::method; +use wiremock::matchers::path; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +const LOGIN_ISSUER_ENV_VAR: &str = "CODEX_APP_SERVER_LOGIN_ISSUER"; // Helper to create a minimal config.toml for the app server #[derive(Default)] @@ -98,6 +102,58 @@ stream_max_retries = 0 std::fs::write(config_toml, contents) } +async fn mock_device_code_usercode(server: &MockServer, interval_seconds: u64) { + Mock::given(method("POST")) + .and(path("/api/accounts/deviceauth/usercode")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "device_auth_id": "device-auth-123", + "user_code": "CODE-12345", + "interval": interval_seconds.to_string(), + }))) + .mount(server) + .await; +} + +async fn mock_device_code_usercode_failure(server: &MockServer, status: u16) { + Mock::given(method("POST")) + .and(path("/api/accounts/deviceauth/usercode")) + .respond_with(ResponseTemplate::new(status)) + .mount(server) + .await; +} + +async fn mock_device_code_token_success(server: &MockServer) { + Mock::given(method("POST")) + .and(path("/api/accounts/deviceauth/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "authorization_code": "poll-code-321", + "code_challenge": "code-challenge-321", + "code_verifier": "code-verifier-321", + }))) + .mount(server) + .await; +} + +async fn mock_device_code_token_failure(server: &MockServer, status: u16) { + Mock::given(method("POST")) + .and(path("/api/accounts/deviceauth/token")) + .respond_with(ResponseTemplate::new(status)) + .mount(server) + .await; +} + +async fn mock_device_code_oauth_token(server: &MockServer, id_token: &str) { + Mock::given(method("POST")) + .and(path("/oauth/token")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id_token": id_token, + "access_token": "access-token-123", + "refresh_token": "refresh-token-123", + }))) + .mount(server) + .await; +} + #[tokio::test] async fn logout_account_removes_auth_and_notifies() -> Result<()> { let codex_home = TempDir::new()?; @@ -912,6 +968,305 @@ async fn login_account_chatgpt_rejected_when_forced_api() -> Result<()> { Ok(()) } +#[tokio::test] +async fn login_account_chatgpt_device_code_returns_error_when_disabled() -> Result<()> { + let codex_home = TempDir::new()?; + let mock_server = MockServer::start().await; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + requires_openai_auth: Some(true), + base_url: Some(format!("{}/v1", mock_server.uri())), + ..Default::default() + }, + )?; + write_models_cache(codex_home.path())?; + mock_device_code_usercode_failure(&mock_server, /*status*/ 404).await; + + let issuer = mock_server.uri(); + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[ + ("OPENAI_API_KEY", None), + (LOGIN_ISSUER_ENV_VAR, Some(issuer.as_str())), + ], + ) + .await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp.send_login_account_chatgpt_device_code_request().await?; + let err: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + assert!( + err.error + .message + .contains("device code login is not enabled"), + "unexpected error: {:?}", + err.error.message + ); + + let maybe_completed = timeout( + Duration::from_millis(500), + mcp.read_stream_until_notification_message("account/login/completed"), + ) + .await; + assert!( + maybe_completed.is_err(), + "account/login/completed should not be emitted when device code start fails" + ); + assert!( + !codex_home.path().join("auth.json").exists(), + "auth.json should not be created when device code start fails" + ); + Ok(()) +} + +#[tokio::test] +async fn login_account_chatgpt_device_code_succeeds_and_notifies() -> Result<()> { + let codex_home = TempDir::new()?; + let mock_server = MockServer::start().await; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + requires_openai_auth: Some(true), + base_url: Some(format!("{}/v1", mock_server.uri())), + ..Default::default() + }, + )?; + write_models_cache(codex_home.path())?; + + mock_device_code_usercode(&mock_server, /*interval_seconds*/ 0).await; + mock_device_code_token_success(&mock_server).await; + let id_token = encode_id_token( + &ChatGptIdTokenClaims::new() + .email("device@example.com") + .plan_type("pro") + .chatgpt_account_id("org-device"), + )?; + mock_device_code_oauth_token(&mock_server, &id_token).await; + + let issuer = mock_server.uri(); + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[ + ("OPENAI_API_KEY", None), + (LOGIN_ISSUER_ENV_VAR, Some(issuer.as_str())), + ], + ) + .await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp.send_login_account_chatgpt_device_code_request().await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let login: LoginAccountResponse = to_response(resp)?; + let LoginAccountResponse::ChatgptDeviceCode { + login_id, + verification_url, + user_code, + } = login + else { + bail!("unexpected login response: {login:?}"); + }; + assert_eq!(verification_url, format!("{issuer}/codex/device")); + assert_eq!(user_code, "CODE-12345"); + + let note = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("account/login/completed"), + ) + .await??; + let parsed: ServerNotification = note.try_into()?; + let ServerNotification::AccountLoginCompleted(payload) = parsed else { + bail!("unexpected notification: {parsed:?}"); + }; + assert_eq!(payload.login_id, Some(login_id)); + assert_eq!(payload.success, true); + assert_eq!(payload.error, None); + + let note = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("account/updated"), + ) + .await??; + let parsed: ServerNotification = note.try_into()?; + let ServerNotification::AccountUpdated(payload) = parsed else { + bail!("unexpected notification: {parsed:?}"); + }; + assert_eq!(payload.auth_mode, Some(AuthMode::Chatgpt)); + assert_eq!(payload.plan_type, Some(AccountPlanType::Pro)); + assert!( + codex_home.path().join("auth.json").exists(), + "auth.json should be created when device code login succeeds" + ); + Ok(()) +} + +#[tokio::test] +async fn login_account_chatgpt_device_code_failure_notifies_without_account_update() -> Result<()> { + let codex_home = TempDir::new()?; + let mock_server = MockServer::start().await; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + requires_openai_auth: Some(true), + base_url: Some(format!("{}/v1", mock_server.uri())), + ..Default::default() + }, + )?; + write_models_cache(codex_home.path())?; + + mock_device_code_usercode(&mock_server, /*interval_seconds*/ 0).await; + mock_device_code_token_failure(&mock_server, /*status*/ 500).await; + + let issuer = mock_server.uri(); + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[ + ("OPENAI_API_KEY", None), + (LOGIN_ISSUER_ENV_VAR, Some(issuer.as_str())), + ], + ) + .await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp.send_login_account_chatgpt_device_code_request().await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let login: LoginAccountResponse = to_response(resp)?; + let LoginAccountResponse::ChatgptDeviceCode { login_id, .. } = login else { + bail!("unexpected login response: {login:?}"); + }; + + let note = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("account/login/completed"), + ) + .await??; + let parsed: ServerNotification = note.try_into()?; + let ServerNotification::AccountLoginCompleted(payload) = parsed else { + bail!("unexpected notification: {parsed:?}"); + }; + assert_eq!(payload.login_id, Some(login_id)); + assert_eq!(payload.success, false); + assert!( + payload + .error + .as_deref() + .is_some_and(|error| error.contains("device auth failed with status")), + "unexpected error: {:?}", + payload.error + ); + + let maybe_updated = timeout( + Duration::from_millis(500), + mcp.read_stream_until_notification_message("account/updated"), + ) + .await; + assert!( + maybe_updated.is_err(), + "account/updated should not be emitted when device code login fails" + ); + assert!( + !codex_home.path().join("auth.json").exists(), + "auth.json should not be created when device code login fails" + ); + Ok(()) +} + +#[tokio::test] +async fn login_account_chatgpt_device_code_can_be_cancelled() -> Result<()> { + let codex_home = TempDir::new()?; + let mock_server = MockServer::start().await; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + requires_openai_auth: Some(true), + base_url: Some(format!("{}/v1", mock_server.uri())), + ..Default::default() + }, + )?; + write_models_cache(codex_home.path())?; + + mock_device_code_usercode(&mock_server, /*interval_seconds*/ 1).await; + mock_device_code_token_failure(&mock_server, /*status*/ 404).await; + + let issuer = mock_server.uri(); + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[ + ("OPENAI_API_KEY", None), + (LOGIN_ISSUER_ENV_VAR, Some(issuer.as_str())), + ], + ) + .await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp.send_login_account_chatgpt_device_code_request().await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let login: LoginAccountResponse = to_response(resp)?; + let LoginAccountResponse::ChatgptDeviceCode { login_id, .. } = login else { + bail!("unexpected login response: {login:?}"); + }; + + let cancel_id = mcp + .send_cancel_login_account_request(CancelLoginAccountParams { + login_id: login_id.clone(), + }) + .await?; + let cancel_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)), + ) + .await??; + let cancel: CancelLoginAccountResponse = to_response(cancel_resp)?; + assert_eq!(cancel.status, CancelLoginAccountStatus::Canceled); + + let note = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("account/login/completed"), + ) + .await??; + let parsed: ServerNotification = note.try_into()?; + let ServerNotification::AccountLoginCompleted(payload) = parsed else { + bail!("unexpected notification: {parsed:?}"); + }; + assert_eq!(payload.login_id, Some(login_id)); + assert_eq!(payload.success, false); + assert!( + payload.error.is_some(), + "expected a non-empty error on device code cancel" + ); + + let maybe_updated = timeout( + Duration::from_millis(500), + mcp.read_stream_until_notification_message("account/updated"), + ) + .await; + assert!( + maybe_updated.is_err(), + "account/updated should not be emitted when device code login is cancelled" + ); + assert!( + !codex_home.path().join("auth.json").exists(), + "auth.json should not be created when device code login is cancelled" + ); + Ok(()) +} + #[tokio::test] // Serialize tests that launch the login server since it binds to a fixed port. #[serial(login_port)] diff --git a/codex-rs/app-server/tests/suite/v2/analytics.rs b/codex-rs/app-server/tests/suite/v2/analytics.rs index e64287cb22..a389ffbe1a 100644 --- a/codex-rs/app-server/tests/suite/v2/analytics.rs +++ b/codex-rs/app-server/tests/suite/v2/analytics.rs @@ -31,7 +31,7 @@ async fn app_server_default_analytics_disabled_without_flag() -> Result<()> { &config, SERVICE_VERSION, Some("codex-app-server"), - false, + /*default_analytics_enabled*/ false, ) .map_err(|err| anyhow::anyhow!(err.to_string()))?; @@ -56,7 +56,7 @@ async fn app_server_default_analytics_enabled_with_flag() -> Result<()> { &config, SERVICE_VERSION, Some("codex-app-server"), - true, + /*default_analytics_enabled*/ true, ) .map_err(|err| anyhow::anyhow!(err.to_string()))?; diff --git a/codex-rs/app-server/tests/suite/v2/command_exec.rs b/codex-rs/app-server/tests/suite/v2/command_exec.rs index ecd897eb84..50e8285665 100644 --- a/codex-rs/app-server/tests/suite/v2/command_exec.rs +++ b/codex-rs/app-server/tests/suite/v2/command_exec.rs @@ -722,15 +722,15 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate let mut ws1 = connect_websocket(bind_addr).await?; let mut ws2 = connect_websocket(bind_addr).await?; - send_initialize_request(&mut ws1, 1, "ws_client_one").await?; - read_initialize_response(&mut ws1, 1).await?; - send_initialize_request(&mut ws2, 2, "ws_client_two").await?; - read_initialize_response(&mut ws2, 2).await?; + send_initialize_request(&mut ws1, /*id*/ 1, "ws_client_one").await?; + read_initialize_response(&mut ws1, /*request_id*/ 1).await?; + send_initialize_request(&mut ws2, /*id*/ 2, "ws_client_two").await?; + read_initialize_response(&mut ws2, /*request_id*/ 2).await?; send_request( &mut ws1, "command/exec", - 101, + /*id*/ 101, Some(serde_json::json!({ "command": [ "python3", @@ -749,12 +749,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate assert_eq!(delta.stream, CommandExecOutputStream::Stdout); let delta_text = String::from_utf8(STANDARD.decode(&delta.delta_base64)?)?; assert!(delta_text.contains("ready")); - wait_for_process_marker(&marker, true).await?; + wait_for_process_marker(&marker, /*should_exist*/ true).await?; send_request( &mut ws2, "command/exec/terminate", - 102, + /*id*/ 102, Some(serde_json::json!({ "processId": "shared-process", })), @@ -773,12 +773,12 @@ async fn command_exec_process_ids_are_connection_scoped_and_disconnect_terminate terminate_error.error.message, "no active command/exec for process id \"shared-process\"" ); - wait_for_process_marker(&marker, true).await?; + wait_for_process_marker(&marker, /*should_exist*/ true).await?; assert_no_message(&mut ws2, Duration::from_millis(250)).await?; ws1.close(None).await?; - wait_for_process_marker(&marker, false).await?; + wait_for_process_marker(&marker, /*should_exist*/ false).await?; process .kill() diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index c0922d3256..a1bde9e965 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -50,19 +50,19 @@ async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()> let server = responses::start_mock_server().await; let sse1 = responses::sse(vec![ responses::ev_assistant_message("m1", "FIRST_REPLY"), - responses::ev_completed_with_tokens("r1", 70_000), + responses::ev_completed_with_tokens("r1", /*total_tokens*/ 70_000), ]); let sse2 = responses::sse(vec![ responses::ev_assistant_message("m2", "SECOND_REPLY"), - responses::ev_completed_with_tokens("r2", 330_000), + responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000), ]); let sse3 = responses::sse(vec![ responses::ev_assistant_message("m3", "LOCAL_SUMMARY"), - responses::ev_completed_with_tokens("r3", 200), + responses::ev_completed_with_tokens("r3", /*total_tokens*/ 200), ]); let sse4 = responses::sse(vec![ responses::ev_assistant_message("m4", "FINAL_REPLY"), - responses::ev_completed_with_tokens("r4", 120), + responses::ev_completed_with_tokens("r4", /*total_tokens*/ 120), ]); responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3, sse4]).await; @@ -72,7 +72,7 @@ async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()> &server.uri(), &BTreeMap::default(), AUTO_COMPACT_LIMIT, - None, + /*requires_openai_auth*/ None, "mock_provider", COMPACT_PROMPT, )?; @@ -110,15 +110,15 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<() let server = responses::start_mock_server().await; let sse1 = responses::sse(vec![ responses::ev_assistant_message("m1", "FIRST_REPLY"), - responses::ev_completed_with_tokens("r1", 70_000), + responses::ev_completed_with_tokens("r1", /*total_tokens*/ 70_000), ]); let sse2 = responses::sse(vec![ responses::ev_assistant_message("m2", "SECOND_REPLY"), - responses::ev_completed_with_tokens("r2", 330_000), + responses::ev_completed_with_tokens("r2", /*total_tokens*/ 330_000), ]); let sse3 = responses::sse(vec![ responses::ev_assistant_message("m3", "FINAL_REPLY"), - responses::ev_completed_with_tokens("r3", 120), + responses::ev_completed_with_tokens("r3", /*total_tokens*/ 120), ]); let responses_log = responses::mount_sse_sequence(&server, vec![sse1, sse2, sse3]).await; @@ -197,7 +197,7 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() - let server = responses::start_mock_server().await; let sse = responses::sse(vec![ responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"), - responses::ev_completed_with_tokens("r1", 200), + responses::ev_completed_with_tokens("r1", /*total_tokens*/ 200), ]); responses::mount_sse_sequence(&server, vec![sse]).await; @@ -207,7 +207,7 @@ async fn thread_compact_start_triggers_compaction_and_returns_empty_response() - &server.uri(), &BTreeMap::default(), AUTO_COMPACT_LIMIT, - None, + /*requires_openai_auth*/ None, "mock_provider", COMPACT_PROMPT, )?; @@ -257,7 +257,7 @@ async fn thread_compact_start_rejects_invalid_thread_id() -> Result<()> { &server.uri(), &BTreeMap::default(), AUTO_COMPACT_LIMIT, - None, + /*requires_openai_auth*/ None, "mock_provider", COMPACT_PROMPT, )?; @@ -293,7 +293,7 @@ async fn thread_compact_start_rejects_unknown_thread_id() -> Result<()> { &server.uri(), &BTreeMap::default(), AUTO_COMPACT_LIMIT, - None, + /*requires_openai_auth*/ None, "mock_provider", COMPACT_PROMPT, )?; diff --git a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs index 19158c68cb..bc311b31ca 100644 --- a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs +++ b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs @@ -58,27 +58,27 @@ async fn websocket_transport_routes_per_connection_handshake_and_responses() -> let mut ws1 = connect_websocket(bind_addr).await?; let mut ws2 = connect_websocket(bind_addr).await?; - send_initialize_request(&mut ws1, 1, "ws_client_one").await?; - let first_init = read_response_for_id(&mut ws1, 1).await?; + send_initialize_request(&mut ws1, /*id*/ 1, "ws_client_one").await?; + let first_init = read_response_for_id(&mut ws1, /*id*/ 1).await?; assert_eq!(first_init.id, RequestId::Integer(1)); // Initialize responses are request-scoped and must not leak to other // connections. assert_no_message(&mut ws2, Duration::from_millis(250)).await?; - send_config_read_request(&mut ws2, 2).await?; - let not_initialized = read_error_for_id(&mut ws2, 2).await?; + send_config_read_request(&mut ws2, /*id*/ 2).await?; + let not_initialized = read_error_for_id(&mut ws2, /*id*/ 2).await?; assert_eq!(not_initialized.error.message, "Not initialized"); - send_initialize_request(&mut ws2, 3, "ws_client_two").await?; - let second_init = read_response_for_id(&mut ws2, 3).await?; + send_initialize_request(&mut ws2, /*id*/ 3, "ws_client_two").await?; + let second_init = read_response_for_id(&mut ws2, /*id*/ 3).await?; assert_eq!(second_init.id, RequestId::Integer(3)); // Same request-id on different connections must route independently. - send_config_read_request(&mut ws1, 77).await?; - send_config_read_request(&mut ws2, 77).await?; - let ws1_config = read_response_for_id(&mut ws1, 77).await?; - let ws2_config = read_response_for_id(&mut ws2, 77).await?; + send_config_read_request(&mut ws1, /*id*/ 77).await?; + send_config_read_request(&mut ws2, /*id*/ 77).await?; + let ws1_config = read_response_for_id(&mut ws1, /*id*/ 77).await?; + let ws2_config = read_response_for_id(&mut ws2, /*id*/ 77).await?; assert_eq!(ws1_config.id, RequestId::Integer(77)); assert_eq!(ws2_config.id, RequestId::Integer(77)); @@ -108,8 +108,8 @@ async fn websocket_transport_serves_health_endpoints_on_same_listener() -> Resul assert_eq!(healthz.status(), StatusCode::OK); let mut ws = connect_websocket(bind_addr).await?; - send_initialize_request(&mut ws, 1, "ws_health_client").await?; - let init = read_response_for_id(&mut ws, 1).await?; + send_initialize_request(&mut ws, /*id*/ 1, "ws_health_client").await?; + let init = read_response_for_id(&mut ws, /*id*/ 1).await?; assert_eq!(init.id, RequestId::Integer(1)); process @@ -128,14 +128,14 @@ async fn websocket_transport_rejects_browser_origin_without_auth() -> Result<()> let (mut process, bind_addr) = spawn_websocket_server(codex_home.path()).await?; let mut ws = connect_websocket(bind_addr).await?; - send_initialize_request(&mut ws, 1, "ws_loopback_client").await?; - let init = read_response_for_id(&mut ws, 1).await?; + send_initialize_request(&mut ws, /*id*/ 1, "ws_loopback_client").await?; + let init = read_response_for_id(&mut ws, /*id*/ 1).await?; assert_eq!(init.id, RequestId::Integer(1)); drop(ws); assert_websocket_connect_rejected_with_headers( bind_addr, - None, + /*bearer_token*/ None, Some("https://evil.example"), StatusCode::FORBIDDEN, ) @@ -165,12 +165,12 @@ async fn websocket_transport_rejects_missing_and_invalid_capability_tokens() -> let (mut process, bind_addr) = spawn_websocket_server_with_args(codex_home.path(), "ws://127.0.0.1:0", &auth_args).await?; - assert_websocket_connect_rejected(bind_addr, None).await?; + assert_websocket_connect_rejected(bind_addr, /*bearer_token*/ None).await?; assert_websocket_connect_rejected(bind_addr, Some("wrong-token")).await?; let mut ws = connect_websocket_with_bearer(bind_addr, Some("super-secret-token")).await?; - send_initialize_request(&mut ws, 1, "ws_auth_client").await?; - let init = read_response_for_id(&mut ws, 1).await?; + send_initialize_request(&mut ws, /*id*/ 1, "ws_auth_client").await?; + let init = read_response_for_id(&mut ws, /*id*/ 1).await?; assert_eq!(init.id, RequestId::Integer(1)); process @@ -266,8 +266,8 @@ async fn websocket_transport_verifies_signed_short_lived_bearer_tokens() -> Resu }), )?; let mut ws = connect_websocket_with_bearer(bind_addr, Some(valid_token.as_str())).await?; - send_initialize_request(&mut ws, 1, "ws_signed_auth_client").await?; - let init = read_response_for_id(&mut ws, 1).await?; + send_initialize_request(&mut ws, /*id*/ 1, "ws_signed_auth_client").await?; + let init = read_response_for_id(&mut ws, /*id*/ 1).await?; assert_eq!(init.id, RequestId::Integer(1)); process @@ -320,8 +320,8 @@ async fn websocket_transport_allows_unauthenticated_non_loopback_startup_by_defa spawn_websocket_server_with_args(codex_home.path(), "ws://0.0.0.0:0", &[]).await?; let mut ws = connect_websocket(bind_addr).await?; - send_initialize_request(&mut ws, 1, "ws_non_loopback_default_client").await?; - let init = read_response_for_id(&mut ws, 1).await?; + send_initialize_request(&mut ws, /*id*/ 1, "ws_non_loopback_default_client").await?; + let init = read_response_for_id(&mut ws, /*id*/ 1).await?; assert_eq!(init.id, RequestId::Integer(1)); process @@ -411,7 +411,7 @@ pub(super) async fn spawn_websocket_server_with_args( } pub(super) async fn connect_websocket(bind_addr: SocketAddr) -> Result { - connect_websocket_with_bearer(bind_addr, None).await + connect_websocket_with_bearer(bind_addr, /*bearer_token*/ None).await } pub(super) async fn connect_websocket_with_bearer( @@ -419,7 +419,7 @@ pub(super) async fn connect_websocket_with_bearer( bearer_token: Option<&str>, ) -> Result { let url = format!("ws://{}", connectable_bind_addr(bind_addr)); - let request = websocket_request(url.as_str(), bearer_token, None)?; + let request = websocket_request(url.as_str(), bearer_token, /*origin*/ None)?; let deadline = Instant::now() + Duration::from_secs(10); loop { match connect_async(request.clone()).await { @@ -441,7 +441,7 @@ async fn assert_websocket_connect_rejected( assert_websocket_connect_rejected_with_headers( bind_addr, bearer_token, - None, + /*origin*/ None, StatusCode::UNAUTHORIZED, ) .await diff --git a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs index 08156b9d95..591b70af09 100644 --- a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs +++ b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket_unix.rs @@ -156,16 +156,16 @@ async fn start_ctrl_c_restart_fixture(turn_delay: Duration) -> Result Model { fn expected_visible_models() -> Vec { // Filter by supported_in_api to support testing with both ChatGPT and non-ChatGPT auth modes. - let mut presets = - ModelPreset::filter_by_auth(codex_core::test_support::all_model_presets().clone(), false); + let mut presets = ModelPreset::filter_by_auth( + codex_core::test_support::all_model_presets().clone(), + /*chatgpt_mode*/ false, + ); // Mirror `ModelsManager::build_available_models()` default selection after auth filtering. ModelPreset::mark_default_by_picker_visibility(&mut presets); diff --git a/codex-rs/app-server/tests/suite/v2/plan_item.rs b/codex-rs/app-server/tests/suite/v2/plan_item.rs index 0ed93cbeae..97e67fa090 100644 --- a/codex-rs/app-server/tests/suite/v2/plan_item.rs +++ b/codex-rs/app-server/tests/suite/v2/plan_item.rs @@ -59,7 +59,7 @@ async fn plan_mode_uses_proposed_plan_block_for_plan_item() -> Result<()> { let turn = start_plan_mode_turn(&mut mcp).await?; let (_, completed_items, plan_deltas, turn_completed) = collect_turn_notifications(&mut mcp).await?; - wait_for_responses_request_count(&server, 1).await?; + wait_for_responses_request_count(&server, /*expected_count*/ 1).await?; assert_eq!(turn_completed.turn.id, turn.id); assert_eq!(turn_completed.turn.status, TurnStatus::Completed); @@ -116,7 +116,7 @@ async fn plan_mode_without_proposed_plan_does_not_emit_plan_item() -> Result<()> let _turn = start_plan_mode_turn(&mut mcp).await?; let (_, completed_items, plan_deltas, _) = collect_turn_notifications(&mut mcp).await?; - wait_for_responses_request_count(&server, 1).await?; + wait_for_responses_request_count(&server, /*expected_count*/ 1).await?; let has_plan_item = completed_items .iter() diff --git a/codex-rs/app-server/tests/suite/v2/plugin_install.rs b/codex-rs/app-server/tests/suite/v2/plugin_install.rs index 8c597d94a3..90a7d813be 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_install.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_install.rs @@ -118,7 +118,7 @@ async fn plugin_install_returns_invalid_request_for_not_available_plugin() -> Re "sample-plugin", "./sample-plugin", Some("NOT_AVAILABLE"), - None, + /*auth_policy*/ None, )?; write_plugin_source(repo_root.path(), "sample-plugin", &[])?; let marketplace_path = @@ -217,8 +217,8 @@ async fn plugin_install_force_remote_sync_enables_remote_plugin_before_local_ins "debug", "sample-plugin", "./sample-plugin", - None, - None, + /*install_policy*/ None, + /*auth_policy*/ None, )?; write_plugin_source(repo_root.path(), "sample-plugin", &[])?; let marketplace_path = @@ -286,8 +286,8 @@ async fn plugin_install_tracks_analytics_event() -> Result<()> { "debug", "sample-plugin", "./sample-plugin", - None, - None, + /*install_policy*/ None, + /*auth_policy*/ None, )?; write_plugin_source(repo_root.path(), "sample-plugin", &[])?; let marketplace_path = @@ -401,8 +401,8 @@ async fn plugin_install_returns_apps_needing_auth() -> Result<()> { "debug", "sample-plugin", "./sample-plugin", - None, - None, + /*install_policy*/ None, + /*auth_policy*/ None, )?; write_plugin_source(repo_root.path(), "sample-plugin", &["alpha", "beta"])?; let marketplace_path = @@ -481,7 +481,7 @@ async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> { "debug", "sample-plugin", "./sample-plugin", - None, + /*install_policy*/ None, Some("ON_USE"), )?; write_plugin_source( @@ -542,8 +542,8 @@ async fn plugin_install_makes_bundled_mcp_servers_available_to_followup_requests "debug", "sample-plugin", "./sample-plugin", - None, - None, + /*install_policy*/ None, + /*auth_policy*/ None, )?; write_plugin_source(repo_root.path(), "sample-plugin", &[])?; std::fs::write( diff --git a/codex-rs/app-server/tests/suite/v2/plugin_list.rs b/codex-rs/app-server/tests/suite/v2/plugin_list.rs index ce3d4f5286..4136c6d679 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_list.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_list.rs @@ -931,7 +931,8 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> { timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; wait_for_path_exists(&marker_path).await?; - wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?; + wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1) + .await?; let request_id = mcp .send_plugin_list_request(PluginListParams { cwds: None, @@ -957,7 +958,8 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> { .collect::>(), vec![("linear@openai-curated".to_string(), true, true)] ); - wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?; + wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1) + .await?; } let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; @@ -969,7 +971,7 @@ async fn app_server_startup_remote_plugin_sync_runs_once() -> Result<()> { } tokio::time::sleep(Duration::from_millis(250)).await; - wait_for_remote_plugin_request_count(&server, "/plugins/list", 1).await?; + wait_for_remote_plugin_request_count(&server, "/plugins/list", /*expected_count*/ 1).await?; Ok(()) } @@ -1029,7 +1031,7 @@ async fn plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request() -> let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; - wait_for_featured_plugin_request_count(&server, 1).await?; + wait_for_featured_plugin_request_count(&server, /*expected_count*/ 1).await?; let request_id = mcp .send_plugin_list_request(PluginListParams { diff --git a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs b/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs index e8a39efa64..317155da70 100644 --- a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs +++ b/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs @@ -99,7 +99,7 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> { codex_home.path(), &responses_server.uri(), realtime_server.uri(), - true, + /*realtime_enabled*/ true, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -137,7 +137,9 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> { assert!(started.session_id.is_some()); assert_eq!(started.version, RealtimeConversationVersion::V2); - let startup_context_request = realtime_server.wait_for_request(0, 0).await; + let startup_context_request = realtime_server + .wait_for_request(/*connection_index*/ 0, /*request_index*/ 0) + .await; assert_eq!( startup_context_request.body_json()["type"].as_str(), Some("session.update") @@ -306,7 +308,7 @@ async fn realtime_conversation_stop_emits_closed_notification() -> Result<()> { codex_home.path(), &responses_server.uri(), realtime_server.uri(), - true, + /*realtime_enabled*/ true, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -378,7 +380,7 @@ async fn realtime_conversation_requires_feature_flag() -> Result<()> { codex_home.path(), &responses_server.uri(), realtime_server.uri(), - false, + /*realtime_enabled*/ false, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; diff --git a/codex-rs/app-server/tests/suite/v2/review.rs b/codex-rs/app-server/tests/suite/v2/review.rs index 6febbaf52a..d56b9318e3 100644 --- a/codex-rs/app-server/tests/suite/v2/review.rs +++ b/codex-rs/app-server/tests/suite/v2/review.rs @@ -149,7 +149,7 @@ async fn review_start_exec_approval_item_id_matches_command_execution_item() -> "rev-parse".to_string(), "HEAD".to_string(), ], - None, + /*workdir*/ None, Some(5000), "review-call-1", )?, diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index a81c87bd4e..e6be4e5a7c 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -53,7 +53,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { "2025-01-05T12:00:00Z", preview, Some("mock_provider"), - None, + /*git_info*/ None, )?; let original_path = codex_home @@ -267,7 +267,7 @@ async fn thread_fork_surfaces_cloud_requirements_load_errors() -> Result<()> { "2025-01-05T12:00:00Z", "Saved user message", Some("mock_provider"), - None, + /*git_info*/ None, )?; let refresh_token_url = format!("{}/oauth/token", server.uri()); @@ -331,7 +331,7 @@ async fn thread_fork_ephemeral_remains_pathless_and_omits_listing() -> Result<() "2025-01-05T12:00:00Z", preview, Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index 2b5d1fe8a0..62faba9c17 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -58,7 +58,16 @@ async fn list_threads( source_kinds: Option>, archived: Option, ) -> Result { - list_threads_with_sort(mcp, cursor, limit, providers, source_kinds, None, archived).await + list_threads_with_sort( + mcp, + cursor, + limit, + providers, + source_kinds, + /*sort_key*/ None, + archived, + ) + .await } async fn list_threads_with_sort( @@ -110,7 +119,7 @@ where &ts_rfc, preview, Some(provider_for_index(i)), - None, + /*git_info*/ None, )?); } Ok(ids) @@ -172,11 +181,11 @@ async fn thread_list_basic_empty() -> Result<()> { data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert!(data.is_empty()); @@ -256,7 +265,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul let ThreadListResponse { data, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ @@ -264,7 +273,7 @@ async fn thread_list_reports_system_error_idle_flag_after_failed_turn() -> Resul ThreadSourceKind::Cli, ThreadSourceKind::VsCode, ]), - None, + /*archived*/ None, ) .await?; let listed = data @@ -323,7 +332,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { "2025-01-02T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let _b = create_fake_rollout( codex_home.path(), @@ -331,7 +340,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { "2025-01-01T13:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let _c = create_fake_rollout( codex_home.path(), @@ -339,7 +348,7 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { "2025-01-01T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = init_mcp(codex_home.path()).await?; @@ -350,11 +359,11 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { next_cursor: cursor1, } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(2), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!(data1.len(), 2); @@ -380,8 +389,8 @@ async fn thread_list_pagination_next_cursor_none_on_last_page() -> Result<()> { Some(cursor1), Some(2), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert!(data2.len() <= 2); @@ -413,7 +422,7 @@ async fn thread_list_respects_provider_filter() -> Result<()> { "2025-01-02T10:00:00Z", "X", Some("mock_provider"), - None, + /*git_info*/ None, )?; // mock_provider let _b = create_fake_rollout( codex_home.path(), @@ -421,7 +430,7 @@ async fn thread_list_respects_provider_filter() -> Result<()> { "2025-01-02T11:00:00Z", "X", Some("other_provider"), - None, + /*git_info*/ None, )?; let mut mcp = init_mcp(codex_home.path()).await?; @@ -431,11 +440,11 @@ async fn thread_list_respects_provider_filter() -> Result<()> { data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["other_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!(data.len(), 1); @@ -465,7 +474,7 @@ async fn thread_list_respects_cwd_filter() -> Result<()> { "2025-01-02T10:00:00Z", "filtered", Some("mock_provider"), - None, + /*git_info*/ None, )?; let unfiltered_id = create_fake_rollout( codex_home.path(), @@ -473,7 +482,7 @@ async fn thread_list_respects_cwd_filter() -> Result<()> { "2025-01-02T11:00:00Z", "unfiltered", Some("mock_provider"), - None, + /*git_info*/ None, )?; let target_cwd = codex_home.path().join("target-cwd"); @@ -535,7 +544,7 @@ sqlite = true "2025-01-02T10:00:00Z", "match: needle", Some("mock_provider"), - None, + /*git_info*/ None, )?; let _non_match = create_fake_rollout( codex_home.path(), @@ -543,7 +552,7 @@ sqlite = true "2025-01-02T11:00:00Z", "no hit here", Some("mock_provider"), - None, + /*git_info*/ None, )?; let newer_match = create_fake_rollout( codex_home.path(), @@ -551,7 +560,7 @@ sqlite = true "2025-01-02T12:00:00Z", "needle suffix", Some("mock_provider"), - None, + /*git_info*/ None, )?; // `thread/list` only applies `search_term` on the sqlite path. In this test we @@ -560,7 +569,9 @@ sqlite = true let state_db = codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()) .await?; - state_db.mark_backfill_complete(None).await?; + state_db + .mark_backfill_complete(/*last_watermark*/ None) + .await?; let mut mcp = init_mcp(codex_home.path()).await?; let request_id = mcp @@ -602,7 +613,7 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result "2025-02-01T10:00:00Z", "CLI", Some("mock_provider"), - None, + /*git_info*/ None, )?; let exec_id = create_fake_rollout_with_source( codex_home.path(), @@ -610,7 +621,7 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result "2025-02-01T11:00:00Z", "Exec", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::Exec, )?; @@ -620,11 +631,11 @@ async fn thread_list_empty_source_kinds_defaults_to_interactive_only() -> Result data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(Vec::new()), - None, + /*archived*/ None, ) .await?; @@ -648,7 +659,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<() "2025-02-01T10:00:00Z", "CLI", Some("mock_provider"), - None, + /*git_info*/ None, )?; let parent_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string())?; @@ -658,7 +669,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<() "2025-02-01T11:00:00Z", "SubAgent", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id, depth: 1, @@ -674,11 +685,11 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<() data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ThreadSourceKind::SubAgentThreadSpawn]), - None, + /*archived*/ None, ) .await?; @@ -704,7 +715,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { "2025-02-02T09:00:00Z", "Review", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::SubAgent(SubAgentSource::Review), )?; let compact_id = create_fake_rollout_with_source( @@ -713,7 +724,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { "2025-02-02T10:00:00Z", "Compact", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::SubAgent(SubAgentSource::Compact), )?; let spawn_id = create_fake_rollout_with_source( @@ -722,7 +733,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { "2025-02-02T11:00:00Z", "Spawn", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id, depth: 1, @@ -737,7 +748,7 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { "2025-02-02T12:00:00Z", "Other", Some("mock_provider"), - None, + /*git_info*/ None, CoreSessionSource::SubAgent(SubAgentSource::Other("custom".to_string())), )?; @@ -745,11 +756,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { let review = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ThreadSourceKind::SubAgentReview]), - None, + /*archived*/ None, ) .await?; let review_ids: Vec<_> = review @@ -761,11 +772,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { let compact = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ThreadSourceKind::SubAgentCompact]), - None, + /*archived*/ None, ) .await?; let compact_ids: Vec<_> = compact @@ -777,11 +788,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { let spawn = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ThreadSourceKind::SubAgentThreadSpawn]), - None, + /*archived*/ None, ) .await?; let spawn_ids: Vec<_> = spawn.data.iter().map(|thread| thread.id.as_str()).collect(); @@ -789,11 +800,11 @@ async fn thread_list_filters_by_subagent_variant() -> Result<()> { let other = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), Some(vec![ThreadSourceKind::SubAgentOther]), - None, + /*archived*/ None, ) .await?; let other_ids: Vec<_> = other.data.iter().map(|thread| thread.id.as_str()).collect(); @@ -812,7 +823,7 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> { // paging past the first two pages to reach the desired count. create_fake_rollouts( codex_home.path(), - 24, + /*count*/ 24, |i| { if i < 16 { "skip_provider" @@ -820,7 +831,16 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> { "target_provider" } }, - |i| timestamp_at(2025, 3, 30 - i as u32, 12, 0, 0), + |i| { + timestamp_at( + /*year*/ 2025, + /*month*/ 3, + 30 - i as u32, + /*hour*/ 12, + /*minute*/ 0, + /*second*/ 0, + ) + }, "Hello", )?; @@ -832,11 +852,11 @@ async fn thread_list_fetches_until_limit_or_exhausted() -> Result<()> { data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(8), Some(vec!["target_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!( @@ -864,12 +884,19 @@ async fn thread_list_enforces_max_limit() -> Result<()> { create_fake_rollouts( codex_home.path(), - 105, + /*count*/ 105, |_| "mock_provider", |i| { let month = 5 + (i / 28); let day = (i % 28) + 1; - timestamp_at(2025, month as u32, day as u32, 0, 0, 0) + timestamp_at( + /*year*/ 2025, + month as u32, + day as u32, + /*hour*/ 0, + /*minute*/ 0, + /*second*/ 0, + ) }, "Hello", )?; @@ -880,11 +907,11 @@ async fn thread_list_enforces_max_limit() -> Result<()> { data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(200), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!( @@ -909,7 +936,7 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<() // ensure the server exhausts pagination without looping forever. create_fake_rollouts( codex_home.path(), - 22, + /*count*/ 22, |i| { if i < 15 { "skip_provider" @@ -917,7 +944,16 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<() "target_provider" } }, - |i| timestamp_at(2025, 4, 28 - i as u32, 8, 0, 0), + |i| { + timestamp_at( + /*year*/ 2025, + /*month*/ 4, + 28 - i as u32, + /*hour*/ 8, + /*minute*/ 0, + /*second*/ 0, + ) + }, "Hello", )?; @@ -929,11 +965,11 @@ async fn thread_list_stops_when_not_enough_filtered_results_exist() -> Result<() data, next_cursor, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["target_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!( @@ -977,11 +1013,11 @@ async fn thread_list_includes_git_info() -> Result<()> { let ThreadListResponse { data, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; let thread = data @@ -1013,7 +1049,7 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> { "2025-01-02T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_b = create_fake_rollout( codex_home.path(), @@ -1021,7 +1057,7 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> { "2025-01-01T13:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_c = create_fake_rollout( codex_home.path(), @@ -1029,19 +1065,19 @@ async fn thread_list_default_sorts_by_created_at() -> Result<()> { "2025-01-01T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = init_mcp(codex_home.path()).await?; let ThreadListResponse { data, .. } = list_threads_with_sort( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, - None, - None, + /*source_kinds*/ None, + /*sort_key*/ None, + /*archived*/ None, ) .await?; @@ -1062,7 +1098,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> { "2025-01-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_mid = create_fake_rollout( codex_home.path(), @@ -1070,7 +1106,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> { "2025-01-01T11:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_new = create_fake_rollout( codex_home.path(), @@ -1078,7 +1114,7 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> { "2025-01-01T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; set_rollout_mtime( @@ -1098,12 +1134,12 @@ async fn thread_list_sort_updated_at_orders_by_mtime() -> Result<()> { let ThreadListResponse { data, .. } = list_threads_with_sort( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(ThreadSortKey::UpdatedAt), - None, + /*archived*/ None, ) .await?; @@ -1124,7 +1160,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { "2025-02-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_b = create_fake_rollout( codex_home.path(), @@ -1132,7 +1168,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { "2025-02-01T11:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_c = create_fake_rollout( codex_home.path(), @@ -1140,7 +1176,7 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { "2025-02-01T12:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; set_rollout_mtime( @@ -1164,12 +1200,12 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { .. } = list_threads_with_sort( &mut mcp, - None, + /*cursor*/ None, Some(2), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(ThreadSortKey::UpdatedAt), - None, + /*archived*/ None, ) .await?; let ids_page1: Vec<_> = page1.iter().map(|thread| thread.id.as_str()).collect(); @@ -1185,9 +1221,9 @@ async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> { Some(cursor1), Some(2), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(ThreadSortKey::UpdatedAt), - None, + /*archived*/ None, ) .await?; let ids_page2: Vec<_> = page2.iter().map(|thread| thread.id.as_str()).collect(); @@ -1208,7 +1244,7 @@ async fn thread_list_created_at_tie_breaks_by_uuid() -> Result<()> { "2025-02-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_b = create_fake_rollout( codex_home.path(), @@ -1216,18 +1252,18 @@ async fn thread_list_created_at_tie_breaks_by_uuid() -> Result<()> { "2025-02-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = init_mcp(codex_home.path()).await?; let ThreadListResponse { data, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; @@ -1251,7 +1287,7 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> { "2025-02-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let id_b = create_fake_rollout( codex_home.path(), @@ -1259,7 +1295,7 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> { "2025-02-01T11:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; let updated_at = "2025-02-03T00:00:00Z"; @@ -1276,12 +1312,12 @@ async fn thread_list_updated_at_tie_breaks_by_uuid() -> Result<()> { let ThreadListResponse { data, .. } = list_threads_with_sort( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(ThreadSortKey::UpdatedAt), - None, + /*archived*/ None, ) .await?; @@ -1305,7 +1341,7 @@ async fn thread_list_updated_at_uses_mtime() -> Result<()> { "2025-02-01T10:00:00Z", "Hello", Some("mock_provider"), - None, + /*git_info*/ None, )?; set_rollout_mtime( @@ -1317,12 +1353,12 @@ async fn thread_list_updated_at_uses_mtime() -> Result<()> { let ThreadListResponse { data, .. } = list_threads_with_sort( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(ThreadSortKey::UpdatedAt), - None, + /*archived*/ None, ) .await?; @@ -1351,7 +1387,7 @@ async fn thread_list_archived_filter() -> Result<()> { "2025-03-01T10:00:00Z", "Active", Some("mock_provider"), - None, + /*git_info*/ None, )?; let archived_id = create_fake_rollout( codex_home.path(), @@ -1359,7 +1395,7 @@ async fn thread_list_archived_filter() -> Result<()> { "2025-03-01T09:00:00Z", "Archived", Some("mock_provider"), - None, + /*git_info*/ None, )?; let archived_dir = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR); @@ -1376,11 +1412,11 @@ async fn thread_list_archived_filter() -> Result<()> { let ThreadListResponse { data, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, - None, + /*source_kinds*/ None, + /*archived*/ None, ) .await?; assert_eq!(data.len(), 1); @@ -1388,10 +1424,10 @@ async fn thread_list_archived_filter() -> Result<()> { let ThreadListResponse { data, .. } = list_threads( &mut mcp, - None, + /*cursor*/ None, Some(10), Some(vec!["mock_provider".to_string()]), - None, + /*source_kinds*/ None, Some(true), ) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs index 680e70f2d8..9ce233f948 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs @@ -184,7 +184,7 @@ async fn thread_metadata_update_repairs_missing_sqlite_row_for_stored_thread() - "2025-01-05T12:00:00Z", preview, Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -237,7 +237,7 @@ async fn thread_metadata_update_repairs_loaded_thread_without_resetting_summary( "2025-01-06T08:30:00Z", preview, Some("mock_provider"), - None, + /*git_info*/ None, )?; let thread_uuid = ThreadId::from_string(&thread_id)?; let rollout_path = rollout_path(codex_home.path(), "2025-01-06T08-30-00", &thread_id); @@ -245,10 +245,10 @@ async fn thread_metadata_update_repairs_loaded_thread_without_resetting_summary( Some(&state_db), rollout_path.as_path(), "mock_provider", - None, + /*builder*/ None, &[], - None, - None, + /*archived_only*/ None, + /*new_thread_memory_mode*/ None, ) .await; @@ -317,7 +317,7 @@ async fn thread_metadata_update_repairs_missing_sqlite_row_for_archived_thread() "2025-01-06T08:30:00Z", preview, Some("mock_provider"), - None, + /*git_info*/ None, )?; let archived_dir = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR); @@ -430,7 +430,9 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> { async fn init_state_db(codex_home: &Path) -> Result> { let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?; - state_db.mark_backfill_complete(None).await?; + state_db + .mark_backfill_complete(/*last_watermark*/ None) + .await?; Ok(state_db) } diff --git a/codex-rs/app-server/tests/suite/v2/thread_name_websocket.rs b/codex-rs/app-server/tests/suite/v2/thread_name_websocket.rs index a05518fc3f..4ec7344d6c 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_name_websocket.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_name_websocket.rs @@ -43,14 +43,14 @@ async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> { send_request( &mut ws1, "thread/resume", - 10, + /*id*/ 10, Some(serde_json::to_value(ThreadResumeParams { thread_id: conversation_id.clone(), ..Default::default() })?), ) .await?; - let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, 10).await?; + let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, /*id*/ 10).await?; let resume: ThreadResumeResponse = to_response::(resume_resp)?; assert_eq!(resume.thread.id, conversation_id); @@ -58,15 +58,19 @@ async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> { send_request( &mut ws1, "thread/name/set", - 11, + /*id*/ 11, Some(serde_json::to_value(ThreadSetNameParams { thread_id: conversation_id.clone(), name: renamed.to_string(), })?), ) .await?; - let (rename_resp, ws1_notification) = - read_response_and_notification_for_method(&mut ws1, 11, "thread/name/updated").await?; + let (rename_resp, ws1_notification) = read_response_and_notification_for_method( + &mut ws1, + /*id*/ 11, + "thread/name/updated", + ) + .await?; let _: ThreadSetNameResponse = to_response::(rename_resp)?; assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?; @@ -105,15 +109,19 @@ async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> { send_request( &mut ws1, "thread/name/set", - 20, + /*id*/ 20, Some(serde_json::to_value(ThreadSetNameParams { thread_id: conversation_id.clone(), name: renamed.to_string(), })?), ) .await?; - let (rename_resp, ws1_notification) = - read_response_and_notification_for_method(&mut ws1, 20, "thread/name/updated").await?; + let (rename_resp, ws1_notification) = read_response_and_notification_for_method( + &mut ws1, + /*id*/ 20, + "thread/name/updated", + ) + .await?; let _: ThreadSetNameResponse = to_response::(rename_resp)?; assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?; @@ -135,11 +143,11 @@ async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> { } async fn initialize_both_clients(ws1: &mut WsClient, ws2: &mut WsClient) -> Result<()> { - send_initialize_request(ws1, 1, "ws_client_one").await?; - timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, 1)).await??; + send_initialize_request(ws1, /*id*/ 1, "ws_client_one").await?; + timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, /*id*/ 1)).await??; - send_initialize_request(ws2, 2, "ws_client_two").await?; - timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, 2)).await??; + send_initialize_request(ws2, /*id*/ 2, "ws_client_two").await?; + timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, /*id*/ 2)).await??; Ok(()) } @@ -151,7 +159,7 @@ fn create_rollout(codex_home: &std::path::Path, filename_ts: &str) -> Result Result<()> { .map(|elem| serde_json::to_value(elem).expect("serialize text element")) .collect(), Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -112,7 +112,7 @@ async fn thread_read_can_include_turns() -> Result<()> { .map(|elem| serde_json::to_value(elem).expect("serialize text element")) .collect(), Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -215,7 +215,7 @@ async fn thread_name_set_is_reflected_in_read_list_and_resume() -> Result<()> { preview, vec![], Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 4443abd6ec..564de50d3a 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -171,7 +171,7 @@ async fn thread_resume_returns_rollout_history() -> Result<()> { .map(|elem| serde_json::to_value(elem).expect("serialize text element")) .collect(), Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -368,7 +368,9 @@ stream_max_retries = 0 )?; let state_db = StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; - state_db.mark_backfill_complete(None).await?; + state_db + .mark_backfill_complete(/*last_watermark*/ None) + .await?; let mut mcp = McpProcess::new(codex_home.path()).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; @@ -429,7 +431,7 @@ async fn thread_resume_and_read_interrupt_incomplete_rollout_turn_when_thread_is "Saved user message", Vec::new(), Some("mock_provider"), - None, + /*git_info*/ None, )?; let rollout_file_path = rollout_path(codex_home.path(), filename_ts, &conversation_id); let persisted_rollout = std::fs::read_to_string(&rollout_file_path)?; @@ -1022,7 +1024,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call-1", )?, @@ -1143,7 +1145,7 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R primary.read_stream_until_notification_message("turn/completed"), ) .await??; - wait_for_responses_request_count(&server, 3).await?; + wait_for_responses_request_count(&server, /*expected_count*/ 3).await?; Ok(()) } @@ -1309,7 +1311,7 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result< primary.read_stream_until_notification_message("turn/completed"), ) .await??; - wait_for_responses_request_count(&server, 3).await?; + wait_for_responses_request_count(&server, /*expected_count*/ 3).await?; Ok(()) } @@ -1465,7 +1467,7 @@ async fn thread_resume_surfaces_cloud_requirements_load_errors() -> Result<()> { "Saved user message", Vec::new(), Some("mock_provider"), - None, + /*git_info*/ None, )?; let refresh_token_url = format!("{}/oauth/token", server.uri()); let mut mcp = McpProcess::new_with_env( @@ -1933,7 +1935,7 @@ fn setup_rollout_fixture(codex_home: &Path, server_uri: &str) -> Result .await??; let _: ThreadShellCommandResponse = to_response::(shell_resp)?; - let started = wait_for_command_execution_started(&mut mcp, None).await?; + let started = wait_for_command_execution_started(&mut mcp, /*expected_id*/ None).await?; let ThreadItem::CommandExecution { id, source, status, .. } = &started.item @@ -167,7 +167,7 @@ async fn thread_shell_command_uses_existing_active_turn() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call-approve", )?, @@ -345,7 +345,7 @@ async fn wait_for_command_execution_started_by_source( expected_source: CommandExecutionSource, ) -> Result { loop { - let started = wait_for_command_execution_started(mcp, None).await?; + let started = wait_for_command_execution_started(mcp, /*expected_id*/ None).await?; let ThreadItem::CommandExecution { source, .. } = &started.item else { continue; }; diff --git a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs index 6427ae7ce0..5808f0fe79 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_unsubscribe.rs @@ -219,7 +219,7 @@ async fn thread_unsubscribe_during_turn_interrupts_turn_and_emits_thread_closed( wait_for_responses_request_count_to_stabilize( &server, - 1, + /*expected_count*/ 1, std::time::Duration::from_millis(200), ) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 8d7ca02613..3155a4be9c 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -888,7 +888,7 @@ async fn turn_start_uses_migrated_pragmatic_personality_without_override_v2() -> "2025-01-01T00:00:00Z", "history user message", Some("mock_provider"), - None, + /*git_info*/ None, )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -1031,7 +1031,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call1", )?, @@ -1042,7 +1042,7 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call2", )?, @@ -1186,7 +1186,7 @@ async fn turn_start_exec_approval_decline_v2() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call-decline", )?, @@ -1331,14 +1331,14 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> { let responses = vec![ create_shell_command_sse_response( vec!["echo".to_string(), "first".to_string(), "turn".to_string()], - None, + /*workdir*/ None, Some(5000), "call-first", )?, create_final_assistant_message_sse_response("done first")?, create_shell_command_sse_response( vec!["echo".to_string(), "second".to_string(), "turn".to_string()], - None, + /*workdir*/ None, Some(5000), "call-second", )?, diff --git a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs index c8ae882e23..105ae54542 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs @@ -71,7 +71,7 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> { format!("while [ ! -f '{release_marker_escaped}' ]; do sleep 0.01; done"); let response = create_shell_command_sse_response( vec!["/bin/sh".to_string(), "-c".to_string(), wait_for_interrupt], - None, + /*workdir*/ None, Some(5000), "call-zsh-fork", )?; @@ -197,7 +197,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call-zsh-fork-decline", )?, @@ -332,7 +332,7 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> { "-c".to_string(), "print(42)".to_string(), ], - None, + /*workdir*/ None, Some(5000), "call-zsh-fork-cancel", )?]; @@ -805,7 +805,7 @@ fn find_test_zsh_path() -> Result> { ); return Ok(None); } - match core_test_support::fetch_dotslash_file(&dotslash_zsh, None) { + match core_test_support::fetch_dotslash_file(&dotslash_zsh, /*dotslash_cache*/ None) { Ok(path) => return Ok(Some(path)), Err(error) => { eprintln!("failed to fetch vendored zsh via dotslash: {error:#}"); diff --git a/codex-rs/app-server/tests/suite/v2/windows_sandbox_setup.rs b/codex-rs/app-server/tests/suite/v2/windows_sandbox_setup.rs index b3cbfdf325..a0466a459b 100644 --- a/codex-rs/app-server/tests/suite/v2/windows_sandbox_setup.rs +++ b/codex-rs/app-server/tests/suite/v2/windows_sandbox_setup.rs @@ -26,7 +26,7 @@ async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<( codex_home.path(), &server.uri(), &BTreeMap::new(), - 500_000, + /*auto_compact_limit*/ 500_000, Some(false), "mock_provider", "compact prompt", diff --git a/codex-rs/apply-patch/src/invocation.rs b/codex-rs/apply-patch/src/invocation.rs index 7623aef80d..519671798d 100644 --- a/codex-rs/apply-patch/src/invocation.rs +++ b/codex-rs/apply-patch/src/invocation.rs @@ -524,14 +524,14 @@ mod tests { #[test] fn test_heredoc() { - assert_match(&heredoc_script(""), None); + assert_match(&heredoc_script(""), /*expected_workdir*/ None); } #[test] fn test_heredoc_non_login_shell() { let script = heredoc_script(""); let args = strs_to_strings(&["bash", "-c", &script]); - assert_match_args(args, None); + assert_match_args(args, /*expected_workdir*/ None); } #[test] @@ -565,17 +565,20 @@ PATCH"#, #[test] fn test_powershell_heredoc() { let script = heredoc_script(""); - assert_match_args(args_powershell(&script), None); + assert_match_args(args_powershell(&script), /*expected_workdir*/ None); } #[test] fn test_powershell_heredoc_no_profile() { let script = heredoc_script(""); - assert_match_args(args_powershell_no_profile(&script), None); + assert_match_args( + args_powershell_no_profile(&script), + /*expected_workdir*/ None, + ); } #[test] fn test_pwsh_heredoc() { let script = heredoc_script(""); - assert_match_args(args_pwsh(&script), None); + assert_match_args(args_pwsh(&script), /*expected_workdir*/ None); } #[test] diff --git a/codex-rs/apply-patch/src/parser.rs b/codex-rs/apply-patch/src/parser.rs index 8785b38519..274a45497a 100644 --- a/codex-rs/apply-patch/src/parser.rs +++ b/codex-rs/apply-patch/src/parser.rs @@ -672,7 +672,7 @@ fn test_parse_patch_lenient() { #[test] fn test_parse_one_hunk() { assert_eq!( - parse_one_hunk(&["bad"], 234), + parse_one_hunk(&["bad"], /*line_number*/ 234), Err(InvalidHunkError { message: "'bad' is not a valid hunk header. \ Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'".to_string(), @@ -685,7 +685,11 @@ fn test_parse_one_hunk() { #[test] fn test_update_file_chunk() { assert_eq!( - parse_update_file_chunk(&["bad"], 123, false), + parse_update_file_chunk( + &["bad"], + /*line_number*/ 123, + /*allow_missing_context*/ false + ), Err(InvalidHunkError { message: "Expected update hunk to start with a @@ context marker, got: 'bad'" .to_string(), @@ -693,14 +697,18 @@ fn test_update_file_chunk() { }) ); assert_eq!( - parse_update_file_chunk(&["@@"], 123, false), + parse_update_file_chunk( + &["@@"], + /*line_number*/ 123, + /*allow_missing_context*/ false + ), Err(InvalidHunkError { message: "Update hunk does not contain any lines".to_string(), line_number: 124 }) ); assert_eq!( - parse_update_file_chunk(&["@@", "bad"], 123, false), + parse_update_file_chunk(&["@@", "bad"], /*line_number*/ 123, /*allow_missing_context*/ false), Err(InvalidHunkError { message: "Unexpected line found in update hunk: 'bad'. \ Every line should start with ' ' (context line), '+' (added line), or '-' (removed line)".to_string(), @@ -708,7 +716,11 @@ fn test_update_file_chunk() { }) ); assert_eq!( - parse_update_file_chunk(&["@@", "*** End of File"], 123, false), + parse_update_file_chunk( + &["@@", "*** End of File"], + /*line_number*/ 123, + /*allow_missing_context*/ false + ), Err(InvalidHunkError { message: "Update hunk does not contain any lines".to_string(), line_number: 124 @@ -725,8 +737,8 @@ fn test_update_file_chunk() { " context2", "*** End Patch", ], - 123, - false + /*line_number*/ 123, + /*allow_missing_context*/ false ), Ok(( (UpdateFileChunk { @@ -749,7 +761,11 @@ fn test_update_file_chunk() { )) ); assert_eq!( - parse_update_file_chunk(&["@@", "+line", "*** End of File"], 123, false), + parse_update_file_chunk( + &["@@", "+line", "*** End of File"], + /*line_number*/ 123, + /*allow_missing_context*/ false + ), Ok(( (UpdateFileChunk { change_context: None, diff --git a/codex-rs/apply-patch/src/seek_sequence.rs b/codex-rs/apply-patch/src/seek_sequence.rs index b005b08c75..3555963120 100644 --- a/codex-rs/apply-patch/src/seek_sequence.rs +++ b/codex-rs/apply-patch/src/seek_sequence.rs @@ -122,7 +122,10 @@ mod tests { fn test_exact_match_finds_sequence() { let lines = to_vec(&["foo", "bar", "baz"]); let pattern = to_vec(&["bar", "baz"]); - assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(1)); + assert_eq!( + seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false), + Some(1) + ); } #[test] @@ -130,7 +133,10 @@ mod tests { let lines = to_vec(&["foo ", "bar\t\t"]); // Pattern omits trailing whitespace. let pattern = to_vec(&["foo", "bar"]); - assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(0)); + assert_eq!( + seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false), + Some(0) + ); } #[test] @@ -138,7 +144,10 @@ mod tests { let lines = to_vec(&[" foo ", " bar\t"]); // Pattern omits any additional whitespace. let pattern = to_vec(&["foo", "bar"]); - assert_eq!(seek_sequence(&lines, &pattern, 0, false), Some(0)); + assert_eq!( + seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false), + Some(0) + ); } #[test] @@ -146,6 +155,9 @@ mod tests { let lines = to_vec(&["just one line"]); let pattern = to_vec(&["too", "many", "lines"]); // Should not panic – must return None when pattern cannot possibly fit. - assert_eq!(seek_sequence(&lines, &pattern, 0, false), None); + assert_eq!( + seek_sequence(&lines, &pattern, /*start*/ 0, /*eof*/ false), + None + ); } } diff --git a/codex-rs/backend-client/src/client.rs b/codex-rs/backend-client/src/client.rs index e7fe145987..cb1e003ff2 100644 --- a/codex-rs/backend-client/src/client.rs +++ b/codex-rs/backend-client/src/client.rs @@ -474,7 +474,13 @@ impl Client { crate::types::PlanType::Plus => AccountPlanType::Plus, crate::types::PlanType::Pro => AccountPlanType::Pro, crate::types::PlanType::Team => AccountPlanType::Team, + crate::types::PlanType::SelfServeBusinessUsageBased => { + AccountPlanType::SelfServeBusinessUsageBased + } crate::types::PlanType::Business => AccountPlanType::Business, + crate::types::PlanType::EnterpriseCbpUsageBased => { + AccountPlanType::EnterpriseCbpUsageBased + } crate::types::PlanType::Enterprise => AccountPlanType::Enterprise, crate::types::PlanType::Edu | crate::types::PlanType::Education => AccountPlanType::Edu, crate::types::PlanType::Guest @@ -499,6 +505,18 @@ mod tests { use super::*; use pretty_assertions::assert_eq; + #[test] + fn map_plan_type_supports_usage_based_business_variants() { + assert_eq!( + Client::map_plan_type(crate::types::PlanType::SelfServeBusinessUsageBased), + AccountPlanType::SelfServeBusinessUsageBased + ); + assert_eq!( + Client::map_plan_type(crate::types::PlanType::EnterpriseCbpUsageBased), + AccountPlanType::EnterpriseCbpUsageBased + ); + } + #[test] fn usage_payload_maps_primary_and_additional_rate_limits() { let payload = RateLimitStatusPayload { diff --git a/codex-rs/chatgpt/src/connectors.rs b/codex-rs/chatgpt/src/connectors.rs index 3c2b2fe4ef..aa13d70d47 100644 --- a/codex-rs/chatgpt/src/connectors.rs +++ b/codex-rs/chatgpt/src/connectors.rs @@ -245,9 +245,9 @@ mod tests { let merged = merge_connectors_with_accessible( vec![app("alpha")], vec![app("alpha"), app("beta")], - true, + /*all_connectors_loaded*/ true, ); - assert_eq!(merged, vec![merged_app("alpha", true)]); + assert_eq!(merged, vec![merged_app("alpha", /*is_accessible*/ true)]); } #[test] @@ -255,11 +255,14 @@ mod tests { let merged = merge_connectors_with_accessible( vec![app("alpha")], vec![app("alpha"), app("beta")], - false, + /*all_connectors_loaded*/ false, ); assert_eq!( merged, - vec![merged_app("alpha", true), merged_app("beta", true)] + vec![ + merged_app("alpha", /*is_accessible*/ true), + merged_app("beta", /*is_accessible*/ true) + ] ); } @@ -272,7 +275,10 @@ mod tests { AppConnectorId("gmail".to_string()), ], ); - assert_eq!(connectors, vec![app("alpha"), merged_app("gmail", false)]); + assert_eq!( + connectors, + vec![app("alpha"), merged_app("gmail", /*is_accessible*/ false)] + ); } #[test] diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index 7e703efe10..6788d5f9e7 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -41,7 +41,6 @@ codex-state = { workspace = true } codex-stdio-to-uds = { workspace = true } codex-terminal-detection = { workspace = true } codex-tui = { workspace = true } -codex-tui-app-server = { workspace = true } libc = { workspace = true } owo-colors = { workspace = true } regex-lite = { workspace = true } diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index f64c45c73f..adb205eefa 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -498,7 +498,7 @@ mod tests { let legacy_config = build_debug_sandbox_config( Vec::new(), ConfigOverrides { - sandbox_mode: Some(create_sandbox_mode(false)), + sandbox_mode: Some(create_sandbox_mode(/*full_auto*/ false)), ..Default::default() }, Some(codex_home_path.clone()), @@ -507,8 +507,8 @@ mod tests { let config = load_debug_sandbox_config_with_codex_home( Vec::new(), - None, - false, + /*codex_linux_sandbox_exe*/ None, + /*full_auto*/ false, Some(codex_home_path), ) .await?; @@ -541,8 +541,8 @@ mod tests { let err = load_debug_sandbox_config_with_codex_home( Vec::new(), - None, - true, + /*codex_linux_sandbox_exe*/ None, + /*full_auto*/ true, Some(codex_home.path().to_path_buf()), ) .await diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 1f962bf2f4..fe035ecae3 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -525,7 +525,7 @@ struct FeatureToggles { #[derive(Debug, Default, Parser, Clone)] struct InteractiveRemoteOptions { - /// Connect the app-server-backed TUI to a remote app server websocket endpoint. + /// Connect the TUI to a remote app server websocket endpoint. /// /// Accepted forms: `ws://host:port` or `wss://host:port`. #[arg(long = "remote", value_name = "ADDR")] @@ -1226,10 +1226,9 @@ async fn run_interactive_tui( } } - let use_app_server_tui = codex_tui::should_use_app_server_tui(&interactive).await?; let normalized_remote = remote .as_deref() - .map(codex_tui_app_server::normalize_remote_addr) + .map(codex_tui::normalize_remote_addr) .transpose() .map_err(std::io::Error::other)?; if remote_auth_token_env.is_some() && normalized_remote.is_none() { @@ -1237,93 +1236,19 @@ async fn run_interactive_tui( "`--remote-auth-token-env` requires `--remote`.", )); } - if normalized_remote.is_some() && !use_app_server_tui { - return Ok(AppExitInfo::fatal( - "`--remote` requires the `tui_app_server` feature flag to be enabled.", - )); - } - if use_app_server_tui { - let remote_auth_token = remote_auth_token_env - .as_deref() - .map(read_remote_auth_token_from_env_var) - .transpose() - .map_err(std::io::Error::other)?; - codex_tui_app_server::run_main( - into_app_server_tui_cli(interactive), - arg0_paths, - codex_core::config_loader::LoaderOverrides::default(), - normalized_remote, - remote_auth_token, - ) - .await - .map(into_legacy_app_exit_info) - } else { - codex_tui::run_main( - interactive, - arg0_paths, - codex_core::config_loader::LoaderOverrides::default(), - ) - .await - } -} - -fn into_app_server_tui_cli(cli: TuiCli) -> codex_tui_app_server::Cli { - codex_tui_app_server::Cli { - prompt: cli.prompt, - images: cli.images, - resume_picker: cli.resume_picker, - resume_last: cli.resume_last, - resume_session_id: cli.resume_session_id, - resume_show_all: cli.resume_show_all, - fork_picker: cli.fork_picker, - fork_last: cli.fork_last, - fork_session_id: cli.fork_session_id, - fork_show_all: cli.fork_show_all, - model: cli.model, - oss: cli.oss, - oss_provider: cli.oss_provider, - config_profile: cli.config_profile, - sandbox_mode: cli.sandbox_mode, - approval_policy: cli.approval_policy, - full_auto: cli.full_auto, - dangerously_bypass_approvals_and_sandbox: cli.dangerously_bypass_approvals_and_sandbox, - cwd: cli.cwd, - web_search: cli.web_search, - add_dir: cli.add_dir, - no_alt_screen: cli.no_alt_screen, - config_overrides: cli.config_overrides, - } -} - -fn into_legacy_update_action( - action: codex_tui_app_server::update_action::UpdateAction, -) -> UpdateAction { - match action { - codex_tui_app_server::update_action::UpdateAction::NpmGlobalLatest => { - UpdateAction::NpmGlobalLatest - } - codex_tui_app_server::update_action::UpdateAction::BunGlobalLatest => { - UpdateAction::BunGlobalLatest - } - codex_tui_app_server::update_action::UpdateAction::BrewUpgrade => UpdateAction::BrewUpgrade, - } -} - -fn into_legacy_exit_reason(reason: codex_tui_app_server::ExitReason) -> ExitReason { - match reason { - codex_tui_app_server::ExitReason::UserRequested => ExitReason::UserRequested, - codex_tui_app_server::ExitReason::Fatal(message) => ExitReason::Fatal(message), - } -} - -fn into_legacy_app_exit_info(exit_info: codex_tui_app_server::AppExitInfo) -> AppExitInfo { - AppExitInfo { - token_usage: exit_info.token_usage, - thread_id: exit_info.thread_id, - thread_name: exit_info.thread_name, - update_action: exit_info.update_action.map(into_legacy_update_action), - exit_reason: into_legacy_exit_reason(exit_info.exit_reason), - } + let remote_auth_token = remote_auth_token_env + .as_deref() + .map(read_remote_auth_token_from_env_var) + .transpose() + .map_err(std::io::Error::other)?; + codex_tui::run_main( + interactive, + arg0_paths, + codex_core::config_loader::LoaderOverrides::default(), + normalized_remote, + remote_auth_token, + ) + .await } fn confirm(prompt: &str) -> std::io::Result { @@ -1588,14 +1513,17 @@ mod tests { update_action: None, exit_reason: ExitReason::UserRequested, }; - let lines = format_exit_messages(exit_info, false); + let lines = format_exit_messages(exit_info, /*color_enabled*/ false); assert!(lines.is_empty()); } #[test] fn format_exit_messages_includes_resume_hint_without_color() { - let exit_info = sample_exit_info(Some("123e4567-e89b-12d3-a456-426614174000"), None); - let lines = format_exit_messages(exit_info, false); + let exit_info = sample_exit_info( + Some("123e4567-e89b-12d3-a456-426614174000"), + /*thread_name*/ None, + ); + let lines = format_exit_messages(exit_info, /*color_enabled*/ false); assert_eq!( lines, vec![ @@ -1608,8 +1536,11 @@ mod tests { #[test] fn format_exit_messages_applies_color_when_enabled() { - let exit_info = sample_exit_info(Some("123e4567-e89b-12d3-a456-426614174000"), None); - let lines = format_exit_messages(exit_info, true); + let exit_info = sample_exit_info( + Some("123e4567-e89b-12d3-a456-426614174000"), + /*thread_name*/ None, + ); + let lines = format_exit_messages(exit_info, /*color_enabled*/ true); assert_eq!(lines.len(), 2); assert!(lines[1].contains("\u{1b}[36m")); } @@ -1620,7 +1551,7 @@ mod tests { Some("123e4567-e89b-12d3-a456-426614174000"), Some("my-thread"), ); - let lines = format_exit_messages(exit_info, false); + let lines = format_exit_messages(exit_info, /*color_enabled*/ false); assert_eq!( lines, vec![ @@ -1846,8 +1777,12 @@ mod tests { #[test] fn reject_remote_mode_for_non_interactive_subcommands() { - let err = reject_remote_mode_for_subcommand(Some("127.0.0.1:4500"), None, "exec") - .expect_err("non-interactive subcommands should reject --remote"); + let err = reject_remote_mode_for_subcommand( + Some("127.0.0.1:4500"), + /*remote_auth_token_env*/ None, + "exec", + ) + .expect_err("non-interactive subcommands should reject --remote"); assert!( err.to_string() .contains("only supported for interactive TUI commands") @@ -1856,8 +1791,12 @@ mod tests { #[test] fn reject_remote_auth_token_env_for_non_interactive_subcommands() { - let err = reject_remote_mode_for_subcommand(None, Some("CODEX_REMOTE_AUTH_TOKEN"), "exec") - .expect_err("non-interactive subcommands should reject --remote-auth-token-env"); + let err = reject_remote_mode_for_subcommand( + /*remote*/ None, + Some("CODEX_REMOTE_AUTH_TOKEN"), + "exec", + ) + .expect_err("non-interactive subcommands should reject --remote-auth-token-env"); assert!( err.to_string() .contains("only supported for interactive TUI commands") @@ -1871,7 +1810,7 @@ mod tests { out_dir: PathBuf::from("/tmp/out"), }); let err = reject_remote_mode_for_app_server_subcommand( - None, + /*remote*/ None, Some("CODEX_REMOTE_AUTH_TOKEN"), Some(&subcommand), ) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 12e62d880f..b5080ea63f 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -327,11 +327,11 @@ impl CloudRequirementsService { let Some(auth) = self.auth_manager.auth().await else { return Ok(None); }; + let Some(plan_type) = auth.account_plan_type() else { + return Ok(None); + }; if !auth.is_chatgpt_auth() - || !matches!( - auth.account_plan_type(), - Some(PlanType::Business | PlanType::Enterprise) - ) + || !(plan_type.is_business_like() || matches!(plan_type, PlanType::Enterprise)) { return Ok(None); } @@ -547,11 +547,11 @@ impl CloudRequirementsService { let Some(auth) = self.auth_manager.auth().await else { return false; }; + let Some(plan_type) = auth.account_plan_type() else { + return false; + }; if !auth.is_chatgpt_auth() - || !matches!( - auth.account_plan_type(), - Some(PlanType::Business | PlanType::Enterprise) - ) + || !(plan_type.is_business_like() || matches!(plan_type, PlanType::Enterprise)) { return false; } @@ -848,7 +848,7 @@ mod tests { write_auth_json(tmp.path(), auth_json).expect("write auth"); Arc::new(AuthManager::new( tmp.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )) } @@ -872,7 +872,7 @@ mod tests { .expect("write auth"); Arc::new(AuthManager::new( tmp.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )) } @@ -909,7 +909,7 @@ mod tests { access_token, refresh_token, last_refresh, - None, + /*auth_mode*/ None, ) } @@ -980,7 +980,7 @@ mod tests { ManagedAuthContext { manager: Arc::new(AuthManager::new( home.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )), _home: home, @@ -1125,6 +1125,20 @@ mod tests { assert_eq!(result, Ok(None)); } + #[tokio::test] + async fn fetch_cloud_requirements_skips_team_like_usage_based_plan() { + let codex_home = tempdir().expect("tempdir"); + let service = CloudRequirementsService::new( + auth_manager_with_plan("self_serve_business_usage_based"), + Arc::new(StaticFetcher { + contents: Some("allowed_approval_policies = [\"never\"]".to_string()), + }), + codex_home.path().to_path_buf(), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + assert_eq!(service.fetch().await, Ok(None)); + } + #[tokio::test] async fn fetch_cloud_requirements_allows_business_plan() { let codex_home = tempdir().expect("tempdir"); @@ -1153,6 +1167,34 @@ mod tests { ); } + #[tokio::test] + async fn fetch_cloud_requirements_allows_business_like_usage_based_plan() { + let codex_home = tempdir().expect("tempdir"); + let service = CloudRequirementsService::new( + auth_manager_with_plan("enterprise_cbp_usage_based"), + Arc::new(StaticFetcher { + contents: Some("allowed_approval_policies = [\"never\"]".to_string()), + }), + codex_home.path().to_path_buf(), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + assert_eq!( + service.fetch().await, + Ok(Some(ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::Never]), + allowed_sandbox_modes: None, + allowed_web_search_modes: None, + guardian_developer_instructions: None, + feature_requirements: None, + mcp_servers: None, + apps: None, + rules: None, + enforce_residency: None, + network: None, + })) + ); + } + #[tokio::test] async fn fetch_cloud_requirements_allows_hc_plan_as_enterprise() { let codex_home = tempdir().expect("tempdir"); @@ -1183,7 +1225,7 @@ mod tests { #[tokio::test] async fn fetch_cloud_requirements_handles_missing_contents() { - let result = parse_for_fetch(None); + let result = parse_for_fetch(/*contents*/ None); assert!(result.is_none()); } @@ -1327,7 +1369,7 @@ enabled = false .expect("write initial auth"); let auth_manager = Arc::new(AuthManager::new( auth_home.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )); @@ -1396,7 +1438,7 @@ enabled = false .expect("write initial auth"); let auth_manager = Arc::new(AuthManager::new( auth_home.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )); @@ -1523,7 +1565,7 @@ enabled = false .expect("write auth"); let auth_manager = Arc::new(AuthManager::new( auth_home.path().to_path_buf(), - false, + /*enable_codex_api_key_env*/ false, AuthCredentialsStoreMode::File, )); @@ -1615,7 +1657,11 @@ enabled = false async fn fetch_cloud_requirements_writes_cache_when_identity_is_incomplete() { let codex_home = tempdir().expect("tempdir"); let service = CloudRequirementsService::new( - auth_manager_with_plan_and_identity("business", None, Some("account-12345")), + auth_manager_with_plan_and_identity( + "business", + /*chatgpt_user_id*/ None, + Some("account-12345"), + ), Arc::new(StaticFetcher { contents: Some("allowed_approval_policies = [\"never\"]".to_string()), }), @@ -1667,7 +1713,11 @@ enabled = false "allowed_approval_policies = [\"on-request\"]".to_string(), ))])); let service = CloudRequirementsService::new( - auth_manager_with_plan_and_identity("business", None, Some("account-12345")), + auth_manager_with_plan_and_identity( + "business", + /*chatgpt_user_id*/ None, + Some("account-12345"), + ), fetcher.clone(), codex_home.path().to_path_buf(), CLOUD_REQUIREMENTS_TIMEOUT, diff --git a/codex-rs/cloud-tasks/Cargo.toml b/codex-rs/cloud-tasks/Cargo.toml index 4ba9968fb0..7587b341b9 100644 --- a/codex-rs/cloud-tasks/Cargo.toml +++ b/codex-rs/cloud-tasks/Cargo.toml @@ -24,7 +24,7 @@ codex-client = { workspace = true } codex-core = { path = "../core" } codex-git-utils = { workspace = true } codex-login = { path = "../login" } -codex-tui = { path = "../tui" } +codex-tui = { workspace = true } codex-utils-cli = { workspace = true } crossterm = { workspace = true, features = ["event-stream"] } ratatui = { workspace = true } diff --git a/codex-rs/cloud-tasks/src/app.rs b/codex-rs/cloud-tasks/src/app.rs index 4a8ca9900a..aa02be97f1 100644 --- a/codex-rs/cloud-tasks/src/app.rs +++ b/codex-rs/cloud-tasks/src/app.rs @@ -408,7 +408,7 @@ mod tests { &self, id: TaskId, ) -> codex_cloud_tasks_client::Result { - self.list_tasks(None, None, None) + self.list_tasks(/*env*/ None, /*limit*/ None, /*cursor*/ None) .await? .tasks .into_iter() @@ -497,7 +497,7 @@ mod tests { let backend = FakeBackend { by_env }; // Act + Assert - let root = load_tasks(&backend, None).await.unwrap(); + let root = load_tasks(&backend, /*env*/ None).await.unwrap(); assert_eq!(root.len(), 2); assert_eq!(root[0].title, "root-1"); diff --git a/codex-rs/cloud-tasks/src/lib.rs b/codex-rs/cloud-tasks/src/lib.rs index 7766c1eb8b..f11eca38f5 100644 --- a/codex-rs/cloud-tasks/src/lib.rs +++ b/codex-rs/cloud-tasks/src/lib.rs @@ -930,7 +930,8 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option) -> an if let Some(page) = app.new_task.as_mut() { if page.composer.flush_paste_burst_if_due() { needs_redraw = true; } if page.composer.is_in_paste_burst() { - let _ = frame_tx.send(Instant::now() + codex_tui::ComposerInput::recommended_flush_delay()); + let _ = frame_tx + .send(Instant::now() + codex_tui::ComposerInput::recommended_flush_delay()); } } // Keep spinner pulsing only while loading. @@ -1491,7 +1492,9 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option) -> an _ => { if page.submitting { // Ignore input while submitting - } else if let codex_tui::ComposerAction::Submitted(text) = page.composer.input(key) { + } else if let codex_tui::ComposerAction::Submitted(text) = + page.composer.input(key) + { // Submit only if we have an env id if let Some(env) = page.env_id.clone() { append_error_log(format!( @@ -1521,7 +1524,10 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option) -> an needs_redraw = true; // If paste‑burst is active, schedule a micro‑flush frame. if page.composer.is_in_paste_burst() { - let _ = frame_tx.send(Instant::now() + codex_tui::ComposerInput::recommended_flush_delay()); + let _ = frame_tx.send( + Instant::now() + + codex_tui::ComposerInput::recommended_flush_delay(), + ); } // Always schedule an immediate redraw for key edits in the composer. let _ = frame_tx.send(Instant::now()); @@ -2168,7 +2174,7 @@ mod tests { async fn branch_override_is_used_when_provided() { let git_ref = resolve_git_ref_with_git_info( Some(&"feature/override".to_string()), - &StubGitInfo::new(None, None), + &StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None), ) .await; @@ -2179,7 +2185,7 @@ mod tests { async fn trims_override_whitespace() { let git_ref = resolve_git_ref_with_git_info( Some(&" feature/spaces ".to_string()), - &StubGitInfo::new(None, None), + &StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None), ) .await; @@ -2189,7 +2195,7 @@ mod tests { #[tokio::test] async fn prefers_current_branch_when_available() { let git_ref = resolve_git_ref_with_git_info( - None, + /*branch_override*/ None, &StubGitInfo::new( Some("default-main".to_string()), Some("feature/current".to_string()), @@ -2203,8 +2209,8 @@ mod tests { #[tokio::test] async fn falls_back_to_current_branch_when_default_is_missing() { let git_ref = resolve_git_ref_with_git_info( - None, - &StubGitInfo::new(None, Some("develop".to_string())), + /*branch_override*/ None, + &StubGitInfo::new(/*default_branch*/ None, Some("develop".to_string())), ) .await; @@ -2213,7 +2219,11 @@ mod tests { #[tokio::test] async fn falls_back_to_main_when_no_git_info_is_available() { - let git_ref = resolve_git_ref_with_git_info(None, &StubGitInfo::new(None, None)).await; + let git_ref = resolve_git_ref_with_git_info( + /*branch_override*/ None, + &StubGitInfo::new(/*default_branch*/ None, /*current_branch*/ None), + ) + .await; assert_eq!(git_ref, "main"); } @@ -2236,7 +2246,7 @@ mod tests { is_review: false, attempt_total: None, }; - let lines = format_task_status_lines(&task, now, false); + let lines = format_task_status_lines(&task, now, /*colorize*/ false); assert_eq!( lines, vec![ @@ -2261,7 +2271,7 @@ mod tests { is_review: false, attempt_total: Some(1), }; - let lines = format_task_status_lines(&task, now, false); + let lines = format_task_status_lines(&task, now, /*colorize*/ false); assert_eq!( lines, vec![ @@ -2303,7 +2313,12 @@ mod tests { attempt_total: Some(1), }, ]; - let lines = format_task_list_lines(&tasks, "https://chatgpt.com/backend-api", now, false); + let lines = format_task_list_lines( + &tasks, + "https://chatgpt.com/backend-api", + now, + /*colorize*/ false, + ); assert_eq!( lines, vec![ diff --git a/codex-rs/cloud-tasks/tests/env_filter.rs b/codex-rs/cloud-tasks/tests/env_filter.rs index 688ccd29bd..89bd2fc71c 100644 --- a/codex-rs/cloud-tasks/tests/env_filter.rs +++ b/codex-rs/cloud-tasks/tests/env_filter.rs @@ -5,23 +5,35 @@ use codex_cloud_tasks_client::MockClient; async fn mock_backend_varies_by_env() { let client = MockClient; - let root = CloudBackend::list_tasks(&client, None, None, None) - .await - .unwrap() - .tasks; + let root = CloudBackend::list_tasks( + &client, /*env*/ None, /*limit*/ None, /*cursor*/ None, + ) + .await + .unwrap() + .tasks; assert!(root.iter().any(|t| t.title.contains("Update README"))); - let a = CloudBackend::list_tasks(&client, Some("env-A"), None, None) - .await - .unwrap() - .tasks; + let a = CloudBackend::list_tasks( + &client, + Some("env-A"), + /*limit*/ None, + /*cursor*/ None, + ) + .await + .unwrap() + .tasks; assert_eq!(a.len(), 1); assert_eq!(a[0].title, "A: First"); - let b = CloudBackend::list_tasks(&client, Some("env-B"), None, None) - .await - .unwrap() - .tasks; + let b = CloudBackend::list_tasks( + &client, + Some("env-B"), + /*limit*/ None, + /*cursor*/ None, + ) + .await + .unwrap() + .tasks; assert_eq!(b.len(), 2); assert!(b[0].title.starts_with("B: ")); } diff --git a/codex-rs/code-mode/src/description.rs b/codex-rs/code-mode/src/description.rs index c875e2a1b1..3a87fef092 100644 --- a/codex-rs/code-mode/src/description.rs +++ b/codex-rs/code-mode/src/description.rs @@ -7,15 +7,17 @@ use crate::PUBLIC_TOOL_NAME; const MAX_JS_SAFE_INTEGER: u64 = (1_u64 << 53) - 1; const CODE_MODE_ONLY_PREFACE: &str = "Use `exec/wait` tool to run all other tools, do not attempt to use any other tools directly"; -const EXEC_DESCRIPTION_TEMPLATE: &str = r#"## exec -- Runs raw JavaScript in an isolated context (no Node, no file system, or network access, no console). -- Send raw JavaScript source text, not JSON, quoted strings, or markdown code fences. +const EXEC_DESCRIPTION_TEMPLATE: &str = r#"Run JavaScript code to orchestrate/compose tool calls +- Evaluates the provided JavaScript code in a fresh V8 isolate as an async module. +- All nested tools are available on the global `tools` object, for example `await tools.exec_command(...)`. Tool names are exposed as normalized JavaScript identifiers, for example `await tools.mcp__ologs__get_profile(...)`. +- Nested tool methods take either a string or an object as their input argument. +- Nested tools return either an object or a string, based on the description. +- Runs raw JavaScript -- no Node, no file system, no network access, no console. +- Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences. - You may optionally start the tool input with a first-line pragma like `// @exec: {"yield_time_ms": 10000, "max_output_tokens": 1000}`. - `yield_time_ms` asks `exec` to yield early after that many milliseconds if the script is still running. - `max_output_tokens` sets the token budget for direct `exec` results. By default the result is truncated to 10000 tokens. -- All nested tools are available on the global `tools` object, for example `await tools.exec_command(...)`. Tool names are exposed as normalized JavaScript identifiers, for example `await tools.mcp__ologs__get_profile(...)`. -- Tool methods take either string or object as parameter. -- They return either a structured value or a string based on the description above. +- When the JS code is fully evaluated, the isolate's lifetime ends and unawaited promises are silently discarded. - Global helpers: - `exit()`: Immediately ends the current script successfully (like an early return from the top level). @@ -548,8 +550,10 @@ mod tests { #[test] fn code_mode_only_description_includes_nested_tools() { - let description = - build_exec_tool_description(&[("foo".to_string(), "bar".to_string())], true); + let description = build_exec_tool_description( + &[("foo".to_string(), "bar".to_string())], + /*code_mode_only*/ true, + ); assert!(description.contains("### `foo` (`foo`)")); } } diff --git a/codex-rs/code-mode/src/service.rs b/codex-rs/code-mode/src/service.rs index 260b891d36..5b67dd17b8 100644 --- a/codex-rs/code-mode/src/service.rs +++ b/codex-rs/code-mode/src/service.rs @@ -631,7 +631,7 @@ text(JSON.stringify(returnsUndefined)); event_rx, control_rx, initial_response_tx, - 60_000, + /*initial_yield_time_ms*/ 60_000, )); event_tx.send(RuntimeEvent::Started).unwrap(); diff --git a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs b/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs index 10c72d72be..eed0e470a4 100644 --- a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs +++ b/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs @@ -967,8 +967,8 @@ mod tests { fn websocket_url_from_http_base_defaults_to_ws_path() { let url = websocket_url_from_api_url( "http://127.0.0.1:8011", - None, - None, + /*query_params*/ None, + /*model*/ None, RealtimeEventParser::V1, RealtimeSessionMode::Conversational, ) @@ -983,7 +983,7 @@ mod tests { fn websocket_url_from_ws_base_defaults_to_ws_path() { let url = websocket_url_from_api_url( "wss://example.com", - None, + /*query_params*/ None, Some("realtime-test-model"), RealtimeEventParser::V1, RealtimeSessionMode::Conversational, @@ -999,7 +999,7 @@ mod tests { fn websocket_url_from_v1_base_appends_realtime_path() { let url = websocket_url_from_api_url( "https://api.openai.com/v1", - None, + /*query_params*/ None, Some("snapshot"), RealtimeEventParser::V1, RealtimeSessionMode::Conversational, @@ -1015,7 +1015,7 @@ mod tests { fn websocket_url_from_nested_v1_base_appends_realtime_path() { let url = websocket_url_from_api_url( "https://example.com/openai/v1", - None, + /*query_params*/ None, Some("snapshot"), RealtimeEventParser::V1, RealtimeSessionMode::Conversational, @@ -1050,8 +1050,8 @@ mod tests { fn websocket_url_v1_ignores_transcription_mode() { let url = websocket_url_from_api_url( "https://example.com", - None, - None, + /*query_params*/ None, + /*model*/ None, RealtimeEventParser::V1, RealtimeSessionMode::Transcription, ) @@ -1085,8 +1085,8 @@ mod tests { fn websocket_url_omits_intent_for_realtime_v2_transcription_mode() { let url = websocket_url_from_api_url( "https://example.com", - None, - None, + /*query_params*/ None, + /*model*/ None, RealtimeEventParser::RealtimeV2, RealtimeSessionMode::Transcription, ) diff --git a/codex-rs/codex-api/src/rate_limits.rs b/codex-rs/codex-api/src/rate_limits.rs index 730f94d209..1c71dc7502 100644 --- a/codex-rs/codex-api/src/rate_limits.rs +++ b/codex-rs/codex-api/src/rate_limits.rs @@ -277,7 +277,7 @@ mod tests { HeaderValue::from_static("1704069000"), ); - let snapshot = parse_rate_limit_for_limit(&headers, None).expect("snapshot"); + let snapshot = parse_rate_limit_for_limit(&headers, /*limit_id*/ None).expect("snapshot"); assert_eq!(snapshot.limit_id.as_deref(), Some("codex")); assert_eq!(snapshot.limit_name, None); let primary = snapshot.primary.expect("primary"); diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index 696fd2953e..bf6d755060 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -515,7 +515,12 @@ mod tests { let stream = ReaderStream::new(reader).map_err(|err| TransportError::Network(err.to_string())); let (tx, mut rx) = mpsc::channel::>(16); - tokio::spawn(process_sse(Box::pin(stream), tx, idle_timeout(), None)); + tokio::spawn(process_sse( + Box::pin(stream), + tx, + idle_timeout(), + /*telemetry*/ None, + )); let mut events = Vec::new(); while let Some(ev) = rx.recv().await { @@ -541,7 +546,12 @@ mod tests { let (tx, mut rx) = mpsc::channel::>(8); let stream = ReaderStream::new(std::io::Cursor::new(body)) .map_err(|err| TransportError::Network(err.to_string())); - tokio::spawn(process_sse(Box::pin(stream), tx, idle_timeout(), None)); + tokio::spawn(process_sse( + Box::pin(stream), + tx, + idle_timeout(), + /*telemetry*/ None, + )); let mut out = Vec::new(); while let Some(ev) = rx.recv().await { @@ -695,7 +705,12 @@ mod tests { let stream: ByteStream = Box::pin(stream); let (tx, mut rx) = mpsc::channel::>(8); - tokio::spawn(process_sse(stream, tx, idle_timeout(), None)); + tokio::spawn(process_sse( + stream, + tx, + idle_timeout(), + /*telemetry*/ None, + )); let events = tokio::time::timeout(Duration::from_millis(1000), async { let mut events = Vec::new(); @@ -894,7 +909,12 @@ mod tests { bytes: Box::pin(bytes), }; - let mut stream = spawn_response_stream(stream_response, idle_timeout(), None, None); + let mut stream = spawn_response_stream( + stream_response, + idle_timeout(), + /*telemetry*/ None, + /*turn_state*/ None, + ); let event = stream .rx_event .recv() diff --git a/codex-rs/codex-api/tests/clients.rs b/codex-rs/codex-api/tests/clients.rs index 63a033dea3..ba0f416788 100644 --- a/codex-rs/codex-api/tests/clients.rs +++ b/codex-rs/codex-api/tests/clients.rs @@ -203,7 +203,12 @@ async fn responses_client_uses_responses_path() -> Result<()> { let body = serde_json::json!({ "echo": true }); let _stream = client - .stream(body, HeaderMap::new(), Compression::None, None) + .stream( + body, + HeaderMap::new(), + Compression::None, + /*turn_state*/ None, + ) .await?; let requests = state.take_stream_requests(); @@ -220,7 +225,12 @@ async fn streaming_client_adds_auth_headers() -> Result<()> { let body = serde_json::json!({ "model": "gpt-test" }); let _stream = client - .stream(body, HeaderMap::new(), Compression::None, None) + .stream( + body, + HeaderMap::new(), + Compression::None, + /*turn_state*/ None, + ) .await?; let requests = state.take_stream_requests(); diff --git a/codex-rs/codex-api/tests/models_integration.rs b/codex-rs/codex-api/tests/models_integration.rs index 4167c877dc..3ffb2496b4 100644 --- a/codex-rs/codex-api/tests/models_integration.rs +++ b/codex-rs/codex-api/tests/models_integration.rs @@ -85,7 +85,7 @@ async fn models_client_hits_models_endpoint() { availability_nux: None, apply_patch_tool_type: None, web_search_tool_type: Default::default(), - truncation_policy: TruncationPolicyConfig::bytes(10_000), + truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000), supports_parallel_tool_calls: false, supports_image_detail_original: false, context_window: Some(272_000), diff --git a/codex-rs/codex-api/tests/sse_end_to_end.rs b/codex-rs/codex-api/tests/sse_end_to_end.rs index f625fdcd9d..80972340dd 100644 --- a/codex-rs/codex-api/tests/sse_end_to_end.rs +++ b/codex-rs/codex-api/tests/sse_end_to_end.rs @@ -125,7 +125,7 @@ async fn responses_stream_parses_items_and_completed_end_to_end() -> Result<()> serde_json::json!({"echo": true}), HeaderMap::new(), Compression::None, - None, + /*turn_state*/ None, ) .await?; diff --git a/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs b/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs index 3d0492fa83..300674357a 100644 --- a/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs +++ b/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs @@ -69,8 +69,12 @@ pub enum PlanType { FreeWorkspace, #[serde(rename = "team")] Team, + #[serde(rename = "self_serve_business_usage_based")] + SelfServeBusinessUsageBased, #[serde(rename = "business")] Business, + #[serde(rename = "enterprise_cbp_usage_based")] + EnterpriseCbpUsageBased, #[serde(rename = "education")] Education, #[serde(rename = "quorum")] diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 57d762c0f1..d63fa1e8b2 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -5,6 +5,7 @@ use codex_protocol::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use serde::Deserialize; use serde::Serialize; +use serde::de::Error as _; use std::collections::BTreeMap; use std::fmt; @@ -132,7 +133,93 @@ pub struct McpServerRequirement { pub identity: McpServerIdentity, } -#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct NetworkDomainPermissionsToml { + #[serde(flatten)] + pub entries: BTreeMap, +} + +impl NetworkDomainPermissionsToml { + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn allowed_domains(&self) -> Option> { + let allowed_domains: Vec = self + .entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Allow)) + .map(|(pattern, _)| pattern.clone()) + .collect(); + (!allowed_domains.is_empty()).then_some(allowed_domains) + } + + pub fn denied_domains(&self) -> Option> { + let denied_domains: Vec = self + .entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Deny)) + .map(|(pattern, _)| pattern.clone()) + .collect(); + (!denied_domains.is_empty()).then_some(denied_domains) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "lowercase")] +pub enum NetworkDomainPermissionToml { + Allow, + Deny, +} + +impl std::fmt::Display for NetworkDomainPermissionToml { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let permission = match self { + Self::Allow => "allow", + Self::Deny => "deny", + }; + f.write_str(permission) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct NetworkUnixSocketPermissionsToml { + #[serde(flatten)] + pub entries: BTreeMap, +} + +impl NetworkUnixSocketPermissionsToml { + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn allow_unix_sockets(&self) -> Vec { + self.entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkUnixSocketPermissionToml::Allow)) + .map(|(path, _)| path.clone()) + .collect() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "lowercase")] +pub enum NetworkUnixSocketPermissionToml { + Allow, + None, +} + +impl std::fmt::Display for NetworkUnixSocketPermissionToml { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let permission = match self { + Self::Allow => "allow", + Self::None => "none", + }; + f.write_str(permission) + } +} + +#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq)] pub struct NetworkRequirementsToml { pub enabled: Option, pub http_port: Option, @@ -140,17 +227,121 @@ pub struct NetworkRequirementsToml { pub allow_upstream_proxy: Option, pub dangerously_allow_non_loopback_proxy: Option, pub dangerously_allow_all_unix_sockets: Option, - pub allowed_domains: Option>, + pub domains: Option, /// When true, only managed `allowed_domains` are respected while managed /// network enforcement is active. User allowlist entries are ignored. pub managed_allowed_domains_only: Option, - pub denied_domains: Option>, - pub allow_unix_sockets: Option>, + pub unix_sockets: Option, pub allow_local_binding: Option, } +#[derive(Deserialize)] +struct RawNetworkRequirementsToml { + enabled: Option, + http_port: Option, + socks_port: Option, + allow_upstream_proxy: Option, + dangerously_allow_non_loopback_proxy: Option, + dangerously_allow_all_unix_sockets: Option, + domains: Option, + #[serde(default)] + allowed_domains: Option>, + /// When true, only managed `allowed_domains` are respected while managed + /// network enforcement is active. User allowlist entries are ignored. + managed_allowed_domains_only: Option, + #[serde(default)] + denied_domains: Option>, + unix_sockets: Option, + #[serde(default)] + allow_unix_sockets: Option>, + allow_local_binding: Option, +} + +impl<'de> Deserialize<'de> for NetworkRequirementsToml { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let raw = RawNetworkRequirementsToml::deserialize(deserializer)?; + let RawNetworkRequirementsToml { + enabled, + http_port, + socks_port, + allow_upstream_proxy, + dangerously_allow_non_loopback_proxy, + dangerously_allow_all_unix_sockets, + domains, + allowed_domains, + managed_allowed_domains_only, + denied_domains, + unix_sockets, + allow_unix_sockets, + allow_local_binding, + } = raw; + + if domains.is_some() && (allowed_domains.is_some() || denied_domains.is_some()) { + return Err(D::Error::custom( + "`experimental_network.domains` cannot be combined with legacy `allowed_domains` or `denied_domains`", + )); + } + + if unix_sockets.is_some() && allow_unix_sockets.is_some() { + return Err(D::Error::custom( + "`experimental_network.unix_sockets` cannot be combined with legacy `allow_unix_sockets`", + )); + } + + Ok(Self { + enabled, + http_port, + socks_port, + allow_upstream_proxy, + dangerously_allow_non_loopback_proxy, + dangerously_allow_all_unix_sockets, + domains: domains + .or_else(|| legacy_domain_permissions_from_lists(allowed_domains, denied_domains)), + managed_allowed_domains_only, + unix_sockets: unix_sockets + .or_else(|| legacy_unix_socket_permissions_from_list(allow_unix_sockets)), + allow_local_binding, + }) + } +} + +/// Legacy list normalization is intentionally lossy: explicit empty legacy +/// lists are treated as unset when converted to the canonical network +/// permission shape. +fn legacy_domain_permissions_from_lists( + allowed_domains: Option>, + denied_domains: Option>, +) -> Option { + let mut entries = BTreeMap::new(); + + for pattern in allowed_domains.unwrap_or_default() { + entries.insert(pattern, NetworkDomainPermissionToml::Allow); + } + + for pattern in denied_domains.unwrap_or_default() { + entries.insert(pattern, NetworkDomainPermissionToml::Deny); + } + + (!entries.is_empty()).then_some(NetworkDomainPermissionsToml { entries }) +} + +fn legacy_unix_socket_permissions_from_list( + allow_unix_sockets: Option>, +) -> Option { + let entries = allow_unix_sockets + .unwrap_or_default() + .into_iter() + .map(|path| (path, NetworkUnixSocketPermissionToml::Allow)) + .collect::>(); + + (!entries.is_empty()).then_some(NetworkUnixSocketPermissionsToml { entries }) +} + /// Normalized network constraints derived from requirements TOML. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] pub struct NetworkConstraints { pub enabled: Option, pub http_port: Option, @@ -158,15 +349,24 @@ pub struct NetworkConstraints { pub allow_upstream_proxy: Option, pub dangerously_allow_non_loopback_proxy: Option, pub dangerously_allow_all_unix_sockets: Option, - pub allowed_domains: Option>, + pub domains: Option, /// When true, only managed `allowed_domains` are respected while managed /// network enforcement is active. User allowlist entries are ignored. pub managed_allowed_domains_only: Option, - pub denied_domains: Option>, - pub allow_unix_sockets: Option>, + pub unix_sockets: Option, pub allow_local_binding: Option, } +impl<'de> Deserialize<'de> for NetworkConstraints { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let requirements = NetworkRequirementsToml::deserialize(deserializer)?; + Ok(requirements.into()) + } +} + impl From for NetworkConstraints { fn from(value: NetworkRequirementsToml) -> Self { let NetworkRequirementsToml { @@ -176,10 +376,9 @@ impl From for NetworkConstraints { allow_upstream_proxy, dangerously_allow_non_loopback_proxy, dangerously_allow_all_unix_sockets, - allowed_domains, + domains, managed_allowed_domains_only, - denied_domains, - allow_unix_sockets, + unix_sockets, allow_local_binding, } = value; Self { @@ -189,10 +388,9 @@ impl From for NetworkConstraints { allow_upstream_proxy, dangerously_allow_non_loopback_proxy, dangerously_allow_all_unix_sockets, - allowed_domains, + domains, managed_allowed_domains_only, - denied_domains, - allow_unix_sockets, + unix_sockets, allow_local_binding, } } @@ -1470,6 +1668,78 @@ guardian_developer_instructions = """ #[test] fn network_requirements_are_preserved_as_constraints_with_source() -> Result<()> { + let toml_str = r#" + [experimental_network] + enabled = true + allow_upstream_proxy = false + dangerously_allow_all_unix_sockets = true + managed_allowed_domains_only = true + allow_local_binding = false + + [experimental_network.domains] + "api.example.com" = "allow" + "*.openai.com" = "allow" + "blocked.example.com" = "deny" + + [experimental_network.unix_sockets] + "/tmp/example.sock" = "allow" + "#; + + let source = RequirementSource::CloudRequirements; + let mut requirements_with_sources = ConfigRequirementsWithSources::default(); + requirements_with_sources.merge_unset_fields(source.clone(), from_str(toml_str)?); + + let requirements = ConfigRequirements::try_from(requirements_with_sources)?; + let sourced_network = requirements + .network + .expect("network requirements should be preserved as constraints"); + + assert_eq!(sourced_network.source, source); + assert_eq!(sourced_network.value.enabled, Some(true)); + assert_eq!(sourced_network.value.allow_upstream_proxy, Some(false)); + assert_eq!( + sourced_network.value.dangerously_allow_all_unix_sockets, + Some(true) + ); + assert_eq!( + sourced_network.value.domains.as_ref(), + Some(&NetworkDomainPermissionsToml { + entries: BTreeMap::from([ + ( + "*.openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "blocked.example.com".to_string(), + NetworkDomainPermissionToml::Deny, + ), + ]), + }) + ); + assert_eq!( + sourced_network.value.managed_allowed_domains_only, + Some(true) + ); + assert_eq!( + sourced_network.value.unix_sockets.as_ref(), + Some(&NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([( + "/tmp/example.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + )]), + }) + ); + assert_eq!(sourced_network.value.allow_local_binding, Some(false)); + + Ok(()) + } + + #[test] + fn legacy_network_requirements_are_preserved_as_constraints_with_source() -> Result<()> { let toml_str = r#" [experimental_network] enabled = true @@ -1499,29 +1769,137 @@ guardian_developer_instructions = """ Some(true) ); assert_eq!( - sourced_network.value.allowed_domains.as_ref(), - Some(&vec![ - "api.example.com".to_string(), - "*.openai.com".to_string() - ]) + sourced_network.value.domains.as_ref(), + Some(&NetworkDomainPermissionsToml { + entries: BTreeMap::from([ + ( + "*.openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "blocked.example.com".to_string(), + NetworkDomainPermissionToml::Deny, + ), + ]), + }) ); assert_eq!( sourced_network.value.managed_allowed_domains_only, Some(true) ); assert_eq!( - sourced_network.value.denied_domains.as_ref(), - Some(&vec!["blocked.example.com".to_string()]) - ); - assert_eq!( - sourced_network.value.allow_unix_sockets.as_ref(), - Some(&vec!["/tmp/example.sock".to_string()]) + sourced_network.value.unix_sockets.as_ref(), + Some(&NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([( + "/tmp/example.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + )]), + }) ); assert_eq!(sourced_network.value.allow_local_binding, Some(false)); Ok(()) } + #[test] + fn mixed_legacy_and_canonical_network_requirements_are_rejected() { + let err = from_str::( + r#" + [experimental_network] + allowed_domains = ["api.example.com"] + + [experimental_network.domains] + "*.openai.com" = "allow" + "#, + ) + .expect_err("mixed network domain shapes should fail"); + + assert!( + err.to_string() + .contains("`experimental_network.domains` cannot be combined"), + "unexpected error: {err:#}" + ); + + let err = from_str::( + r#" + [experimental_network] + allow_unix_sockets = ["/tmp/example.sock"] + + [experimental_network.unix_sockets] + "/tmp/another.sock" = "allow" + "#, + ) + .expect_err("mixed network unix socket shapes should fail"); + + assert!( + err.to_string() + .contains("`experimental_network.unix_sockets` cannot be combined"), + "unexpected error: {err:#}" + ); + } + + #[test] + fn network_permission_containers_project_allowed_and_denied_entries() { + let domains = NetworkDomainPermissionsToml { + entries: BTreeMap::from([ + ( + "*.openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "blocked.example.com".to_string(), + NetworkDomainPermissionToml::Deny, + ), + ]), + }; + let unix_sockets = NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([ + ( + "/tmp/example.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + ), + ( + "/tmp/ignored.sock".to_string(), + NetworkUnixSocketPermissionToml::None, + ), + ]), + }; + + assert_eq!( + domains.allowed_domains(), + Some(vec![ + "*.openai.com".to_string(), + "api.example.com".to_string() + ]) + ); + assert_eq!( + domains.denied_domains(), + Some(vec!["blocked.example.com".to_string()]) + ); + assert_eq!( + NetworkDomainPermissionsToml { + entries: BTreeMap::from([( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + } + .denied_domains(), + None + ); + assert_eq!( + unix_sockets.allow_unix_sockets(), + vec!["/tmp/example.sock".to_string()] + ); + } + #[test] fn deserialize_mcp_server_requirements() -> Result<()> { let toml_str = r#" diff --git a/codex-rs/config/src/constraint.rs b/codex-rs/config/src/constraint.rs index d6c8e09f5f..13cb867ce1 100644 --- a/codex-rs/config/src/constraint.rs +++ b/codex-rs/config/src/constraint.rs @@ -188,8 +188,10 @@ mod tests { #[test] fn constrained_allow_any_accepts_any_value() { - let mut constrained = Constrained::allow_any(5); - constrained.set(-10).expect("allow any accepts all values"); + let mut constrained = Constrained::allow_any(/*initial_value*/ 5); + constrained + .set(/*value*/ -10) + .expect("allow any accepts all values"); assert_eq!(constrained.value(), -10); } @@ -201,13 +203,13 @@ mod tests { #[test] fn constrained_allow_only_rejects_different_values() { - let mut constrained = Constrained::allow_only(5); + let mut constrained = Constrained::allow_only(/*only_value*/ 5); constrained - .set(5) + .set(/*value*/ 5) .expect("allowed value should be accepted"); let err = constrained - .set(6) + .set(/*value*/ 6) .expect_err("different value should be rejected"); assert_eq!(err, invalid_value("6", "[5]")); assert_eq!(constrained.value(), 5); @@ -215,18 +217,19 @@ mod tests { #[test] fn constrained_normalizer_applies_on_init_and_set() -> anyhow::Result<()> { - let mut constrained = Constrained::normalized(-1, |value| value.max(0))?; + let mut constrained = + Constrained::normalized(/*initial_value*/ -1, |value| value.max(0))?; assert_eq!(constrained.value(), 0); - constrained.set(-5)?; + constrained.set(/*value*/ -5)?; assert_eq!(constrained.value(), 0); - constrained.set(10)?; + constrained.set(/*value*/ 10)?; assert_eq!(constrained.value(), 10); Ok(()) } #[test] fn constrained_new_rejects_invalid_initial_value() { - let result = Constrained::new(0, |value| { + let result = Constrained::new(/*initial_value*/ 0, |value| { if *value > 0 { Ok(()) } else { @@ -239,7 +242,7 @@ mod tests { #[test] fn constrained_set_rejects_invalid_value_and_leaves_previous() { - let mut constrained = Constrained::new(1, |value| { + let mut constrained = Constrained::new(/*initial_value*/ 1, |value| { if *value > 0 { Ok(()) } else { @@ -249,7 +252,7 @@ mod tests { .expect("initial value should be accepted"); let err = constrained - .set(-5) + .set(/*value*/ -5) .expect_err("negative values should be rejected"); assert_eq!(err, invalid_value("-5", "positive values")); assert_eq!(constrained.value(), 1); @@ -257,7 +260,7 @@ mod tests { #[test] fn constrained_can_set_allows_probe_without_setting() { - let constrained = Constrained::new(1, |value| { + let constrained = Constrained::new(/*initial_value*/ 1, |value| { if *value > 0 { Ok(()) } else { diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index 7bd572fea3..9492d56b4d 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -25,7 +25,11 @@ pub use config_requirements::FeatureRequirementsToml; pub use config_requirements::McpServerIdentity; pub use config_requirements::McpServerRequirement; pub use config_requirements::NetworkConstraints; +pub use config_requirements::NetworkDomainPermissionToml; +pub use config_requirements::NetworkDomainPermissionsToml; pub use config_requirements::NetworkRequirementsToml; +pub use config_requirements::NetworkUnixSocketPermissionToml; +pub use config_requirements::NetworkUnixSocketPermissionsToml; pub use config_requirements::RequirementSource; pub use config_requirements::ResidencyRequirement; pub use config_requirements::SandboxModeRequirement; diff --git a/codex-rs/connectors/src/lib.rs b/codex-rs/connectors/src/lib.rs index 1d3a729233..a74ece5a35 100644 --- a/codex-rs/connectors/src/lib.rs +++ b/codex-rs/connectors/src/lib.rs @@ -419,7 +419,7 @@ mod tests { "https://chatgpt.example".to_string(), Some(format!("account-{id}")), Some(format!("user-{id}")), - true, + /*is_workspace_account*/ true, ) } @@ -444,21 +444,31 @@ mod tests { let call_counter = Arc::clone(&calls); let key = cache_key("shared"); - let first = list_all_connectors_with_options(key.clone(), false, false, move |_path| { - let call_counter = Arc::clone(&call_counter); - async move { - call_counter.fetch_add(1, Ordering::SeqCst); - Ok(DirectoryListResponse { - apps: vec![app("alpha", "Alpha")], - next_token: None, - }) - } - }) + let first = list_all_connectors_with_options( + key.clone(), + /*is_workspace_account*/ false, + /*force_refetch*/ false, + move |_path| { + let call_counter = Arc::clone(&call_counter); + async move { + call_counter.fetch_add(1, Ordering::SeqCst); + Ok(DirectoryListResponse { + apps: vec![app("alpha", "Alpha")], + next_token: None, + }) + } + }, + ) .await?; - let second = list_all_connectors_with_options(key, false, false, move |_path| async move { - anyhow::bail!("cache should have been used"); - }) + let second = list_all_connectors_with_options( + key, + /*is_workspace_account*/ false, + /*force_refetch*/ false, + move |_path| async move { + anyhow::bail!("cache should have been used"); + }, + ) .await?; assert_eq!(calls.load(Ordering::SeqCst), 1); @@ -472,40 +482,45 @@ mod tests { let calls = Arc::new(AtomicUsize::new(0)); let call_counter = Arc::clone(&calls); - let connectors = list_all_connectors_with_options(key, true, true, move |path| { - let call_counter = Arc::clone(&call_counter); - async move { - call_counter.fetch_add(1, Ordering::SeqCst); - if path.starts_with("/connectors/directory/list_workspace") { - Ok(DirectoryListResponse { - apps: vec![ - DirectoryApp { - description: Some("Merged description".to_string()), - branding: Some(AppBranding { - category: Some("calendar".to_string()), - developer: None, - website: None, - privacy_policy: None, - terms_of_service: None, - is_discoverable_app: true, - }), - ..app("alpha", "") - }, - DirectoryApp { - visibility: Some("HIDDEN".to_string()), - ..app("hidden", "Hidden") - }, - ], - next_token: None, - }) - } else { - Ok(DirectoryListResponse { - apps: vec![app("alpha", " Alpha "), app("beta", "Beta")], - next_token: None, - }) + let connectors = list_all_connectors_with_options( + key, + /*is_workspace_account*/ true, + /*force_refetch*/ true, + move |path| { + let call_counter = Arc::clone(&call_counter); + async move { + call_counter.fetch_add(1, Ordering::SeqCst); + if path.starts_with("/connectors/directory/list_workspace") { + Ok(DirectoryListResponse { + apps: vec![ + DirectoryApp { + description: Some("Merged description".to_string()), + branding: Some(AppBranding { + category: Some("calendar".to_string()), + developer: None, + website: None, + privacy_policy: None, + terms_of_service: None, + is_discoverable_app: true, + }), + ..app("alpha", "") + }, + DirectoryApp { + visibility: Some("HIDDEN".to_string()), + ..app("hidden", "Hidden") + }, + ], + next_token: None, + }) + } else { + Ok(DirectoryListResponse { + apps: vec![app("alpha", " Alpha "), app("beta", "Beta")], + next_token: None, + }) + } } - } - }) + }, + ) .await?; assert_eq!(calls.load(Ordering::SeqCst), 2); diff --git a/codex-rs/core-skills/src/loader_tests.rs b/codex-rs/core-skills/src/loader_tests.rs index ad196df670..3702856306 100644 --- a/codex-rs/core-skills/src/loader_tests.rs +++ b/codex-rs/core-skills/src/loader_tests.rs @@ -115,7 +115,7 @@ fn load_skills_for_test(config: &TestConfig) -> SkillLoadOutcome { super::load_skills_from_roots(super::skill_roots_with_home_dir( &config.config_layer_stack, &config.cwd, - None, + /*home_dir*/ None, Vec::new(), )) } diff --git a/codex-rs/core-skills/src/manager_tests.rs b/codex-rs/core-skills/src/manager_tests.rs index 9e6c60c0e6..62218f7311 100644 --- a/codex-rs/core-skills/src/manager_tests.rs +++ b/codex-rs/core-skills/src/manager_tests.rs @@ -141,7 +141,10 @@ fn new_with_disabled_bundled_skills_removes_stale_cached_system_skills() { fs::write(stale_system_skill_dir.join("SKILL.md"), "# stale\n") .expect("write stale system skill"); - let _skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), false); + let _skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ false, + ); assert!( !codex_home.path().join("skills/.system").exists(), @@ -154,7 +157,10 @@ async fn skills_for_config_reuses_cache_for_same_effective_config() { let codex_home = tempfile::tempdir().expect("tempdir"); let cwd = tempfile::tempdir().expect("tempdir"); let config_layer_stack = config_stack(&codex_home, ""); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); write_user_skill(&codex_home, "a", "skill-a", "from a"); let outcome1 = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]); @@ -185,14 +191,17 @@ async fn skills_for_config_disables_plugin_skills_by_name() { ); let config_layer_stack = config_stack( &codex_home, - &name_toggle_config("sample:sample-search", false), + &name_toggle_config("sample:sample-search", /*enabled*/ false), ); let plugin_skill_root = skill_path .parent() .and_then(std::path::Path::parent) .expect("plugin skill should live under a skills root") .to_path_buf(); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); let outcome = skills_for_config_with_stack( &skills_manager, @@ -223,7 +232,10 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() { let cwd = tempfile::tempdir().expect("tempdir"); let extra_root = tempfile::tempdir().expect("tempdir"); let config_layer_stack = config_stack(&codex_home, ""); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); let _ = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]); write_user_skill(&extra_root, "x", "extra-skill", "from extra root"); @@ -237,7 +249,7 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() { let outcome_with_extra = skills_manager .skills_for_cwd_with_extra_user_roots( &base_input, - true, + /*force_reload*/ true, std::slice::from_ref(&extra_root_path), ) .await; @@ -262,7 +274,9 @@ async fn skills_for_cwd_reuses_cached_entry_even_when_entry_has_extra_roots() { config_layer_stack.clone(), bundled_skills_enabled_from_stack(&config_layer_stack), ); - let outcome_without_extra = skills_manager.skills_for_cwd(&base_input, false).await; + let outcome_without_extra = skills_manager + .skills_for_cwd(&base_input, /*force_reload*/ false) + .await; assert_eq!(outcome_without_extra.skills, outcome_with_extra.skills); assert_eq!(outcome_without_extra.errors, outcome_with_extra.errors); } @@ -279,7 +293,10 @@ async fn skills_for_config_excludes_bundled_skills_when_disabled_in_config() { ) .expect("write bundled skill"); let config_layer_stack = config_stack(&codex_home, "[skills.bundled]\nenabled = false\n"); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), false); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ false, + ); // Recreate the cached bundled skill after startup cleanup so this assertion exercises // root selection rather than relying on directory removal succeeding. @@ -312,7 +329,10 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() { let extra_root_a = tempfile::tempdir().expect("tempdir"); let extra_root_b = tempfile::tempdir().expect("tempdir"); let config_layer_stack = config_stack(&codex_home, ""); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); let _ = skills_for_config_with_stack(&skills_manager, &cwd, &config_layer_stack, &[]); write_user_skill(&extra_root_a, "x", "extra-skill-a", "from extra root a"); @@ -328,7 +348,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() { let outcome_a = skills_manager .skills_for_cwd_with_extra_user_roots( &base_input, - true, + /*force_reload*/ true, std::slice::from_ref(&extra_root_a_path), ) .await; @@ -349,7 +369,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() { let outcome_b = skills_manager .skills_for_cwd_with_extra_user_roots( &base_input, - false, + /*force_reload*/ false, std::slice::from_ref(&extra_root_b_path), ) .await; @@ -369,7 +389,7 @@ async fn skills_for_cwd_with_extra_roots_only_refreshes_on_force_reload() { let outcome_reloaded = skills_manager .skills_for_cwd_with_extra_user_roots( &base_input, - true, + /*force_reload*/ true, std::slice::from_ref(&extra_root_b_path), ) .await; @@ -408,11 +428,13 @@ fn disabled_paths_for_skills_allows_session_flags_to_override_user_layer() { .expect("user config path should be absolute"); let user_layer = ConfigLayerEntry::new( ConfigLayerSource::User { file: user_file }, - toml::from_str(&path_toggle_config(&skill_path, false)).expect("user layer toml"), + toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false)) + .expect("user layer toml"), ); let session_layer = ConfigLayerEntry::new( ConfigLayerSource::SessionFlags, - toml::from_str(&path_toggle_config(&skill_path, true)).expect("session layer toml"), + toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true)) + .expect("session layer toml"), ); let stack = ConfigLayerStack::new( vec![user_layer, session_layer], @@ -438,11 +460,13 @@ fn disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill( .expect("user config path should be absolute"); let user_layer = ConfigLayerEntry::new( ConfigLayerSource::User { file: user_file }, - toml::from_str(&path_toggle_config(&skill_path, true)).expect("user layer toml"), + toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ true)) + .expect("user layer toml"), ); let session_layer = ConfigLayerEntry::new( ConfigLayerSource::SessionFlags, - toml::from_str(&path_toggle_config(&skill_path, false)).expect("session layer toml"), + toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false)) + .expect("session layer toml"), ); let stack = ConfigLayerStack::new( vec![user_layer, session_layer], @@ -468,7 +492,8 @@ fn disabled_paths_for_skills_disables_matching_name_selectors() { .expect("user config path should be absolute"); let user_layer = ConfigLayerEntry::new( ConfigLayerSource::User { file: user_file }, - toml::from_str(&name_toggle_config("github:yeet", false)).expect("user layer toml"), + toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ false)) + .expect("user layer toml"), ); let stack = ConfigLayerStack::new( vec![user_layer], @@ -494,11 +519,13 @@ fn disabled_paths_for_skills_allows_name_selector_to_override_path_selector() { .expect("user config path should be absolute"); let user_layer = ConfigLayerEntry::new( ConfigLayerSource::User { file: user_file }, - toml::from_str(&path_toggle_config(&skill_path, false)).expect("user layer toml"), + toml::from_str(&path_toggle_config(&skill_path, /*enabled*/ false)) + .expect("user layer toml"), ); let session_layer = ConfigLayerEntry::new( ConfigLayerSource::SessionFlags, - toml::from_str(&name_toggle_config("github:yeet", true)).expect("session layer toml"), + toml::from_str(&name_toggle_config("github:yeet", /*enabled*/ true)) + .expect("session layer toml"), ); let stack = ConfigLayerStack::new( vec![user_layer, session_layer], @@ -527,12 +554,15 @@ async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill() "---\nname: demo-skill\ndescription: demo description\n---\n\n# Body\n", ) .expect("write skill"); - let disabled_skill_config = path_toggle_config(&skill_path, false); - let enabled_skill_config = path_toggle_config(&skill_path, true); + let disabled_skill_config = path_toggle_config(&skill_path, /*enabled*/ false); + let enabled_skill_config = path_toggle_config(&skill_path, /*enabled*/ true); let parent_stack = config_stack(&codex_home, &disabled_skill_config); let child_stack = config_stack_with_session_flags(&codex_home, &disabled_skill_config, &enabled_skill_config); - let skills_manager = SkillsManager::new(codex_home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + codex_home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); let parent_input = SkillsLoadInput::new( cwd.path().to_path_buf(), Vec::new(), @@ -540,7 +570,9 @@ async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill() bundled_skills_enabled_from_stack(&parent_stack), ); - let parent_outcome = skills_manager.skills_for_cwd(&parent_input, true).await; + let parent_outcome = skills_manager + .skills_for_cwd(&parent_input, /*force_reload*/ true) + .await; let parent_skill = parent_outcome .skills .iter() diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 86c8788228..b2586c55d0 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -506,9 +506,6 @@ "use_linux_sandbox_bwrap": { "type": "boolean" }, - "voice_transcription": { - "type": "boolean" - }, "web_search": { "type": "boolean" }, @@ -916,6 +913,16 @@ ], "type": "object" }, + "NetworkDomainPermissionToml": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, + "NetworkDomainPermissionsToml": { + "type": "object" + }, "NetworkModeSchema": { "enum": [ "limited", @@ -929,32 +936,17 @@ "allow_local_binding": { "type": "boolean" }, - "allow_unix_sockets": { - "items": { - "type": "string" - }, - "type": "array" - }, "allow_upstream_proxy": { "type": "boolean" }, - "allowed_domains": { - "items": { - "type": "string" - }, - "type": "array" - }, "dangerously_allow_all_unix_sockets": { "type": "boolean" }, "dangerously_allow_non_loopback_proxy": { "type": "boolean" }, - "denied_domains": { - "items": { - "type": "string" - }, - "type": "array" + "domains": { + "$ref": "#/definitions/NetworkDomainPermissionsToml" }, "enable_socks5": { "type": "boolean" @@ -973,10 +965,23 @@ }, "socks_url": { "type": "string" + }, + "unix_sockets": { + "$ref": "#/definitions/NetworkUnixSocketPermissionsToml" } }, "type": "object" }, + "NetworkUnixSocketPermissionToml": { + "enum": [ + "allow", + "none" + ], + "type": "string" + }, + "NetworkUnixSocketPermissionsToml": { + "type": "object" + }, "Notice": { "description": "Settings for notices we display to users via the tui and app-server clients (primarily the Codex IDE extension). NOTE: these are different from notifications - notices are warnings, NUX screens, acknowledgements, etc.", "properties": { @@ -2146,9 +2151,6 @@ "use_linux_sandbox_bwrap": { "type": "boolean" }, - "voice_transcription": { - "type": "boolean" - }, "web_search": { "type": "boolean" }, diff --git a/codex-rs/core/src/agent/agent_resolver.rs b/codex-rs/core/src/agent/agent_resolver.rs index 3d1f75f57f..14f21c731c 100644 --- a/codex-rs/core/src/agent/agent_resolver.rs +++ b/codex-rs/core/src/agent/agent_resolver.rs @@ -28,25 +28,6 @@ pub(crate) async fn resolve_agent_target( }) } -/// Resolves multiple tool-facing agent targets to thread ids. -pub(crate) async fn resolve_agent_targets( - session: &Arc, - turn: &Arc, - targets: Vec, -) -> Result, FunctionCallError> { - if targets.is_empty() { - return Err(FunctionCallError::RespondToModel( - "agent targets must be non-empty".to_string(), - )); - } - - let mut resolved = Vec::with_capacity(targets.len()); - for target in &targets { - resolved.push(resolve_agent_target(session, turn, target).await?); - } - Ok(resolved) -} - fn register_session_root(session: &Arc, turn: &Arc) { session .services diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 1efd514f62..9157228df3 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -5,10 +5,8 @@ use crate::agent::role::DEFAULT_ROLE_NAME; use crate::agent::role::resolve_role_config; use crate::agent::status::is_final; use crate::codex_thread::ThreadConfigSnapshot; -use crate::context_manager::is_user_turn_boundary; use crate::error::CodexErr; use crate::error::Result as CodexResult; -use crate::event_mapping::parse_turn_item; use crate::find_archived_thread_path_by_id_str; use crate::find_thread_path_by_id_str; use crate::rollout::RolloutRecorder; @@ -20,10 +18,7 @@ use crate::thread_manager::ThreadManagerState; use codex_features::Feature; use codex_protocol::AgentPath; use codex_protocol::ThreadId; -use codex_protocol::items::TurnItem; -use codex_protocol::models::ContentItem; use codex_protocol::models::FunctionCallOutputPayload; -use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::InitialHistory; use codex_protocol::protocol::InterAgentCommunication; @@ -118,30 +113,36 @@ impl AgentControl { pub(crate) async fn spawn_agent( &self, config: crate::config::Config, - items: Vec, + initial_operation: Op, session_source: Option, ) -> CodexResult { Ok(self - .spawn_agent_internal(config, items, session_source, SpawnAgentOptions::default()) + .spawn_agent_internal( + config, + initial_operation, + session_source, + SpawnAgentOptions::default(), + ) .await? .thread_id) } + /// Spawn an agent thread with some metadata. pub(crate) async fn spawn_agent_with_metadata( &self, config: crate::config::Config, - items: Vec, + initial_operation: Op, session_source: Option, - options: SpawnAgentOptions, + options: SpawnAgentOptions, // TODO(jif) drop with new fork. ) -> CodexResult { - self.spawn_agent_internal(config, items, session_source, options) + self.spawn_agent_internal(config, initial_operation, session_source, options) .await } async fn spawn_agent_internal( &self, config: crate::config::Config, - items: Vec, + initial_operation: Op, session_source: Option, options: SpawnAgentOptions, ) -> CodexResult { @@ -270,7 +271,8 @@ impl AgentControl { ) .await; - self.send_input(new_thread.thread_id, items).await?; + self.send_input(new_thread.thread_id, initial_operation) + .await?; let child_reference = agent_metadata .agent_path .as_ref() @@ -468,23 +470,15 @@ impl AgentControl { pub(crate) async fn send_input( &self, agent_id: ThreadId, - items: Vec, + initial_operation: Op, ) -> CodexResult { - let last_task_message = render_input_preview(&items); + let last_task_message = render_input_preview(&initial_operation); let state = self.upgrade()?; let result = self .handle_thread_request_result( agent_id, &state, - state - .send_op( - agent_id, - Op::UserInput { - items, - final_output_json_schema: None, - }, - ) - .await, + state.send_op(agent_id, initial_operation).await, ) .await; if result.is_ok() { @@ -766,10 +760,7 @@ impl AgentControl { .as_ref() .map(ToString::to_string) .unwrap_or_else(|| thread_id.to_string()); - let last_task_message = match metadata.last_task_message.clone() { - Some(last_task_message) => Some(last_task_message), - None => last_task_message_for_thread(thread.as_ref()).await, - }; + let last_task_message = metadata.last_task_message.clone(); agents.push(ListedAgent { agent_name, agent_status: thread.agent_status().await, @@ -1072,79 +1063,23 @@ fn agent_matches_prefix(agent_path: Option<&AgentPath>, prefix: &AgentPath) -> b }) } -async fn last_task_message_for_thread(thread: &crate::CodexThread) -> Option { - let pending_input = thread.codex.session.pending_input_snapshot().await; - if let Some(message) = pending_input - .iter() - .rev() - .find_map(last_task_message_from_input_item) - { - return Some(message); +pub(crate) fn render_input_preview(initial_operation: &Op) -> String { + match initial_operation { + Op::UserInput { items, .. } => items + .iter() + .map(|item| match item { + UserInput::Text { text, .. } => text.clone(), + UserInput::Image { .. } => "[image]".to_string(), + UserInput::LocalImage { path } => format!("[local_image:{}]", path.display()), + UserInput::Skill { name, path } => format!("[skill:${name}]({})", path.display()), + UserInput::Mention { name, path } => format!("[mention:${name}]({path})"), + _ => "[input]".to_string(), + }) + .collect::>() + .join("\n"), + Op::InterAgentCommunication { communication } => communication.content.clone(), + _ => String::new(), } - - let queued_input = thread - .codex - .session - .queued_response_items_for_next_turn_snapshot() - .await; - if let Some(message) = queued_input - .iter() - .rev() - .find_map(last_task_message_from_input_item) - { - return Some(message); - } - - let history = thread.codex.session.clone_history().await; - history - .raw_items() - .iter() - .rev() - .find_map(last_task_message_from_item) -} - -fn last_task_message_from_input_item(item: &ResponseInputItem) -> Option { - let response_item: ResponseItem = item.clone().into(); - last_task_message_from_item(&response_item) -} - -fn last_task_message_from_item(item: &ResponseItem) -> Option { - if !is_user_turn_boundary(item) { - return None; - } - - match item { - ResponseItem::Message { role, .. } if role == "user" => { - let Some(TurnItem::UserMessage(message)) = parse_turn_item(item) else { - return None; - }; - Some(render_input_preview(&message.content)) - } - ResponseItem::Message { content, .. } => match content.as_slice() { - [ContentItem::InputText { text }] | [ContentItem::OutputText { text }] => { - serde_json::from_str::(text) - .ok() - .map(|communication| communication.content) - } - _ => None, - }, - _ => None, - } -} - -fn render_input_preview(items: &[UserInput]) -> String { - items - .iter() - .map(|item| match item { - UserInput::Text { text, .. } => text.clone(), - UserInput::Image { .. } => "[image]".to_string(), - UserInput::LocalImage { path } => format!("[local_image:{}]", path.display()), - UserInput::Skill { name, path } => format!("[skill:${name}]({})", path.display()), - UserInput::Mention { name, path } => format!("[mention:${name}]({path})"), - _ => "[input]".to_string(), - }) - .collect::>() - .join("\n") } fn thread_spawn_depth(session_source: &SessionSource) -> Option { diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 878d7d0991..e440f98385 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -54,11 +54,12 @@ async fn test_config() -> (TempDir, Config) { test_config_with_cli_overrides(Vec::new()).await } -fn text_input(text: &str) -> Vec { +fn text_input(text: &str) -> Op { vec![UserInput::Text { text: text.to_string(), text_elements: Vec::new(), }] + .into() } struct AgentControlHarness { @@ -217,7 +218,8 @@ async fn send_input_errors_when_manager_dropped() { vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }], + }] + .into(), ) .await .expect_err("send_input should fail without a manager"); @@ -287,7 +289,7 @@ async fn spawn_agent_errors_when_manager_dropped() { let control = AgentControl::default(); let (_home, config) = test_config().await; let err = control - .spawn_agent(config, text_input("hello"), None) + .spawn_agent(config, text_input("hello"), /*session_source*/ None) .await .expect_err("spawn_agent should fail without a manager"); assert_eq!( @@ -321,7 +323,8 @@ async fn send_input_errors_when_thread_missing() { vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), - }], + }] + .into(), ) .await .expect_err("send_input should fail for missing thread"); @@ -387,7 +390,8 @@ async fn send_input_submits_user_message() { vec![UserInput::Text { text: "hello from tests".to_string(), text_elements: Vec::new(), - }], + }] + .into(), ) .await .expect("send_input should succeed"); @@ -419,7 +423,7 @@ async fn send_inter_agent_communication_without_turn_queues_message_without_trig AgentPath::try_from("/root/worker").expect("agent path"), Vec::new(), "hello from tests".to_string(), - false, + /*trigger_turn*/ false, ); let submission_id = harness @@ -444,12 +448,7 @@ async fn send_inter_agent_communication_without_turn_queues_message_without_trig timeout(Duration::from_secs(5), async { loop { - if thread - .codex - .session - .has_queued_response_items_for_next_turn() - .await - { + if thread.codex.session.has_pending_input().await { break; } sleep(Duration::from_millis(10)).await; @@ -531,7 +530,11 @@ async fn spawn_agent_creates_thread_and_sends_prompt() { let harness = AgentControlHarness::new().await; let thread_id = harness .control - .spawn_agent(harness.config.clone(), text_input("spawned"), None) + .spawn_agent( + harness.config.clone(), + text_input("spawned"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed"); let _thread = harness @@ -826,12 +829,20 @@ async fn spawn_agent_respects_max_threads_limit() { .expect("start thread"); let first_agent_id = control - .spawn_agent(config.clone(), text_input("hello"), None) + .spawn_agent( + config.clone(), + text_input("hello"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed"); let err = control - .spawn_agent(config, text_input("hello again"), None) + .spawn_agent( + config, + text_input("hello again"), + /*session_source*/ None, + ) .await .expect_err("spawn_agent should respect max threads"); let CodexErr::AgentLimitReached { @@ -867,7 +878,11 @@ async fn spawn_agent_releases_slot_after_shutdown() { let control = manager.agent_control(); let first_agent_id = control - .spawn_agent(config.clone(), text_input("hello"), None) + .spawn_agent( + config.clone(), + text_input("hello"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed"); let _ = control @@ -876,7 +891,11 @@ async fn spawn_agent_releases_slot_after_shutdown() { .expect("shutdown agent"); let second_agent_id = control - .spawn_agent(config.clone(), text_input("hello again"), None) + .spawn_agent( + config.clone(), + text_input("hello again"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed after shutdown"); let _ = control @@ -905,12 +924,20 @@ async fn spawn_agent_limit_shared_across_clones() { let cloned = control.clone(); let first_agent_id = cloned - .spawn_agent(config.clone(), text_input("hello"), None) + .spawn_agent( + config.clone(), + text_input("hello"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed"); let err = control - .spawn_agent(config, text_input("hello again"), None) + .spawn_agent( + config, + text_input("hello again"), + /*session_source*/ None, + ) .await .expect_err("spawn_agent should respect shared guard"); let CodexErr::AgentLimitReached { max_threads } = err else { @@ -943,7 +970,11 @@ async fn resume_agent_respects_max_threads_limit() { let control = manager.agent_control(); let resumable_id = control - .spawn_agent(config.clone(), text_input("hello"), None) + .spawn_agent( + config.clone(), + text_input("hello"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed"); let _ = control @@ -952,7 +983,11 @@ async fn resume_agent_respects_max_threads_limit() { .expect("shutdown resumable thread"); let active_id = control - .spawn_agent(config.clone(), text_input("occupy"), None) + .spawn_agent( + config.clone(), + text_input("occupy"), + /*session_source*/ None, + ) .await .expect("spawn_agent should succeed for active slot"); @@ -998,7 +1033,7 @@ async fn resume_agent_releases_slot_after_resume_failure() { .expect_err("resume should fail for missing rollout path"); let resumed_id = control - .spawn_agent(config, text_input("hello"), None) + .spawn_agent(config, text_input("hello"), /*session_source*/ None) .await .expect("spawn should succeed after failed resume"); let _ = control @@ -1136,7 +1171,7 @@ async fn multi_agent_v2_completion_ignores_dead_direct_parent() { AgentPath::root(), Vec::new(), "done".to_string(), - true, + /*trigger_turn*/ true, ) )); assert!(!has_subagent_notification(&root_history_items)); @@ -1199,7 +1234,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() { worker_path.clone(), Vec::new(), expected_message.clone(), - false, + /*trigger_turn*/ false, ), }, ); @@ -1234,7 +1269,7 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() { AgentPath::root(), Vec::new(), expected_message, - false, + /*trigger_turn*/ false, ) )); } @@ -1255,7 +1290,7 @@ async fn completion_watcher_notifies_parent_when_child_is_missing() { agent_role: Some("explorer".to_string()), })), child_thread_id.to_string(), - None, + /*child_agent_path*/ None, ); assert_eq!(wait_for_subagent_notification(&parent_thread).await, true); @@ -1516,7 +1551,11 @@ async fn resume_agent_from_rollout_reads_archived_rollout_path() { let harness = AgentControlHarness::new().await; let child_thread_id = harness .control - .spawn_agent(harness.config.clone(), text_input("hello"), None) + .spawn_agent( + harness.config.clone(), + text_input("hello"), + /*session_source*/ None, + ) .await .expect("child spawn should succeed"); diff --git a/codex-rs/core/src/agent/mailbox.rs b/codex-rs/core/src/agent/mailbox.rs new file mode 100644 index 0000000000..c328236475 --- /dev/null +++ b/codex-rs/core/src/agent/mailbox.rs @@ -0,0 +1,161 @@ +use codex_protocol::protocol::InterAgentCommunication; +use std::collections::VecDeque; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use tokio::sync::mpsc; +use tokio::sync::watch; + +#[cfg(test)] +use codex_protocol::AgentPath; + +pub(crate) struct Mailbox { + tx: mpsc::UnboundedSender, + next_seq: AtomicU64, + seq_tx: watch::Sender, +} + +pub(crate) struct MailboxReceiver { + rx: mpsc::UnboundedReceiver, + pending_mails: VecDeque, +} + +impl Mailbox { + pub(crate) fn new() -> (Self, MailboxReceiver) { + let (tx, rx) = mpsc::unbounded_channel(); + let (seq_tx, _) = watch::channel(0); + ( + Self { + tx, + next_seq: AtomicU64::new(0), + seq_tx, + }, + MailboxReceiver { + rx, + pending_mails: VecDeque::new(), + }, + ) + } + + pub(crate) fn subscribe(&self) -> watch::Receiver { + self.seq_tx.subscribe() + } + + pub(crate) fn send(&self, communication: InterAgentCommunication) -> u64 { + let seq = self.next_seq.fetch_add(1, Ordering::Relaxed) + 1; + let _ = self.tx.send(communication); + self.seq_tx.send_replace(seq); + seq + } +} + +impl MailboxReceiver { + fn sync_pending_mails(&mut self) { + while let Ok(mail) = self.rx.try_recv() { + self.pending_mails.push_back(mail); + } + } + + pub(crate) fn has_pending(&mut self) -> bool { + self.sync_pending_mails(); + !self.pending_mails.is_empty() + } + + pub(crate) fn has_pending_trigger_turn(&mut self) -> bool { + self.sync_pending_mails(); + self.pending_mails.iter().any(|mail| mail.trigger_turn) + } + + pub(crate) fn drain(&mut self) -> Vec { + self.sync_pending_mails(); + self.pending_mails.drain(..).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + fn make_mail( + author: AgentPath, + recipient: AgentPath, + content: &str, + trigger_turn: bool, + ) -> InterAgentCommunication { + InterAgentCommunication::new( + author, + recipient, + Vec::new(), + content.to_string(), + trigger_turn, + ) + } + + #[tokio::test] + async fn mailbox_assigns_monotonic_sequence_numbers() { + let (mailbox, _receiver) = Mailbox::new(); + let mut seq_rx = mailbox.subscribe(); + + let seq_a = mailbox.send(make_mail( + AgentPath::root(), + AgentPath::try_from("/root/worker").expect("agent path"), + "one", + /*trigger_turn*/ false, + )); + let seq_b = mailbox.send(make_mail( + AgentPath::root(), + AgentPath::try_from("/root/worker").expect("agent path"), + "two", + /*trigger_turn*/ false, + )); + + seq_rx.changed().await.expect("first seq update"); + assert_eq!(*seq_rx.borrow(), seq_b); + assert_eq!(seq_a, 1); + assert_eq!(seq_b, 2); + } + + #[tokio::test] + async fn mailbox_drains_in_delivery_order() { + let (mailbox, mut receiver) = Mailbox::new(); + let mail_one = make_mail( + AgentPath::root(), + AgentPath::try_from("/root/worker").expect("agent path"), + "one", + /*trigger_turn*/ false, + ); + let mail_two = make_mail( + AgentPath::try_from("/root/worker").expect("agent path"), + AgentPath::root(), + "two", + /*trigger_turn*/ false, + ); + + mailbox.send(mail_one.clone()); + mailbox.send(mail_two.clone()); + + assert_eq!(receiver.drain(), vec![mail_one, mail_two]); + assert!(!receiver.has_pending()); + } + + #[tokio::test] + async fn mailbox_tracks_pending_trigger_turn_mail() { + let (mailbox, mut receiver) = Mailbox::new(); + + mailbox.send(make_mail( + AgentPath::root(), + AgentPath::try_from("/root/worker").expect("agent path"), + "queued", + /*trigger_turn*/ false, + )); + assert!(!receiver.has_pending_trigger_turn()); + + mailbox.send(make_mail( + AgentPath::root(), + AgentPath::try_from("/root/worker").expect("agent path"), + "wake", + /*trigger_turn*/ true, + )); + assert!(receiver.has_pending_trigger_turn()); + } +} diff --git a/codex-rs/core/src/agent/mod.rs b/codex-rs/core/src/agent/mod.rs index 350962dc08..a60fc3004a 100644 --- a/codex-rs/core/src/agent/mod.rs +++ b/codex-rs/core/src/agent/mod.rs @@ -1,11 +1,14 @@ pub(crate) mod agent_resolver; pub(crate) mod control; +pub(crate) mod mailbox; mod registry; pub(crate) mod role; pub(crate) mod status; pub(crate) use codex_protocol::protocol::AgentStatus; pub(crate) use control::AgentControl; +pub(crate) use mailbox::Mailbox; +pub(crate) use mailbox::MailboxReceiver; pub(crate) use registry::exceeds_thread_spawn_depth_limit; pub(crate) use registry::next_thread_spawn_depth; pub(crate) use status::agent_status_from_event; diff --git a/codex-rs/core/src/agent/registry_tests.rs b/codex-rs/core/src/agent/registry_tests.rs index 43d91952a7..fc172fb336 100644 --- a/codex-rs/core/src/agent/registry_tests.rs +++ b/codex-rs/core/src/agent/registry_tests.rs @@ -16,11 +16,26 @@ fn agent_metadata(thread_id: ThreadId) -> AgentMetadata { #[test] fn format_agent_nickname_adds_ordinals_after_reset() { - assert_eq!(format_agent_nickname("Plato", 0), "Plato"); - assert_eq!(format_agent_nickname("Plato", 1), "Plato the 2nd"); - assert_eq!(format_agent_nickname("Plato", 2), "Plato the 3rd"); - assert_eq!(format_agent_nickname("Plato", 10), "Plato the 11th"); - assert_eq!(format_agent_nickname("Plato", 20), "Plato the 21st"); + assert_eq!( + format_agent_nickname("Plato", /*nickname_reset_count*/ 0), + "Plato" + ); + assert_eq!( + format_agent_nickname("Plato", /*nickname_reset_count*/ 1), + "Plato the 2nd" + ); + assert_eq!( + format_agent_nickname("Plato", /*nickname_reset_count*/ 2), + "Plato the 3rd" + ); + assert_eq!( + format_agent_nickname("Plato", /*nickname_reset_count*/ 10), + "Plato the 11th" + ); + assert_eq!( + format_agent_nickname("Plato", /*nickname_reset_count*/ 20), + "Plato the 21st" + ); } #[test] @@ -39,7 +54,10 @@ fn thread_spawn_depth_increments_and_enforces_limit() { }); let child_depth = next_thread_spawn_depth(&session_source); assert_eq!(child_depth, 2); - assert!(exceeds_thread_spawn_depth_limit(child_depth, 1)); + assert!(exceeds_thread_spawn_depth_limit( + child_depth, + /*max_depth*/ 1 + )); } #[test] @@ -47,7 +65,9 @@ fn non_thread_spawn_subagents_default_to_depth_zero() { let session_source = SessionSource::SubAgent(SubAgentSource::Review); assert_eq!(session_depth(&session_source), 0); assert_eq!(next_thread_spawn_depth(&session_source), 1); - assert!(!exceeds_thread_spawn_depth_limit(1, 1)); + assert!(!exceeds_thread_spawn_depth_limit( + /*depth*/ 1, /*max_depth*/ 1 + )); } #[test] @@ -142,14 +162,18 @@ fn release_is_idempotent_for_registered_threads() { #[test] fn failed_spawn_keeps_nickname_marked_used() { let registry = Arc::new(AgentRegistry::default()); - let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot"); + let mut reservation = registry + .reserve_spawn_slot(/*max_threads*/ None) + .expect("reserve slot"); let agent_nickname = reservation .reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None) .expect("reserve agent name"); assert_eq!(agent_nickname, "alpha"); drop(reservation); - let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot"); + let mut reservation = registry + .reserve_spawn_slot(/*max_threads*/ None) + .expect("reserve slot"); let agent_nickname = reservation .reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None) .expect("unused name should still be preferred"); @@ -160,7 +184,7 @@ fn failed_spawn_keeps_nickname_marked_used() { fn agent_nickname_resets_used_pool_when_exhausted() { let registry = Arc::new(AgentRegistry::default()); let mut first = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve first slot"); let first_name = first .reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None) @@ -170,7 +194,7 @@ fn agent_nickname_resets_used_pool_when_exhausted() { assert_eq!(first_name, "alpha"); let mut second = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve second slot"); let second_name = second .reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None) @@ -188,7 +212,7 @@ fn released_nickname_stays_used_until_pool_reset() { let registry = Arc::new(AgentRegistry::default()); let mut first = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve first slot"); let first_name = first .reserve_agent_nickname_with_preference(&["alpha"], /*preferred*/ None) @@ -200,7 +224,7 @@ fn released_nickname_stays_used_until_pool_reset() { registry.release_spawned_thread(first_id); let mut second = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve second slot"); let second_name = second .reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None) @@ -211,7 +235,7 @@ fn released_nickname_stays_used_until_pool_reset() { registry.release_spawned_thread(second_id); let mut third = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve third slot"); let third_name = third .reserve_agent_nickname_with_preference(&["alpha", "beta"], /*preferred*/ None) @@ -230,7 +254,7 @@ fn repeated_resets_advance_the_ordinal_suffix() { let registry = Arc::new(AgentRegistry::default()); let mut first = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve first slot"); let first_name = first .reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None) @@ -241,7 +265,7 @@ fn repeated_resets_advance_the_ordinal_suffix() { registry.release_spawned_thread(first_id); let mut second = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve second slot"); let second_name = second .reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None) @@ -252,7 +276,7 @@ fn repeated_resets_advance_the_ordinal_suffix() { registry.release_spawned_thread(second_id); let mut third = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve third slot"); let third_name = third .reserve_agent_nickname_with_preference(&["Plato"], /*preferred*/ None) @@ -282,7 +306,7 @@ fn register_root_thread_indexes_root_path() { fn reserved_agent_path_is_released_when_spawn_fails() { let registry = Arc::new(AgentRegistry::default()); let mut first = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve first slot"); first .reserve_agent_path(&agent_path("/root/researcher")) @@ -290,7 +314,7 @@ fn reserved_agent_path_is_released_when_spawn_fails() { drop(first); let mut second = registry - .reserve_spawn_slot(None) + .reserve_spawn_slot(/*max_threads*/ None) .expect("reserve second slot"); second .reserve_agent_path(&agent_path("/root/researcher")) @@ -301,7 +325,9 @@ fn reserved_agent_path_is_released_when_spawn_fails() { fn committed_agent_path_is_indexed_until_release() { let registry = Arc::new(AgentRegistry::default()); let thread_id = ThreadId::new(); - let mut reservation = registry.reserve_spawn_slot(None).expect("reserve slot"); + let mut reservation = registry + .reserve_spawn_slot(/*max_threads*/ None) + .expect("reserve slot"); reservation .reserve_agent_path(&agent_path("/root/researcher")) .expect("reserve path"); diff --git a/codex-rs/core/src/agent/role_tests.rs b/codex-rs/core/src/agent/role_tests.rs index c96bd2acf7..5b3941ebda 100644 --- a/codex-rs/core/src/agent/role_tests.rs +++ b/codex-rs/core/src/agent/role_tests.rs @@ -40,7 +40,10 @@ async fn write_role_config(home: &TempDir, name: &str, contents: &str) -> PathBu fn session_flags_layer_count(config: &Config) -> usize { config .config_layer_stack - .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ true, + ) .into_iter() .filter(|layer| layer.name == ConfigLayerSource::SessionFlags) .count() @@ -51,7 +54,7 @@ async fn apply_role_defaults_to_default_and_leaves_config_unchanged() { let (_home, mut config) = test_config_with_cli_overrides(Vec::new()).await; let before = config.clone(); - apply_role_to_config(&mut config, None) + apply_role_to_config(&mut config, /*role_name*/ None) .await .expect("default role should apply"); @@ -529,7 +532,10 @@ writable_roots = ["./sandbox-root"] let role_layer = config .config_layer_stack - .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ true, + ) .into_iter() .rfind(|layer| layer.name == ConfigLayerSource::SessionFlags) .expect("expected a session flags layer"); @@ -630,7 +636,10 @@ enabled = false .expect("custom role should apply"); let plugins_manager = Arc::new(PluginsManager::new(home.path().to_path_buf())); - let skills_manager = SkillsManager::new(home.path().to_path_buf(), true); + let skills_manager = SkillsManager::new( + home.path().to_path_buf(), + /*bundled_skills_enabled*/ true, + ); let plugin_outcome = plugins_manager.plugins_for_config(&config); let effective_skill_roots = plugin_outcome.effective_skill_roots(); let skills_input = skills_load_input_from_config(&config, effective_skill_roots); diff --git a/codex-rs/core/src/auth_env_telemetry.rs b/codex-rs/core/src/auth_env_telemetry.rs index 85cd23fe06..cc5ffa1207 100644 --- a/codex-rs/core/src/auth_env_telemetry.rs +++ b/codex-rs/core/src/auth_env_telemetry.rs @@ -76,7 +76,8 @@ mod tests { supports_websockets: false, }; - let telemetry = collect_auth_env_telemetry(&provider, false); + let telemetry = + collect_auth_env_telemetry(&provider, /*codex_api_key_env_enabled*/ false); assert_eq!( telemetry.provider_env_key_name, diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index e38ff3a562..af5e3007b4 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -72,6 +72,7 @@ use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::W3cTraceContext; +use codex_tools::create_tools_json_for_responses_api; use eventsource_stream::Event; use eventsource_stream::EventStreamError; use futures::StreamExt; @@ -107,7 +108,6 @@ use crate::response_debug_context::extract_response_debug_context; use crate::response_debug_context::extract_response_debug_context_from_api_error; use crate::response_debug_context::telemetry_api_error_message; use crate::response_debug_context::telemetry_transport_error_message; -use crate::tools::spec::create_tools_json_for_responses_api; use crate::util::FeedbackRequestTags; use crate::util::emit_feedback_auth_recovery_tags; use crate::util::emit_feedback_request_tags_with_auth_env; diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 33a1f535c6..908dec56c6 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -1,10 +1,10 @@ -use crate::client_common::tools::ToolSpec; use crate::config::types::Personality; use crate::error::Result; pub use codex_api::common::ResponseEvent; use codex_protocol::models::BaseInstructions; use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::ResponseItem; +use codex_tools::ToolSpec; use futures::Stream; use serde::Deserialize; use serde_json::Value; @@ -157,158 +157,11 @@ fn strip_total_output_header(output: &str) -> Option<(&str, u32)> { } pub(crate) mod tools { - use crate::tools::spec::JsonSchema; - use codex_protocol::config_types::WebSearchContextSize; - use codex_protocol::config_types::WebSearchFilters as ConfigWebSearchFilters; - use codex_protocol::config_types::WebSearchUserLocation as ConfigWebSearchUserLocation; - use codex_protocol::config_types::WebSearchUserLocationType; - use serde::Deserialize; - use serde::Serialize; - use serde_json::Value; - - /// When serialized as JSON, this produces a valid "Tool" in the OpenAI - /// Responses API. - #[derive(Debug, Clone, Serialize, PartialEq)] - #[serde(tag = "type")] - pub(crate) enum ToolSpec { - #[serde(rename = "function")] - Function(ResponsesApiTool), - #[serde(rename = "tool_search")] - ToolSearch { - execution: String, - description: String, - parameters: JsonSchema, - }, - #[serde(rename = "local_shell")] - LocalShell {}, - #[serde(rename = "image_generation")] - ImageGeneration { output_format: String }, - // TODO: Understand why we get an error on web_search although the API docs say it's supported. - // https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses#:~:text=%7B%20type%3A%20%22web_search%22%20%7D%2C - // The `external_web_access` field determines whether the web search is over cached or live content. - // https://platform.openai.com/docs/guides/tools-web-search#live-internet-access - #[serde(rename = "web_search")] - WebSearch { - #[serde(skip_serializing_if = "Option::is_none")] - external_web_access: Option, - #[serde(skip_serializing_if = "Option::is_none")] - filters: Option, - #[serde(skip_serializing_if = "Option::is_none")] - user_location: Option, - #[serde(skip_serializing_if = "Option::is_none")] - search_context_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - search_content_types: Option>, - }, - #[serde(rename = "custom")] - Freeform(FreeformTool), - } - - impl ToolSpec { - pub(crate) fn name(&self) -> &str { - match self { - ToolSpec::Function(tool) => tool.name.as_str(), - ToolSpec::ToolSearch { .. } => "tool_search", - ToolSpec::LocalShell {} => "local_shell", - ToolSpec::ImageGeneration { .. } => "image_generation", - ToolSpec::WebSearch { .. } => "web_search", - ToolSpec::Freeform(tool) => tool.name.as_str(), - } - } - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - pub(crate) struct ResponsesApiWebSearchFilters { - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) allowed_domains: Option>, - } - - impl From for ResponsesApiWebSearchFilters { - fn from(filters: ConfigWebSearchFilters) -> Self { - Self { - allowed_domains: filters.allowed_domains, - } - } - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - pub(crate) struct ResponsesApiWebSearchUserLocation { - #[serde(rename = "type")] - pub(crate) r#type: WebSearchUserLocationType, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) country: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) region: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) city: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) timezone: Option, - } - - impl From for ResponsesApiWebSearchUserLocation { - fn from(user_location: ConfigWebSearchUserLocation) -> Self { - Self { - r#type: user_location.r#type, - country: user_location.country, - region: user_location.region, - city: user_location.city, - timezone: user_location.timezone, - } - } - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct FreeformTool { - pub(crate) name: String, - pub(crate) description: String, - pub(crate) format: FreeformToolFormat, - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct FreeformToolFormat { - pub(crate) r#type: String, - pub(crate) syntax: String, - pub(crate) definition: String, - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - pub struct ResponsesApiTool { - pub(crate) name: String, - pub(crate) description: String, - /// TODO: Validation. When strict is set to true, the JSON schema, - /// `required` and `additional_properties` must be present. All fields in - /// `properties` must be present in `required`. - pub(crate) strict: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) defer_loading: Option, - pub(crate) parameters: JsonSchema, - #[serde(skip)] - pub(crate) output_schema: Option, - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - #[serde(tag = "type")] - pub(crate) enum ToolSearchOutputTool { - #[allow(dead_code)] - #[serde(rename = "function")] - Function(ResponsesApiTool), - #[serde(rename = "namespace")] - Namespace(ResponsesApiNamespace), - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - pub(crate) struct ResponsesApiNamespace { - pub(crate) name: String, - pub(crate) description: String, - pub(crate) tools: Vec, - } - - #[derive(Debug, Clone, Serialize, PartialEq)] - #[serde(tag = "type")] - pub(crate) enum ResponsesApiNamespaceTool { - #[serde(rename = "function")] - Function(ResponsesApiTool), - } + pub(crate) use codex_tools::FreeformTool; + pub(crate) use codex_tools::FreeformToolFormat; + pub(crate) use codex_tools::ResponsesApiTool; + pub(crate) use codex_tools::ToolSearchOutputTool; + pub(crate) use codex_tools::ToolSpec; } pub struct ResponseStream { diff --git a/codex-rs/core/src/client_common_tests.rs b/codex-rs/core/src/client_common_tests.rs index 2f2305c7ab..16a37796ad 100644 --- a/codex-rs/core/src/client_common_tests.rs +++ b/codex-rs/core/src/client_common_tests.rs @@ -52,7 +52,8 @@ fn serializes_text_schema_with_strict_format() { "required": ["answer"], }); let text_controls = - create_text_param_for_request(None, &Some(schema.clone())).expect("text controls"); + create_text_param_for_request(/*verbosity*/ None, &Some(schema.clone())) + .expect("text controls"); let req = ResponsesApiRequest { model: "gpt-5.1".to_string(), @@ -197,49 +198,3 @@ fn reserializes_shell_outputs_for_function_and_custom_tool_calls() { ] ); } - -#[test] -fn tool_search_output_namespace_serializes_with_deferred_child_tools() { - let namespace = tools::ToolSearchOutputTool::Namespace(tools::ResponsesApiNamespace { - name: "mcp__codex_apps__calendar".to_string(), - description: "Plan events".to_string(), - tools: vec![tools::ResponsesApiNamespaceTool::Function( - tools::ResponsesApiTool { - name: "create_event".to_string(), - description: "Create a calendar event.".to_string(), - strict: false, - defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { - properties: Default::default(), - required: None, - additional_properties: None, - }, - output_schema: None, - }, - )], - }); - - let value = serde_json::to_value(namespace).expect("serialize namespace"); - - assert_eq!( - value, - serde_json::json!({ - "type": "namespace", - "name": "mcp__codex_apps__calendar", - "description": "Plan events", - "tools": [ - { - "type": "function", - "name": "create_event", - "description": "Create a calendar event.", - "strict": false, - "defer_loading": true, - "parameters": { - "type": "object", - "properties": {} - } - } - ] - }) - ); -} diff --git a/codex-rs/core/src/client_tests.rs b/codex-rs/core/src/client_tests.rs index 2c07b4fd1d..b7d8075f0d 100644 --- a/codex-rs/core/src/client_tests.rs +++ b/codex-rs/core/src/client_tests.rs @@ -16,14 +16,14 @@ fn test_model_client(session_source: SessionSource) -> ModelClient { crate::model_provider_info::WireApi::Responses, ); ModelClient::new( - None, + /*auth_manager*/ None, ThreadId::new(), provider, session_source, - None, - false, - false, - None, + /*model_verbosity*/ None, + /*enable_request_compression*/ false, + /*include_timing_metrics*/ false, + /*beta_features_header*/ None, ) } @@ -62,11 +62,11 @@ fn test_session_telemetry() -> SessionTelemetry { ThreadId::new(), "gpt-test", "gpt-test", - None, - None, - None, + /*account_id*/ None, + /*account_email*/ None, + /*auth_mode*/ None, "test-originator".to_string(), - false, + /*log_user_prompts*/ false, "test-terminal".to_string(), SessionSource::Cli, ) @@ -91,7 +91,12 @@ async fn summarize_memories_returns_empty_for_empty_input() { let session_telemetry = test_session_telemetry(); let output = client - .summarize_memories(Vec::new(), &model_info, None, &session_telemetry) + .summarize_memories( + Vec::new(), + &model_info, + /*effort*/ None, + &session_telemetry, + ) .await .expect("empty summarize request should succeed"); assert_eq!(output.len(), 0); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ed24d28466..2c70aad7a7 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -11,6 +11,8 @@ use crate::CodexAuth; use crate::SandboxState; use crate::agent::AgentControl; use crate::agent::AgentStatus; +use crate::agent::Mailbox; +use crate::agent::MailboxReceiver; use crate::agent::agent_status_from_event; use crate::apps::render_apps_section; use crate::auth_env_telemetry::collect_auth_env_telemetry; @@ -97,6 +99,7 @@ use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::protocol::FileChange; use codex_protocol::protocol::HasLegacyEvent; +use codex_protocol::protocol::InterAgentCommunication; use codex_protocol::protocol::ItemCompletedEvent; use codex_protocol::protocol::ItemStartedEvent; use codex_protocol::protocol::RawResponseItemEvent; @@ -118,6 +121,7 @@ use codex_protocol::request_user_input::RequestUserInputResponse; use codex_rmcp_client::ElicitationResponse; use codex_rmcp_client::OAuthCredentialsStoreMode; use codex_terminal_detection::user_agent; +use codex_tools::filter_tool_suggest_discoverable_tools_for_client; use codex_utils_output_truncation::TruncationPolicy; use codex_utils_stream_parser::AssistantTextChunk; use codex_utils_stream_parser::AssistantTextStreamParser; @@ -806,7 +810,9 @@ pub(crate) struct Session { pending_mcp_server_refresh_config: Mutex>, pub(crate) conversation: Arc, pub(crate) active_turn: Mutex>, - idle_pending_input: Mutex>, + mailbox: Mailbox, + mailbox_rx: Mutex, + idle_pending_input: Mutex>, // TODO (jif) merge with mailbox! pub(crate) guardian_review_session: GuardianReviewSessionManager, pub(crate) services: SessionServices, js_repl: Arc, @@ -1029,8 +1035,16 @@ impl TurnContext { .network .as_ref()?; Some(TurnContextNetworkItem { - allowed_domains: network.allowed_domains.clone().unwrap_or_default(), - denied_domains: network.denied_domains.clone().unwrap_or_default(), + allowed_domains: network + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains) + .unwrap_or_default(), + denied_domains: network + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::denied_domains) + .unwrap_or_default(), }) } } @@ -1899,6 +1913,7 @@ impl Session { let (out_of_band_elicitation_paused, _out_of_band_elicitation_paused_rx) = watch::channel(false); + let (mailbox, mailbox_rx) = Mailbox::new(); let sess = Arc::new(Session { conversation_id, tx_event: tx_event.clone(), @@ -1909,6 +1924,8 @@ impl Session { pending_mcp_server_refresh_config: Mutex::new(None), conversation: Arc::new(RealtimeConversationManager::new()), active_turn: Mutex::new(None), + mailbox, + mailbox_rx: Mutex::new(mailbox_rx), idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: GuardianReviewSessionManager::default(), services, @@ -3949,6 +3966,18 @@ impl Session { } } + pub(crate) fn subscribe_mailbox_seq(&self) -> watch::Receiver { + self.mailbox.subscribe() + } + + pub(crate) fn enqueue_mailbox_communication(&self, communication: InterAgentCommunication) { + self.mailbox.send(communication); + } + + pub(crate) async fn has_trigger_turn_mailbox_items(&self) -> bool { + self.mailbox_rx.lock().await.has_pending_trigger_turn() + } + pub async fn prepend_pending_input(&self, input: Vec) -> Result<(), ()> { let mut active = self.active_turn.lock().await; match active.as_mut() { @@ -3962,28 +3991,37 @@ impl Session { } pub async fn get_pending_input(&self) -> Vec { - let mut active = self.active_turn.lock().await; - match active.as_mut() { - Some(at) => { - let mut ts = at.turn_state.lock().await; - ts.take_pending_input() + let pending_input = { + let mut active = self.active_turn.lock().await; + match active.as_mut() { + Some(at) => { + let mut ts = at.turn_state.lock().await; + ts.take_pending_input() + } + None => Vec::new(), } - None => Vec::with_capacity(0), - } - } - - pub(crate) async fn pending_input_snapshot(&self) -> Vec { - let active = self.active_turn.lock().await; - match active.as_ref() { - Some(at) => { - let ts = at.turn_state.lock().await; - ts.pending_input_snapshot() - } - None => Vec::with_capacity(0), + }; + let mailbox_items = { + let mut mailbox_rx = self.mailbox_rx.lock().await; + mailbox_rx + .drain() + .into_iter() + .map(|mail| mail.to_response_input_item()) + .collect::>() + }; + if pending_input.is_empty() { + mailbox_items + } else if mailbox_items.is_empty() { + pending_input + } else { + let mut pending_input = pending_input; + pending_input.extend(mailbox_items); + pending_input } } /// Queue response items to be injected into the next active turn created for this session. + #[cfg(test)] pub(crate) async fn queue_response_items_for_next_turn(&self, items: Vec) { if items.is_empty() { return; @@ -3997,17 +4035,14 @@ impl Session { std::mem::take(&mut *self.idle_pending_input.lock().await) } - pub(crate) async fn queued_response_items_for_next_turn_snapshot( - &self, - ) -> Vec { - self.idle_pending_input.lock().await.clone() - } - pub(crate) async fn has_queued_response_items_for_next_turn(&self) -> bool { !self.idle_pending_input.lock().await.is_empty() } pub async fn has_pending_input(&self) -> bool { + if self.mailbox_rx.lock().await.has_pending() { + return true; + } let active = self.active_turn.lock().await; match active.as_ref() { Some(at) => { @@ -4394,10 +4429,6 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv handlers::reload_user_config(&sess).await; false } - Op::ListCustomPrompts => { - handlers::list_custom_prompts(&sess, sub.id.clone()).await; - false - } Op::ListSkills { cwds, force_reload } => { handlers::list_skills(&sess, sub.id.clone(), cwds, force_reload).await; false @@ -4523,13 +4554,11 @@ mod handlers { use crate::tasks::UserShellCommandMode; use crate::tasks::UserShellCommandTask; use crate::tasks::execute_user_shell_command; - use codex_protocol::custom_prompts::CustomPrompt; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::InterAgentCommunication; - use codex_protocol::protocol::ListCustomPromptsResponseEvent; use codex_protocol::protocol::ListSkillsResponseEvent; use codex_protocol::protocol::McpServerRefreshConfig; use codex_protocol::protocol::Op; @@ -4679,18 +4708,10 @@ mod handlers { sub_id: String, communication: InterAgentCommunication, ) { - let pending_item = communication.to_response_input_item(); - if let Ok(()) = sess.inject_response_items(vec![pending_item.clone()]).await { - return; - } - - sess.queue_response_items_for_next_turn(vec![pending_item]) - .await; - if communication.trigger_turn { - let turn_context = sess.new_default_turn_with_sub_id(sub_id).await; - sess.maybe_emit_unknown_model_warning_for_turn(turn_context.as_ref()) - .await; - sess.spawn_task(turn_context, Vec::new(), crate::tasks::RegularTask::new()) + let trigger_turn = communication.trigger_turn; + sess.enqueue_mailbox_communication(communication); + if trigger_turn { + sess.ensure_task_for_pending_inputs_with_sub_id(sub_id) .await; } } @@ -4916,23 +4937,6 @@ mod handlers { sess.send_event_raw(event).await; } - pub async fn list_custom_prompts(sess: &Session, sub_id: String) { - let custom_prompts: Vec = - if let Some(dir) = crate::custom_prompts::default_prompts_dir() { - crate::custom_prompts::discover_prompts_in(&dir).await - } else { - Vec::new() - }; - - let event = Event { - id: sub_id, - msg: EventMsg::ListCustomPromptsResponse(ListCustomPromptsResponseEvent { - custom_prompts, - }), - }; - sess.send_event_raw(event).await; - } - pub async fn list_skills( sess: &Session, sub_id: String, @@ -6543,7 +6547,7 @@ pub(crate) async fn built_tools( ) .await .map(|discoverable_tools| { - crate::tools::discoverable::filter_tool_suggest_discoverable_tools_for_client( + filter_tool_suggest_discoverable_tools_for_client( discoverable_tools, turn_context.app_server_client_name.as_deref(), ) @@ -6854,7 +6858,6 @@ fn realtime_text_for_event(msg: &EventMsg) -> Option { | EventMsg::TurnDiff(_) | EventMsg::GetHistoryEntryResponse(_) | EventMsg::McpListToolsResponse(_) - | EventMsg::ListCustomPromptsResponse(_) | EventMsg::ListSkillsResponse(_) | EventMsg::SkillsUpdateAvailable | EventMsg::PlanUpdate(_) diff --git a/codex-rs/core/src/codex/rollout_reconstruction_tests.rs b/codex-rs/core/src/codex/rollout_reconstruction_tests.rs index e468bfceb5..86abfa6756 100644 --- a/codex-rs/core/src/codex/rollout_reconstruction_tests.rs +++ b/codex-rs/core/src/codex/rollout_reconstruction_tests.rs @@ -41,7 +41,7 @@ fn inter_agent_assistant_message(text: &str) -> ResponseItem { AgentPath::root().join("worker").unwrap(), Vec::new(), text.to_string(), - true, + /*trigger_turn*/ true, ); ResponseItem::Message { id: None, diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 5a556f2071..f5e5d4463e 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -5,6 +5,8 @@ use crate::config::test_config; use crate::config_loader::ConfigLayerStack; use crate::config_loader::ConfigLayerStackOrdering; use crate::config_loader::NetworkConstraints; +use crate::config_loader::NetworkDomainPermissionToml; +use crate::config_loader::NetworkDomainPermissionsToml; use crate::config_loader::RequirementSource; use crate::config_loader::Sourced; use crate::exec::ExecCapturePolicy; @@ -245,17 +247,17 @@ async fn interrupting_regular_turn_waiting_on_startup_prewarm_emits_turn_aborted fn test_model_client_session() -> crate::client::ModelClientSession { crate::client::ModelClient::new( - None, + /*auth_manager*/ None, ThreadId::try_from("00000000-0000-4000-8000-000000000001") .expect("test thread id should be valid"), crate::model_provider_info::ModelProviderInfo::create_openai_provider( - /* base_url */ None, + /* base_url */ /*base_url*/ None, ), codex_protocol::protocol::SessionSource::Exec, - None, - false, - false, - None, + /*model_verbosity*/ None, + /*enable_request_compression*/ false, + /*include_timing_metrics*/ false, + /*beta_features_header*/ None, ) .new_session() } @@ -311,7 +313,7 @@ fn make_connector(id: &str, name: &str) -> AppInfo { #[test] fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text() { - let mut parsers = AssistantMessageStreamParsers::new(false); + let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false); let item_id = "msg-1"; let seeded = parsers.seed_item_text(item_id, "hello doc"); @@ -328,7 +330,7 @@ fn assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text() #[test] fn assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tail() { - let mut parsers = AssistantMessageStreamParsers::new(false); + let mut parsers = AssistantMessageStreamParsers::new(/*plan_mode*/ false); let item_id = "msg-1"; let seeded = parsers.seed_item_text(item_id, "hello anyhow::Result<()> { let spec = crate::config::NetworkProxySpec::from_config_and_constraints( NetworkProxyConfig::default(), - None, + /*requirements*/ None, &SandboxPolicy::new_workspace_write_policy(), )?; let mut exec_policy = Policy::empty(); @@ -455,24 +457,24 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho "example.com", NetworkRuleProtocol::Https, Decision::Allow, - None, + /*justification*/ None, )?; let (started_proxy, _) = Session::start_managed_network_proxy( &spec, &exec_policy, &SandboxPolicy::new_workspace_write_policy(), - None, - None, - false, + /*network_policy_decider*/ None, + /*blocked_request_observer*/ None, + /*managed_network_requirements_enabled*/ false, crate::config::NetworkProxyAuditMetadata::default(), ) .await?; let current_cfg = started_proxy.proxy().current_cfg().await?; assert_eq!( - current_cfg.network.allowed_domains, - vec!["example.com".to_string()] + current_cfg.network.allowed_domains(), + Some(vec!["example.com".to_string()]) ); Ok(()) } @@ -483,7 +485,12 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules() let spec = crate::config::NetworkProxySpec::from_config_and_constraints( NetworkProxyConfig::default(), Some(NetworkConstraints { - allowed_domains: Some(vec!["managed.example.com".to_string()]), + domains: Some(NetworkDomainPermissionsToml { + entries: std::collections::BTreeMap::from([( + "managed.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + }), managed_allowed_domains_only: Some(true), ..Default::default() }), @@ -494,24 +501,24 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules() "example.com", NetworkRuleProtocol::Https, Decision::Allow, - None, + /*justification*/ None, )?; let (started_proxy, _) = Session::start_managed_network_proxy( &spec, &exec_policy, &SandboxPolicy::new_workspace_write_policy(), - None, - None, - false, + /*network_policy_decider*/ None, + /*blocked_request_observer*/ None, + /*managed_network_requirements_enabled*/ false, crate::config::NetworkProxyAuditMetadata::default(), ) .await?; let current_cfg = started_proxy.proxy().current_cfg().await?; assert_eq!( - current_cfg.network.allowed_domains, - vec!["managed.example.com".to_string()] + current_cfg.network.allowed_domains(), + Some(vec!["managed.example.com".to_string()]) ); Ok(()) } @@ -730,7 +737,9 @@ fn non_app_mcp_tools_remain_visible_without_search_selection() { ), ( "mcp__rmcp__echo".to_string(), - make_mcp_tool("rmcp", "echo", None, None), + make_mcp_tool( + "rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None, + ), ), ]); @@ -778,7 +787,9 @@ fn search_tool_selection_keeps_codex_apps_tools_without_mentions() { ), ( "mcp__rmcp__echo".to_string(), - make_mcp_tool("rmcp", "echo", None, None), + make_mcp_tool( + "rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None, + ), ), ]); @@ -828,7 +839,9 @@ fn apps_mentions_add_codex_apps_tools_to_search_selected_set() { ), ( "mcp__rmcp__echo".to_string(), - make_mcp_tool("rmcp", "echo", None, None), + make_mcp_tool( + "rmcp", "echo", /*connector_id*/ None, /*connector_name*/ None, + ), ), ]); @@ -1172,7 +1185,13 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< codex_config::Constrained::allow_any(AskForApproval::UnlessTrusted); let forked = initial .thread_manager - .fork_thread(usize::MAX, fork_config, rollout_path, false, None) + .fork_thread( + usize::MAX, + fork_config, + rollout_path, + /*persist_extended_history*/ false, + /*parent_trace*/ None, + ) .await?; let collaboration_mode = CollaborationMode { @@ -1344,7 +1363,7 @@ async fn thread_rollback_drops_last_turn_from_history() { state.set_reference_context_item(Some(tc.to_turn_context_item())); } - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let rollback_event = wait_for_thread_rolled_back(&rx).await; assert_eq!(rollback_event.num_turns, 1); @@ -1391,7 +1410,7 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns() .collect(); sess.persist_rollout_items(&rollout_items).await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 99).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 99).await; let rollback_event = wait_for_thread_rolled_back(&rx).await; assert_eq!(rollback_event.num_turns, 99); @@ -1408,7 +1427,7 @@ async fn thread_rollback_fails_without_persisted_rollout_path() { sess.record_into_history(&initial_context, tc.as_ref()) .await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let error_event = wait_for_thread_rollback_failed(&rx).await; assert_eq!( @@ -1502,7 +1521,7 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context })) .await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let rollback_event = wait_for_thread_rolled_back(&rx).await; assert_eq!(rollback_event.num_turns, 1); @@ -1610,7 +1629,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio ) .await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let rollback_event = wait_for_thread_rolled_back(&rx).await; assert_eq!(rollback_event.num_turns, 1); @@ -1688,10 +1707,10 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { ]) .await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let first_rollback = wait_for_thread_rolled_back(&rx).await; assert_eq!(first_rollback.num_turns, 1); - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let second_rollback = wait_for_thread_rolled_back(&rx).await; assert_eq!(second_rollback.num_turns, 1); @@ -1726,7 +1745,7 @@ async fn thread_rollback_fails_when_turn_in_progress() { .await; *sess.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - handlers::thread_rollback(&sess, "sub-1".to_string(), 1).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 1).await; let error_event = wait_for_thread_rollback_failed(&rx).await; assert_eq!( @@ -1746,7 +1765,7 @@ async fn thread_rollback_fails_when_num_turns_is_zero() { sess.record_into_history(&initial_context, tc.as_ref()) .await; - handlers::thread_rollback(&sess, "sub-1".to_string(), 0).await; + handlers::thread_rollback(&sess, "sub-1".to_string(), /*num_turns*/ 0).await; let error_event = wait_for_thread_rollback_failed(&rx).await; assert_eq!(error_event.message, "num_turns must be >= 1"); @@ -2145,14 +2164,14 @@ async fn attach_rollout_recorder(session: &Arc) -> PathBuf { config.as_ref(), RolloutRecorderParams::new( ThreadId::default(), - None, + /*forked_from_id*/ None, SessionSource::Exec, BaseInstructions::default(), Vec::new(), EventPersistenceMode::Limited, ), - None, - None, + /*state_db_ctx*/ None, + /*state_builder*/ None, ) .await .expect("create rollout recorder"); @@ -2191,11 +2210,11 @@ fn session_telemetry( conversation_id, ModelsManager::get_model_offline_for_tests(config.model.as_deref()).as_str(), model_info.slug.as_str(), - None, + /*account_id*/ None, Some("test@test.com".to_string()), Some(TelemetryAuthMode::Chatgpt), "test_originator".to_string(), - false, + /*log_user_prompts*/ false, "test".to_string(), session_source, ) @@ -2318,7 +2337,7 @@ async fn new_default_turn_uses_config_aware_skills_for_role_overrides() { .skills_manager .skills_for_cwd( &crate::skills_load_input_from_config(&parent_config, Vec::new()), - true, + /*force_reload*/ true, ) .await; let parent_skill = parent_outcome @@ -2466,7 +2485,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() { let models_manager = Arc::new(ModelsManager::new( config.codex_home.clone(), auth_manager.clone(), - None, + /*model_catalog*/ None, CollaborationModesConfig::default(), )); let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref()); @@ -2515,7 +2534,10 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() { let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit); let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone())); let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager))); - let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true)); + let skills_manager = Arc::new(SkillsManager::new( + config.codex_home.clone(), + /*bundled_skills_enabled*/ true, + )); let result = Session::new( session_configuration, Arc::clone(&config), @@ -2556,7 +2578,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { let models_manager = Arc::new(ModelsManager::new( config.codex_home.clone(), auth_manager.clone(), - None, + /*model_catalog*/ None, CollaborationModesConfig::default(), )); let agent_control = AgentControl::default(); @@ -2619,7 +2641,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { let state = SessionState::new(session_configuration.clone()); let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone())); let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager))); - let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true)); + let skills_manager = Arc::new(SkillsManager::new( + config.codex_home.clone(), + /*bundled_skills_enabled*/ true, + )); let network_approval = Arc::new(NetworkApprovalService::default()); let environment = Arc::new( codex_exec_server::Environment::create(/*exec_server_url*/ None) @@ -2705,13 +2730,14 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { per_turn_config, model_info, &models_manager, - None, + /*network*/ None, environment, "turn_id".to_string(), Arc::clone(&js_repl), skills_outcome, ); + let (mailbox, mailbox_rx) = crate::agent::Mailbox::new(); let session = Session { conversation_id, tx_event, @@ -2722,6 +2748,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { pending_mcp_server_refresh_config: Mutex::new(None), conversation: Arc::new(RealtimeConversationManager::new()), active_turn: Mutex::new(None), + mailbox, + mailbox_rx: Mutex::new(mailbox_rx), idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(), services, @@ -3393,7 +3421,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( let models_manager = Arc::new(ModelsManager::new( config.codex_home.clone(), auth_manager.clone(), - None, + /*model_catalog*/ None, CollaborationModesConfig::default(), )); let agent_control = AgentControl::default(); @@ -3456,7 +3484,10 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( let state = SessionState::new(session_configuration.clone()); let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone())); let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager))); - let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true)); + let skills_manager = Arc::new(SkillsManager::new( + config.codex_home.clone(), + /*bundled_skills_enabled*/ true, + )); let network_approval = Arc::new(NetworkApprovalService::default()); let environment = Arc::new( codex_exec_server::Environment::create(/*exec_server_url*/ None) @@ -3542,13 +3573,14 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( per_turn_config, model_info, &models_manager, - None, + /*network*/ None, environment, "turn_id".to_string(), Arc::clone(&js_repl), skills_outcome, )); + let (mailbox, mailbox_rx) = crate::agent::Mailbox::new(); let session = Arc::new(Session { conversation_id, tx_event, @@ -3559,6 +3591,8 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( pending_mcp_server_refresh_config: Mutex::new(None), conversation: Arc::new(RealtimeConversationManager::new()), active_turn: Mutex::new(None), + mailbox, + mailbox_rx: Mutex::new(mailbox_rx), idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(), services, @@ -3652,7 +3686,8 @@ async fn record_model_warning_appends_user_message() { #[tokio::test] async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() { let (sess, tc, _rx) = make_session_and_context_with_rx().await; - sess.set_previous_turn_settings(None).await; + sess.set_previous_turn_settings(/*previous_turn_settings*/ None) + .await; let input = vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), @@ -3687,15 +3722,28 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes( let mut requirements = config.config_layer_stack.requirements().clone(); requirements.network = Some(Sourced::new( NetworkConstraints { - allowed_domains: Some(vec!["api.example.com".to_string()]), - denied_domains: Some(vec!["blocked.example.com".to_string()]), + domains: Some(NetworkDomainPermissionsToml { + entries: std::collections::BTreeMap::from([ + ( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "blocked.example.com".to_string(), + NetworkDomainPermissionToml::Deny, + ), + ]), + }), ..Default::default() }, RequirementSource::CloudRequirements, )); let layers = config .config_layer_stack - .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ true, + ) .into_iter() .cloned() .collect(); @@ -3892,7 +3940,7 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist revised_prompt: Some("a tiny blue square".to_string()), result: "Zm9v".to_string(), }], - None, + /*reference_context_item*/ None, ) .await; @@ -3946,7 +3994,7 @@ async fn handle_output_item_done_records_image_save_history_message() { tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)), cancellation_token: CancellationToken::new(), }; - handle_output_item_done(&mut ctx, item.clone(), None) + handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None) .await .expect("image generation item should succeed"); @@ -4003,7 +4051,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() { tool_runtime: test_tool_runtime(Arc::clone(&session), Arc::clone(&turn_context)), cancellation_token: CancellationToken::new(), }; - handle_output_item_done(&mut ctx, item.clone(), None) + handle_output_item_done(&mut ctx, item.clone(), /*previously_active_item*/ None) .await .expect("image generation item should still complete"); @@ -4095,10 +4143,13 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co .await; { let mut state = session.state.lock().await; - state.set_reference_context_item(None); + state.set_reference_context_item(/*item*/ None); } session - .replace_history(vec![compacted_summary.clone()], None) + .replace_history( + vec![compacted_summary.clone()], + /*reference_context_item*/ None, + ) .await; session @@ -4133,14 +4184,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline config.as_ref(), RolloutRecorderParams::new( ThreadId::default(), - None, + /*forked_from_id*/ None, SessionSource::Exec, BaseInstructions::default(), Vec::new(), EventPersistenceMode::Limited, ), - None, - None, + /*state_db_ctx*/ None, + /*state_builder*/ None, ) .await .expect("create rollout recorder"); @@ -4230,14 +4281,14 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei config.as_ref(), RolloutRecorderParams::new( ThreadId::default(), - None, + /*forked_from_id*/ None, SessionSource::Exec, BaseInstructions::default(), Vec::new(), EventPersistenceMode::Limited, ), - None, - None, + /*state_db_ctx*/ None, + /*state_builder*/ None, ) .await .expect("create rollout recorder"); @@ -4259,7 +4310,7 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei .await; { let mut state = session.state.lock().await; - state.set_reference_context_item(None); + state.set_reference_context_item(/*item*/ None); } session @@ -4298,7 +4349,7 @@ async fn run_user_shell_command_does_not_set_reference_context_item() { let (session, _turn_context, rx) = make_session_and_context_with_rx().await; { let mut state = session.state.lock().await; - state.set_reference_context_item(None); + state.set_reference_context_item(/*item*/ None); } handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string()) @@ -4451,7 +4502,8 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() .await .expect("inject pending input into active turn"); - sess.on_task_finished(Arc::clone(&tc), None).await; + sess.on_task_finished(Arc::clone(&tc), /*last_agent_message*/ None) + .await; let history = sess.clone_history().await; let expected = ResponseItem::Message { @@ -4543,7 +4595,7 @@ async fn steer_input_requires_active_turn() { }]; let err = sess - .steer_input(input, None) + .steer_input(input, /*expected_turn_id*/ None) .await .expect_err("steering without active turn should fail"); diff --git a/codex-rs/core/src/codex_tests_guardian.rs b/codex-rs/core/src/codex_tests_guardian.rs index 18eb3d177b..f5698d58ff 100644 --- a/codex-rs/core/src/codex_tests_guardian.rs +++ b/codex-rs/core/src/codex_tests_guardian.rs @@ -425,11 +425,14 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() { let models_manager = Arc::new(ModelsManager::new( config.codex_home.clone(), auth_manager.clone(), - None, + /*model_catalog*/ None, CollaborationModesConfig::default(), )); let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone())); - let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone(), true)); + let skills_manager = Arc::new(SkillsManager::new( + config.codex_home.clone(), + /*bundled_skills_enabled*/ true, + )); let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager))); let skills_watcher = Arc::new(SkillsWatcher::noop()); diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 0635718c9c..033f07aa15 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -180,10 +180,7 @@ impl CodexThread { .session .queue_response_items_for_next_turn(items) .await; - self.codex - .session - .ensure_task_for_queued_response_items() - .await; + self.codex.session.ensure_task_for_pending_inputs().await; } Ok(submission_id) diff --git a/codex-rs/core/src/commit_attribution_tests.rs b/codex-rs/core/src/commit_attribution_tests.rs index be7661a604..8b2ce27e1f 100644 --- a/codex-rs/core/src/commit_attribution_tests.rs +++ b/codex-rs/core/src/commit_attribution_tests.rs @@ -11,7 +11,7 @@ fn blank_attribution_disables_trailer_prompt() { #[test] fn default_attribution_uses_codex_trailer() { assert_eq!( - build_commit_message_trailer(None).as_deref(), + build_commit_message_trailer(/*config_attribution*/ None).as_deref(), Some("Co-authored-by: Codex ") ); } @@ -19,7 +19,7 @@ fn default_attribution_uses_codex_trailer() { #[test] fn resolve_value_handles_default_custom_and_blank() { assert_eq!( - resolve_attribution_value(None), + resolve_attribution_value(/*config_attribution*/ None), Some("Codex ".to_string()) ); assert_eq!( diff --git a/codex-rs/core/src/compact_tests.rs b/codex-rs/core/src/compact_tests.rs index 92e889d647..cecf9ce9d7 100644 --- a/codex-rs/core/src/compact_tests.rs +++ b/codex-rs/core/src/compact_tests.rs @@ -216,8 +216,11 @@ async fn process_compacted_history_replaces_developer_messages() { phase: None, }, ]; - let (refreshed, mut expected) = - process_compacted_history_with_test_session(compacted_history, None).await; + let (refreshed, mut expected) = process_compacted_history_with_test_session( + compacted_history, + /*previous_turn_settings*/ None, + ) + .await; expected.push(ResponseItem::Message { id: None, role: "user".to_string(), @@ -241,8 +244,11 @@ async fn process_compacted_history_reinjects_full_initial_context() { end_turn: None, phase: None, }]; - let (refreshed, mut expected) = - process_compacted_history_with_test_session(compacted_history, None).await; + let (refreshed, mut expected) = process_compacted_history_with_test_session( + compacted_history, + /*previous_turn_settings*/ None, + ) + .await; expected.push(ResponseItem::Message { id: None, role: "user".to_string(), @@ -317,8 +323,11 @@ keep me updated phase: None, }, ]; - let (refreshed, mut expected) = - process_compacted_history_with_test_session(compacted_history, None).await; + let (refreshed, mut expected) = process_compacted_history_with_test_session( + compacted_history, + /*previous_turn_settings*/ None, + ) + .await; expected.push(ResponseItem::Message { id: None, role: "user".to_string(), @@ -363,8 +372,11 @@ async fn process_compacted_history_inserts_context_before_last_real_user_message }, ]; - let (refreshed, initial_context) = - process_compacted_history_with_test_session(compacted_history, None).await; + let (refreshed, initial_context) = process_compacted_history_with_test_session( + compacted_history, + /*previous_turn_settings*/ None, + ) + .await; let mut expected = vec![ ResponseItem::Message { id: None, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 4d6527ab80..00227fe2b3 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -306,7 +306,9 @@ enabled = true proxy_url = "http://127.0.0.1:43128" enable_socks5 = false allow_upstream_proxy = false -allowed_domains = ["openai.com"] + +[permissions.workspace.network.domains] +"openai.com" = "allow" "#; let cfg: ConfigToml = toml::from_str(toml).expect("TOML deserialization should succeed for permissions profiles"); @@ -343,9 +345,13 @@ allowed_domains = ["openai.com"] dangerously_allow_non_loopback_proxy: None, dangerously_allow_all_unix_sockets: None, mode: None, - allowed_domains: Some(vec!["openai.com".to_string()]), - denied_domains: None, - allow_unix_sockets: None, + domains: Some(NetworkDomainPermissionsToml { + entries: BTreeMap::from([( + "openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + }), + unix_sockets: None, allow_local_binding: None, }), }, @@ -421,7 +427,12 @@ fn permissions_profiles_network_disabled_by_default_does_not_start_proxy() -> st )]), }), network: Some(NetworkToml { - allowed_domains: Some(vec!["openai.com".to_string()]), + domains: Some(NetworkDomainPermissionsToml { + entries: BTreeMap::from([( + "openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + }), ..Default::default() }), }, @@ -496,7 +507,7 @@ fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Re }, FileSystemSandboxEntry { path: FileSystemPath::Special { - value: FileSystemSpecialPath::project_roots(None), + value: FileSystemSpecialPath::project_roots(/*subpath*/ None), }, access: FileSystemAccessMode::Write, }, @@ -698,7 +709,10 @@ fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> { config.permissions.file_system_sandbox_policy, FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry { path: FileSystemPath::Special { - value: FileSystemSpecialPath::unknown(":future_special_path", None), + value: FileSystemSpecialPath::unknown( + ":future_special_path", + /*subpath*/ None + ), }, access: FileSystemAccessMode::Read, }]), @@ -965,10 +979,10 @@ network_access = false # This should be ignored. let sandbox_mode_override = None; let resolution = sandbox_full_access_cfg.derive_sandbox_policy( sandbox_mode_override, - None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), - None, + /*sandbox_policy_constraint*/ None, ); assert_eq!(resolution, SandboxPolicy::DangerFullAccess); @@ -984,10 +998,10 @@ network_access = true # This should be ignored. let sandbox_mode_override = None; let resolution = sandbox_read_only_cfg.derive_sandbox_policy( sandbox_mode_override, - None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), - None, + /*sandbox_policy_constraint*/ None, ); assert_eq!(resolution, SandboxPolicy::new_read_only_policy()); @@ -1011,10 +1025,10 @@ exclude_slash_tmp = true let sandbox_mode_override = None; let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy( sandbox_mode_override, - None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), - None, + /*sandbox_policy_constraint*/ None, ); if cfg!(target_os = "windows") { assert_eq!(resolution, SandboxPolicy::new_read_only_policy()); @@ -1053,10 +1067,10 @@ trust_level = "trusted" let sandbox_mode_override = None; let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy( sandbox_mode_override, - None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), - None, + /*sandbox_policy_constraint*/ None, ); if cfg!(target_os = "windows") { assert_eq!(resolution, SandboxPolicy::new_read_only_policy()); @@ -1234,7 +1248,7 @@ fn filter_mcp_servers_by_allowlist_allows_all_when_unset() { ("server-b".to_string(), http_mcp("https://example.com/b")), ]); - filter_mcp_servers_by_requirements(&mut servers, None); + filter_mcp_servers_by_requirements(&mut servers, /*mcp_requirements*/ None); assert_eq!( servers @@ -1863,7 +1877,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -1893,7 +1907,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> { let empty = BTreeMap::new(); apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(empty.clone())], )?; let loaded = load_global_mcp_servers(codex_home.path()).await?; @@ -2090,7 +2104,7 @@ async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2163,7 +2177,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2216,7 +2230,7 @@ async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2267,7 +2281,7 @@ async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2333,7 +2347,7 @@ async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyh )]); apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2413,7 +2427,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; let serialized_with_optional = std::fs::read_to_string(&config_path)?; @@ -2444,7 +2458,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh ); apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2535,7 +2549,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers() apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2619,7 +2633,7 @@ async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2666,7 +2680,7 @@ async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2713,7 +2727,7 @@ async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> { apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2764,7 +2778,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh apply_blocking( codex_home.path(), - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], )?; @@ -2918,7 +2932,7 @@ async fn set_feature_enabled_updates_profile() -> anyhow::Result<()> { ConfigEditsBuilder::new(codex_home.path()) .with_profile(Some("dev")) - .set_feature_enabled("guardian_approval", true) + .set_feature_enabled("guardian_approval", /*enabled*/ true) .apply() .await?; @@ -2954,13 +2968,13 @@ async fn set_feature_enabled_persists_default_false_feature_disable_in_profile() ConfigEditsBuilder::new(codex_home.path()) .with_profile(Some("dev")) - .set_feature_enabled("guardian_approval", true) + .set_feature_enabled("guardian_approval", /*enabled*/ true) .apply() .await?; ConfigEditsBuilder::new(codex_home.path()) .with_profile(Some("dev")) - .set_feature_enabled("guardian_approval", false) + .set_feature_enabled("guardian_approval", /*enabled*/ false) .apply() .await?; @@ -2994,13 +3008,13 @@ async fn set_feature_enabled_profile_disable_overrides_root_enable() -> anyhow:: let codex_home = TempDir::new()?; ConfigEditsBuilder::new(codex_home.path()) - .set_feature_enabled("guardian_approval", true) + .set_feature_enabled("guardian_approval", /*enabled*/ true) .apply() .await?; ConfigEditsBuilder::new(codex_home.path()) .with_profile(Some("dev")) - .set_feature_enabled("guardian_approval", false) + .set_feature_enabled("guardian_approval", /*enabled*/ false) .apply() .await?; @@ -4312,7 +4326,8 @@ model_verbosity = "high" supports_websockets: false, }; let model_provider_map = { - let mut model_provider_map = built_in_model_providers(/* openai_base_url */ None); + let mut model_provider_map = + built_in_model_providers(/* openai_base_url */ /*openai_base_url*/ None); model_provider_map.insert("openai-custom".to_string(), openai_custom_provider.clone()); model_provider_map }; @@ -4381,7 +4396,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { windows_sandbox_private_desktop: true, }, approvals_reviewer: ApprovalsReviewer::User, - enforce_residency: Constrained::allow_any(None), + enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, notify: None, cwd: fixture.cwd(), @@ -4523,7 +4538,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { windows_sandbox_private_desktop: true, }, approvals_reviewer: ApprovalsReviewer::User, - enforce_residency: Constrained::allow_any(None), + enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, notify: None, cwd: fixture.cwd(), @@ -4663,7 +4678,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { windows_sandbox_private_desktop: true, }, approvals_reviewer: ApprovalsReviewer::User, - enforce_residency: Constrained::allow_any(None), + enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, notify: None, cwd: fixture.cwd(), @@ -4789,7 +4804,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { windows_sandbox_private_desktop: true, }, approvals_reviewer: ApprovalsReviewer::User, - enforce_residency: Constrained::allow_any(None), + enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, notify: None, cwd: fixture.cwd(), @@ -5131,11 +5146,11 @@ trust_level = "untrusted" .expect("TOML deserialization should succeed"); let resolution = cfg.derive_sandbox_policy( - None, - None, + /*sandbox_mode_override*/ None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &PathBuf::from("/tmp/test"), - None, + /*sandbox_policy_constraint*/ None, ); // Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade) @@ -5183,8 +5198,8 @@ fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defaults() })?; let resolution = cfg.derive_sandbox_policy( - None, - None, + /*sandbox_mode_override*/ None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &project_path, Some(&constrained), @@ -5223,8 +5238,8 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback() })?; let resolution = cfg.derive_sandbox_policy( - None, - None, + /*sandbox_mode_override*/ None, + /*profile_sandbox_mode*/ None, WindowsSandboxLevel::Disabled, &project_path, Some(&constrained), @@ -5241,7 +5256,11 @@ fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback() #[test] fn test_resolve_oss_provider_explicit_override() { let config_toml = ConfigToml::default(); - let result = resolve_oss_provider(Some("custom-provider"), &config_toml, None); + let result = resolve_oss_provider( + Some("custom-provider"), + &config_toml, + /*config_profile*/ None, + ); assert_eq!(result, Some("custom-provider".to_string())); } @@ -5258,7 +5277,11 @@ fn test_resolve_oss_provider_from_profile() { ..Default::default() }; - let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string())); + let result = resolve_oss_provider( + /*explicit_provider*/ None, + &config_toml, + Some("test-profile".to_string()), + ); assert_eq!(result, Some("profile-provider".to_string())); } @@ -5269,7 +5292,11 @@ fn test_resolve_oss_provider_from_global_config() { ..Default::default() }; - let result = resolve_oss_provider(None, &config_toml, None); + let result = resolve_oss_provider( + /*explicit_provider*/ None, + &config_toml, + /*config_profile*/ None, + ); assert_eq!(result, Some("global-provider".to_string())); } @@ -5284,14 +5311,22 @@ fn test_resolve_oss_provider_profile_fallback_to_global() { ..Default::default() }; - let result = resolve_oss_provider(None, &config_toml, Some("test-profile".to_string())); + let result = resolve_oss_provider( + /*explicit_provider*/ None, + &config_toml, + Some("test-profile".to_string()), + ); assert_eq!(result, Some("global-provider".to_string())); } #[test] fn test_resolve_oss_provider_none_when_not_configured() { let config_toml = ConfigToml::default(); - let result = resolve_oss_provider(None, &config_toml, None); + let result = resolve_oss_provider( + /*explicit_provider*/ None, + &config_toml, + /*config_profile*/ None, + ); assert_eq!(result, None); } diff --git a/codex-rs/core/src/config/edit_tests.rs b/codex-rs/core/src/config/edit_tests.rs index d7386c546c..e27add0054 100644 --- a/codex-rs/core/src/config/edit_tests.rs +++ b/codex-rs/core/src/config/edit_tests.rs @@ -16,7 +16,7 @@ fn blocking_set_model_top_level() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: Some("gpt-5.1-codex".to_string()), effort: Some(ReasoningEffort::High), @@ -150,7 +150,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } } apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: Some("o4-mini".to_string()), effort: None, @@ -195,7 +195,7 @@ fn blocking_set_model_writes_through_symlink_chain() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: Some("gpt-5.1-codex".to_string()), effort: Some(ReasoningEffort::High), @@ -228,7 +228,7 @@ fn blocking_set_model_replaces_symlink_on_cycle() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: Some("gpt-5.1-codex".to_string()), effort: None, @@ -267,7 +267,7 @@ network_access = false apply_blocking( codex_home, - None, + /*profile*/ None, &[ ConfigEdit::SetPath { segments: vec![ @@ -322,7 +322,7 @@ profiles = { fast = { model = "gpt-4o", sandbox_mode = "strict" } } apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: None, effort: Some(ReasoningEffort::High), @@ -356,7 +356,7 @@ model_reasoning_effort = "low" apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetModel { model: Some("o5-preview".to_string()), effort: Some(ReasoningEffort::Minimal), @@ -420,7 +420,7 @@ existing = "value" apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetNoticeHideFullAccessWarning(true)], ) .expect("persist"); @@ -450,7 +450,7 @@ existing = "value" apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetNoticeHideRateLimitModelNudge(true)], ) .expect("persist"); @@ -476,7 +476,7 @@ existing = "value" .expect("seed"); apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetNoticeHideModelMigrationPrompt( "hide_gpt5_1_migration_prompt".to_string(), true, @@ -505,7 +505,7 @@ existing = "value" .expect("seed"); apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetNoticeHideModelMigrationPrompt( "hide_gpt-5.1-codex-max_migration_prompt".to_string(), true, @@ -534,7 +534,7 @@ existing = "value" .expect("seed"); apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::RecordModelMigrationSeen { from: "gpt-5".to_string(), to: "gpt-5.1".to_string(), @@ -616,7 +616,7 @@ fn blocking_replace_mcp_servers_round_trips() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(servers.clone())], ) .expect("persist"); @@ -681,7 +681,12 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() { }, ); - apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist"); + apply_blocking( + codex_home, + /*profile*/ None, + &[ConfigEdit::ReplaceMcpServers(servers)], + ) + .expect("persist"); let raw = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let expected = "\ @@ -731,7 +736,12 @@ foo = { command = "cmd" } }, ); - apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist"); + apply_blocking( + codex_home, + /*profile*/ None, + &[ConfigEdit::ReplaceMcpServers(servers)], + ) + .expect("persist"); let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let expected = r#"[mcp_servers] @@ -777,7 +787,12 @@ foo = { command = "cmd" } # keep me }, ); - apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist"); + apply_blocking( + codex_home, + /*profile*/ None, + &[ConfigEdit::ReplaceMcpServers(servers)], + ) + .expect("persist"); let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let expected = r#"[mcp_servers] @@ -822,7 +837,12 @@ foo = { command = "cmd", args = ["--flag"] } # keep me }, ); - apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist"); + apply_blocking( + codex_home, + /*profile*/ None, + &[ConfigEdit::ReplaceMcpServers(servers)], + ) + .expect("persist"); let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let expected = r#"[mcp_servers] @@ -868,7 +888,12 @@ foo = { command = "cmd" } }, ); - apply_blocking(codex_home, None, &[ConfigEdit::ReplaceMcpServers(servers)]).expect("persist"); + apply_blocking( + codex_home, + /*profile*/ None, + &[ConfigEdit::ReplaceMcpServers(servers)], + ) + .expect("persist"); let contents = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let expected = r#"[mcp_servers] @@ -885,7 +910,7 @@ fn blocking_clear_path_noop_when_missing() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::ClearPath { segments: vec!["missing".to_string()], }], @@ -906,7 +931,7 @@ fn blocking_set_path_updates_notifications() { let item = value(false); apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::SetPath { segments: vec!["tui".to_string(), "notifications".to_string()], value: item, @@ -982,7 +1007,7 @@ async fn blocking_set_asynchronous_helpers_available() { let codex_home = tmp.path().to_path_buf(); ConfigEditsBuilder::new(&codex_home) - .set_hide_full_access_warning(true) + .set_hide_full_access_warning(/*acknowledged*/ true) .apply() .await .expect("persist"); @@ -1024,7 +1049,7 @@ fn blocking_builder_set_realtime_audio_persists_and_clears() { ); ConfigEditsBuilder::new(codex_home) - .set_realtime_microphone(None) + .set_realtime_microphone(/*microphone*/ None) .apply_blocking() .expect("clear realtime microphone"); @@ -1053,7 +1078,7 @@ fn replace_mcp_servers_blocking_clears_table_when_empty() { apply_blocking( codex_home, - None, + /*profile*/ None, &[ConfigEdit::ReplaceMcpServers(BTreeMap::new())], ) .expect("persist"); diff --git a/codex-rs/core/src/config/managed_features.rs b/codex-rs/core/src/config/managed_features.rs index 646a161533..44daa241cd 100644 --- a/codex-rs/core/src/config/managed_features.rs +++ b/codex-rs/core/src/config/managed_features.rs @@ -96,7 +96,10 @@ impl ManagedFeatures { impl From for ManagedFeatures { fn from(features: Features) -> Self { Self { - value: ConstrainedWithSource::new(Constrained::allow_any(features), None), + value: ConstrainedWithSource::new( + Constrained::allow_any(features), + /*source*/ None, + ), pinned_features: BTreeMap::new(), } } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index fa21cbad84..1a0722119b 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -123,9 +123,14 @@ pub use network_proxy_spec::NetworkProxySpec; pub use network_proxy_spec::StartedNetworkProxy; pub use permissions::FilesystemPermissionToml; pub use permissions::FilesystemPermissionsToml; +pub use permissions::NetworkDomainPermissionToml; +pub use permissions::NetworkDomainPermissionsToml; pub use permissions::NetworkToml; +pub use permissions::NetworkUnixSocketPermissionToml; +pub use permissions::NetworkUnixSocketPermissionsToml; pub use permissions::PermissionProfileToml; pub use permissions::PermissionsToml; +pub(crate) use permissions::overlay_network_domain_permissions; pub(crate) use permissions::resolve_permission_profile; pub use service::ConfigService; pub use service::ConfigServiceError; diff --git a/codex-rs/core/src/config/network_proxy_spec.rs b/codex-rs/core/src/config/network_proxy_spec.rs index 386100d34b..93b59cf5f5 100644 --- a/codex-rs/core/src/config/network_proxy_spec.rs +++ b/codex-rs/core/src/config/network_proxy_spec.rs @@ -226,33 +226,63 @@ impl NetworkProxySpec { Some(dangerously_allow_all_unix_sockets); } let managed_allowed_domains = if hard_deny_allowlist_misses { - Some(requirements.allowed_domains.clone().unwrap_or_default()) + Some( + requirements + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains) + .unwrap_or_default(), + ) } else { - requirements.allowed_domains.clone() + requirements + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains) }; - if let Some(allowed_domains) = managed_allowed_domains { + if let Some(managed_allowed_domains) = managed_allowed_domains { // Managed requirements seed the baseline allowlist. User additions // can extend that baseline unless managed-only mode pins the // effective allowlist to the managed set. - config.network.allowed_domains = if allowlist_expansion_enabled { - Self::merge_domain_lists(allowed_domains.clone(), &config.network.allowed_domains) + let effective_allowed_domains = if allowlist_expansion_enabled { + Self::merge_domain_lists( + managed_allowed_domains.clone(), + config.network.allowed_domains().as_deref().unwrap_or(&[]), + ) } else { - allowed_domains.clone() + managed_allowed_domains.clone() }; - constraints.allowed_domains = Some(allowed_domains); + config + .network + .set_allowed_domains(effective_allowed_domains); + constraints.allowed_domains = Some(managed_allowed_domains); constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled); } - if let Some(denied_domains) = requirements.denied_domains.clone() { - config.network.denied_domains = if denylist_expansion_enabled { - Self::merge_domain_lists(denied_domains.clone(), &config.network.denied_domains) + let managed_denied_domains = requirements + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::denied_domains); + if let Some(managed_denied_domains) = managed_denied_domains { + let effective_denied_domains = if denylist_expansion_enabled { + Self::merge_domain_lists( + managed_denied_domains.clone(), + config.network.denied_domains().as_deref().unwrap_or(&[]), + ) } else { - denied_domains.clone() + managed_denied_domains.clone() }; - constraints.denied_domains = Some(denied_domains); + config.network.set_denied_domains(effective_denied_domains); + constraints.denied_domains = Some(managed_denied_domains); constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled); } - if let Some(allow_unix_sockets) = requirements.allow_unix_sockets.clone() { - config.network.allow_unix_sockets = allow_unix_sockets.clone(); + if requirements.unix_sockets.is_some() { + let allow_unix_sockets = requirements + .unix_sockets + .as_ref() + .map(codex_config::NetworkUnixSocketPermissionsToml::allow_unix_sockets) + .unwrap_or_default(); + config + .network + .set_allow_unix_sockets(allow_unix_sockets.clone()); constraints.allow_unix_sockets = Some(allow_unix_sockets); } if let Some(allow_local_binding) = requirements.allow_local_binding { @@ -299,37 +329,25 @@ impl NetworkProxySpec { fn apply_exec_policy_network_rules(config: &mut NetworkProxyConfig, exec_policy: &Policy) { let (allowed_domains, denied_domains) = exec_policy.compiled_network_domains(); - upsert_network_domains( - &mut config.network.allowed_domains, - &mut config.network.denied_domains, - allowed_domains, - ); - upsert_network_domains( - &mut config.network.denied_domains, - &mut config.network.allowed_domains, - denied_domains, - ); + upsert_network_domains(config, allowed_domains, /*allow*/ true); + upsert_network_domains(config, denied_domains, /*allow*/ false); } -fn upsert_network_domains( - target: &mut Vec, - opposite: &mut Vec, - hosts: Vec, -) { +fn upsert_network_domains(config: &mut NetworkProxyConfig, hosts: Vec, allow: bool) { let mut incoming = HashSet::new(); - let mut deduped_hosts = Vec::new(); for host in hosts { if incoming.insert(host.clone()) { - deduped_hosts.push(host); + config.network.upsert_domain_permission( + host, + if allow { + codex_network_proxy::NetworkDomainPermission::Allow + } else { + codex_network_proxy::NetworkDomainPermission::Deny + }, + normalize_host, + ); } } - if incoming.is_empty() { - return; - } - - opposite.retain(|entry| !incoming.contains(&normalize_host(entry))); - target.retain(|entry| !incoming.contains(&normalize_host(entry))); - target.extend(deduped_hosts); } #[cfg(test)] diff --git a/codex-rs/core/src/config/network_proxy_spec_tests.rs b/codex-rs/core/src/config/network_proxy_spec_tests.rs index 4c6e82358e..77007885a4 100644 --- a/codex-rs/core/src/config/network_proxy_spec_tests.rs +++ b/codex-rs/core/src/config/network_proxy_spec_tests.rs @@ -1,6 +1,20 @@ use super::*; +use crate::config_loader::NetworkDomainPermissionToml; +use crate::config_loader::NetworkDomainPermissionsToml; +use codex_network_proxy::NetworkDomainPermission; use pretty_assertions::assert_eq; +fn domain_permissions( + entries: impl IntoIterator, +) -> NetworkDomainPermissionsToml { + NetworkDomainPermissionsToml { + entries: entries + .into_iter() + .map(|(pattern, permission)| (pattern.to_string(), permission)) + .collect(), + } +} + #[test] fn build_state_with_audit_metadata_threads_metadata_to_state() { let spec = NetworkProxySpec { @@ -24,9 +38,14 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() { #[test] fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["api.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { - allowed_domains: Some(vec!["*.example.com".to_string()]), + domains: Some(domain_permissions([( + "*.example.com", + NetworkDomainPermissionToml::Allow, + )])), ..Default::default() }; @@ -38,8 +57,11 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { .expect("config should stay within the managed allowlist"); assert_eq!( - spec.config.network.allowed_domains, - vec!["*.example.com".to_string(), "api.example.com".to_string()] + spec.config.network.allowed_domains(), + Some(vec![ + "*.example.com".to_string(), + "api.example.com".to_string() + ]) ); assert_eq!( spec.constraints.allowed_domains, @@ -48,14 +70,92 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(true)); } +#[test] +fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() { + let mut config = NetworkProxyConfig::default(); + config + .network + .set_denied_domains(vec!["api.example.com".to_string()]); + let requirements = NetworkConstraints { + domains: Some(domain_permissions([( + "api.example.com", + NetworkDomainPermissionToml::Allow, + )])), + ..Default::default() + }; + + let spec = NetworkProxySpec::from_config_and_constraints( + config, + Some(requirements), + &SandboxPolicy::new_workspace_write_policy(), + ) + .expect("managed allowlist should not erase a user deny"); + + assert_eq!(spec.config.network.allowed_domains(), None); + assert_eq!( + spec.config.network.denied_domains(), + Some(vec!["api.example.com".to_string()]) + ); + assert_eq!( + spec.constraints.allowed_domains, + Some(vec!["api.example.com".to_string()]) + ); +} + +#[test] +fn requirements_allowlist_expansion_keeps_user_entries_mutable() { + let mut config = NetworkProxyConfig::default(); + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); + let requirements = NetworkConstraints { + domains: Some(domain_permissions([( + "*.example.com", + NetworkDomainPermissionToml::Allow, + )])), + ..Default::default() + }; + + let spec = NetworkProxySpec::from_config_and_constraints( + config, + Some(requirements), + &SandboxPolicy::new_workspace_write_policy(), + ) + .expect("managed baseline should still allow user edits"); + + let mut candidate = spec.config.clone(); + candidate.network.upsert_domain_permission( + "api.example.com".to_string(), + NetworkDomainPermission::Deny, + normalize_host, + ); + + assert_eq!( + candidate.network.allowed_domains(), + Some(vec!["*.example.com".to_string()]) + ); + assert_eq!( + candidate.network.denied_domains(), + Some(vec!["api.example.com".to_string()]) + ); + validate_policy_against_constraints(&candidate, &spec.constraints) + .expect("user allowlist entries should not become managed constraints"); +} + #[test] fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["evil.com".to_string()]; - config.network.denied_domains = vec!["more-blocked.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["evil.com".to_string()]); + config + .network + .set_denied_domains(vec!["more-blocked.example.com".to_string()]); let requirements = NetworkConstraints { - allowed_domains: Some(vec!["*.example.com".to_string()]), - denied_domains: Some(vec!["blocked.example.com".to_string()]), + domains: Some(domain_permissions([ + ("*.example.com", NetworkDomainPermissionToml::Allow), + ("blocked.example.com", NetworkDomainPermissionToml::Deny), + ])), ..Default::default() }; @@ -67,12 +167,12 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { .expect("yolo mode should pin the effective policy to the managed baseline"); assert_eq!( - spec.config.network.allowed_domains, - vec!["*.example.com".to_string()] + spec.config.network.allowed_domains(), + Some(vec!["*.example.com".to_string()]) ); assert_eq!( - spec.config.network.denied_domains, - vec!["blocked.example.com".to_string()] + spec.config.network.denied_domains(), + Some(vec!["blocked.example.com".to_string()]) ); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); assert_eq!(spec.constraints.denylist_expansion_enabled, Some(false)); @@ -81,9 +181,14 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { #[test] fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["api.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { - allowed_domains: Some(vec!["*.example.com".to_string()]), + domains: Some(domain_permissions([( + "*.example.com", + NetworkDomainPermissionToml::Allow, + )])), managed_allowed_domains_only: Some(true), ..Default::default() }; @@ -96,8 +201,8 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { .expect("managed baseline should still load"); assert_eq!( - spec.config.network.allowed_domains, - vec!["*.example.com".to_string()] + spec.config.network.allowed_domains(), + Some(vec!["*.example.com".to_string()]) ); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); } @@ -105,9 +210,14 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { #[test] fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["api.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { - allowed_domains: Some(vec!["managed.example.com".to_string()]), + domains: Some(domain_permissions([( + "managed.example.com", + NetworkDomainPermissionToml::Allow, + )])), managed_allowed_domains_only: Some(true), ..Default::default() }; @@ -120,8 +230,8 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() .expect("managed-only allowlist should still load"); assert_eq!( - spec.config.network.allowed_domains, - vec!["managed.example.com".to_string()] + spec.config.network.allowed_domains(), + Some(vec!["managed.example.com".to_string()]) ); assert_eq!( spec.constraints.allowed_domains, @@ -134,7 +244,9 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() #[test] fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["api.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { managed_allowed_domains_only: Some(true), ..Default::default() @@ -147,7 +259,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain ) .expect("managed-only mode should treat missing managed allowlist as empty"); - assert!(spec.config.network.allowed_domains.is_empty()); + assert_eq!(spec.config.network.allowed_domains(), None); assert_eq!(spec.constraints.allowed_domains, Some(Vec::new())); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); assert!(spec.hard_deny_allowlist_misses); @@ -156,7 +268,9 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain #[test] fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_managed_list() { let mut config = NetworkProxyConfig::default(); - config.network.allowed_domains = vec!["api.example.com".to_string()]; + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { managed_allowed_domains_only: Some(true), ..Default::default() @@ -169,18 +283,89 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m ) .expect("managed-only mode should treat missing managed allowlist as empty"); - assert!(spec.config.network.allowed_domains.is_empty()); + assert_eq!(spec.config.network.allowed_domains(), None); assert_eq!(spec.constraints.allowed_domains, Some(Vec::new())); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); assert!(spec.hard_deny_allowlist_misses); } +#[test] +fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() { + let mut config = NetworkProxyConfig::default(); + config + .network + .set_allowed_domains(vec!["api.example.com".to_string()]); + let requirements = NetworkConstraints { + domains: Some(domain_permissions([( + "managed-blocked.example.com", + NetworkDomainPermissionToml::Deny, + )])), + ..Default::default() + }; + + let spec = NetworkProxySpec::from_config_and_constraints( + config, + Some(requirements), + &SandboxPolicy::DangerFullAccess, + ) + .expect("deny-only requirements should not constrain the allowlist"); + + assert_eq!( + spec.config.network.allowed_domains(), + Some(vec!["api.example.com".to_string()]) + ); + assert_eq!(spec.constraints.allowed_domains, None); + assert_eq!(spec.constraints.allowlist_expansion_enabled, None); + assert_eq!( + spec.config.network.denied_domains(), + Some(vec!["managed-blocked.example.com".to_string()]) + ); +} + +#[test] +fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() { + let mut config = NetworkProxyConfig::default(); + config + .network + .set_denied_domains(vec!["blocked.example.com".to_string()]); + let requirements = NetworkConstraints { + domains: Some(domain_permissions([( + "managed.example.com", + NetworkDomainPermissionToml::Allow, + )])), + ..Default::default() + }; + + let spec = NetworkProxySpec::from_config_and_constraints( + config, + Some(requirements), + &SandboxPolicy::DangerFullAccess, + ) + .expect("allow-only requirements should not constrain the denylist"); + + assert_eq!( + spec.config.network.allowed_domains(), + Some(vec!["managed.example.com".to_string()]) + ); + assert_eq!( + spec.config.network.denied_domains(), + Some(vec!["blocked.example.com".to_string()]) + ); + assert_eq!(spec.constraints.denied_domains, None); + assert_eq!(spec.constraints.denylist_expansion_enabled, None); +} + #[test] fn requirements_denied_domains_are_a_baseline_for_default_mode() { let mut config = NetworkProxyConfig::default(); - config.network.denied_domains = vec!["blocked.example.com".to_string()]; + config + .network + .set_denied_domains(vec!["blocked.example.com".to_string()]); let requirements = NetworkConstraints { - denied_domains: Some(vec!["managed-blocked.example.com".to_string()]), + domains: Some(domain_permissions([( + "managed-blocked.example.com", + NetworkDomainPermissionToml::Deny, + )])), ..Default::default() }; @@ -192,11 +377,55 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() { .expect("default mode should merge managed and user deny entries"); assert_eq!( - spec.config.network.denied_domains, - vec![ + spec.config.network.denied_domains(), + Some(vec![ "managed-blocked.example.com".to_string(), "blocked.example.com".to_string() - ] + ]) + ); + assert_eq!( + spec.constraints.denied_domains, + Some(vec!["managed-blocked.example.com".to_string()]) ); assert_eq!(spec.constraints.denylist_expansion_enabled, Some(true)); } + +#[test] +fn requirements_denylist_expansion_keeps_user_entries_mutable() { + let mut config = NetworkProxyConfig::default(); + config + .network + .set_denied_domains(vec!["blocked.example.com".to_string()]); + let requirements = NetworkConstraints { + domains: Some(domain_permissions([( + "managed-blocked.example.com", + NetworkDomainPermissionToml::Deny, + )])), + ..Default::default() + }; + + let spec = NetworkProxySpec::from_config_and_constraints( + config, + Some(requirements), + &SandboxPolicy::new_workspace_write_policy(), + ) + .expect("managed baseline should still allow user edits"); + + let mut candidate = spec.config.clone(); + candidate.network.upsert_domain_permission( + "blocked.example.com".to_string(), + NetworkDomainPermission::Allow, + normalize_host, + ); + + assert_eq!( + candidate.network.allowed_domains(), + Some(vec!["blocked.example.com".to_string()]) + ); + assert_eq!( + candidate.network.denied_domains(), + Some(vec!["managed-blocked.example.com".to_string()]) + ); + validate_policy_against_constraints(&candidate, &spec.constraints) + .expect("user denylist entries should not become managed constraints"); +} diff --git a/codex-rs/core/src/config/permissions.rs b/codex-rs/core/src/config/permissions.rs index 759c269b76..73dad1c73c 100644 --- a/codex-rs/core/src/config/permissions.rs +++ b/codex-rs/core/src/config/permissions.rs @@ -5,8 +5,11 @@ use std::path::Component; use std::path::Path; use std::path::PathBuf; +use codex_network_proxy::NetworkDomainPermission as ProxyNetworkDomainPermission; use codex_network_proxy::NetworkMode; use codex_network_proxy::NetworkProxyConfig; +use codex_network_proxy::NetworkUnixSocketPermission as ProxyNetworkUnixSocketPermission; +use codex_network_proxy::normalize_host; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; use codex_protocol::permissions::FileSystemSandboxEntry; @@ -56,6 +59,98 @@ pub enum FilesystemPermissionToml { Scoped(BTreeMap), } +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] +pub struct NetworkDomainPermissionsToml { + #[serde(flatten)] + pub entries: BTreeMap, +} + +impl NetworkDomainPermissionsToml { + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + #[cfg(test)] + pub(crate) fn allowed_domains(&self) -> Option> { + let allowed_domains: Vec = self + .entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Allow)) + .map(|(pattern, _)| pattern.clone()) + .collect(); + (!allowed_domains.is_empty()).then_some(allowed_domains) + } + + #[cfg(test)] + pub(crate) fn denied_domains(&self) -> Option> { + let denied_domains: Vec = self + .entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Deny)) + .map(|(pattern, _)| pattern.clone()) + .collect(); + (!denied_domains.is_empty()).then_some(denied_domains) + } +} + +#[derive( + Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, JsonSchema, +)] +#[serde(rename_all = "lowercase")] +pub enum NetworkDomainPermissionToml { + Allow, + Deny, +} + +impl std::fmt::Display for NetworkDomainPermissionToml { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let permission = match self { + Self::Allow => "allow", + Self::Deny => "deny", + }; + f.write_str(permission) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] +pub struct NetworkUnixSocketPermissionsToml { + #[serde(flatten)] + pub entries: BTreeMap, +} + +impl NetworkUnixSocketPermissionsToml { + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub(crate) fn allow_unix_sockets(&self) -> Vec { + self.entries + .iter() + .filter(|(_, permission)| matches!(permission, NetworkUnixSocketPermissionToml::Allow)) + .map(|(path, _)| path.clone()) + .collect() + } +} + +#[derive( + Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, JsonSchema, +)] +#[serde(rename_all = "lowercase")] +pub enum NetworkUnixSocketPermissionToml { + Allow, + None, +} + +impl std::fmt::Display for NetworkUnixSocketPermissionToml { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let permission = match self { + Self::Allow => "allow", + Self::None => "none", + }; + f.write_str(permission) + } +} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct NetworkToml { @@ -69,9 +164,8 @@ pub struct NetworkToml { pub dangerously_allow_all_unix_sockets: Option, #[schemars(with = "Option")] pub mode: Option, - pub allowed_domains: Option>, - pub denied_domains: Option>, - pub allow_unix_sockets: Option>, + pub domains: Option, + pub unix_sockets: Option, pub allow_local_binding: Option, } @@ -114,14 +208,22 @@ impl NetworkToml { if let Some(mode) = self.mode { config.network.mode = mode; } - if let Some(allowed_domains) = self.allowed_domains.as_ref() { - config.network.allowed_domains = allowed_domains.clone(); + if let Some(domains) = self.domains.as_ref() { + overlay_network_domain_permissions(config, domains); } - if let Some(denied_domains) = self.denied_domains.as_ref() { - config.network.denied_domains = denied_domains.clone(); - } - if let Some(allow_unix_sockets) = self.allow_unix_sockets.as_ref() { - config.network.allow_unix_sockets = allow_unix_sockets.clone(); + if let Some(unix_sockets) = self.unix_sockets.as_ref() { + let mut proxy_unix_sockets = config.network.unix_sockets.take().unwrap_or_default(); + for (path, permission) in &unix_sockets.entries { + let permission = match permission { + NetworkUnixSocketPermissionToml::Allow => { + ProxyNetworkUnixSocketPermission::Allow + } + NetworkUnixSocketPermissionToml::None => ProxyNetworkUnixSocketPermission::None, + }; + proxy_unix_sockets.entries.insert(path.clone(), permission); + } + config.network.unix_sockets = + (!proxy_unix_sockets.entries.is_empty()).then_some(proxy_unix_sockets); } if let Some(allow_local_binding) = self.allow_local_binding { config.network.allow_local_binding = allow_local_binding; @@ -135,6 +237,21 @@ impl NetworkToml { } } +pub(crate) fn overlay_network_domain_permissions( + config: &mut NetworkProxyConfig, + domains: &NetworkDomainPermissionsToml, +) { + for (pattern, permission) in &domains.entries { + let permission = match permission { + NetworkDomainPermissionToml::Allow => ProxyNetworkDomainPermission::Allow, + NetworkDomainPermissionToml::Deny => ProxyNetworkDomainPermission::Deny, + }; + config + .network + .upsert_domain_permission(pattern.clone(), permission, normalize_host); + } +} + pub(crate) fn network_proxy_config_from_profile_network( network: Option<&NetworkToml>, ) -> NetworkProxyConfig { diff --git a/codex-rs/core/src/config/permissions_tests.rs b/codex-rs/core/src/config/permissions_tests.rs index b90b903e88..e3ea67d7be 100644 --- a/codex-rs/core/src/config/permissions_tests.rs +++ b/codex-rs/core/src/config/permissions_tests.rs @@ -9,8 +9,10 @@ use tempfile::TempDir; #[test] fn normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths() { - let parsed = - normalize_absolute_path_for_platform(r"\\?\D:\c\x\worktrees\2508\swift-base", true); + let parsed = normalize_absolute_path_for_platform( + r"\\?\D:\c\x\worktrees\2508\swift-base", + /*is_windows*/ true, + ); assert_eq!(parsed, PathBuf::from(r"D:\c\x\worktrees\2508\swift-base")); } @@ -76,3 +78,132 @@ fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()> Ok(()) } + +#[test] +fn network_toml_ignores_legacy_network_list_keys() { + let parsed = toml::from_str::( + r#" +allowed_domains = ["openai.com"] +"#, + ) + .expect("legacy network list keys should be ignored"); + + assert_eq!(parsed, NetworkToml::default()); +} + +#[test] +fn network_permission_containers_project_allowed_and_denied_entries() { + let domains = NetworkDomainPermissionsToml { + entries: BTreeMap::from([ + ( + "*.openai.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + ), + ( + "blocked.example.com".to_string(), + NetworkDomainPermissionToml::Deny, + ), + ]), + }; + let unix_sockets = NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([ + ( + "/tmp/example.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + ), + ( + "/tmp/ignored.sock".to_string(), + NetworkUnixSocketPermissionToml::None, + ), + ]), + }; + + assert_eq!( + domains.allowed_domains(), + Some(vec![ + "*.openai.com".to_string(), + "api.example.com".to_string() + ]) + ); + assert_eq!( + domains.denied_domains(), + Some(vec!["blocked.example.com".to_string()]) + ); + assert_eq!( + NetworkDomainPermissionsToml { + entries: BTreeMap::from([( + "api.example.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + } + .denied_domains(), + None + ); + assert_eq!( + unix_sockets.allow_unix_sockets(), + vec!["/tmp/example.sock".to_string()] + ); +} + +#[test] +fn network_toml_overlays_unix_socket_permissions_by_path() { + let mut config = NetworkProxyConfig::default(); + + NetworkToml { + unix_sockets: Some(NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([ + ( + "/tmp/base.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + ), + ( + "/tmp/override.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + ), + ]), + }), + ..Default::default() + } + .apply_to_network_proxy_config(&mut config); + + NetworkToml { + unix_sockets: Some(NetworkUnixSocketPermissionsToml { + entries: BTreeMap::from([ + ( + "/tmp/extra.sock".to_string(), + NetworkUnixSocketPermissionToml::Allow, + ), + ( + "/tmp/override.sock".to_string(), + NetworkUnixSocketPermissionToml::None, + ), + ]), + }), + ..Default::default() + } + .apply_to_network_proxy_config(&mut config); + + assert_eq!( + config.network.unix_sockets, + Some(codex_network_proxy::NetworkUnixSocketPermissions { + entries: BTreeMap::from([ + ( + "/tmp/base.sock".to_string(), + ProxyNetworkUnixSocketPermission::Allow, + ), + ( + "/tmp/extra.sock".to_string(), + ProxyNetworkUnixSocketPermission::Allow, + ), + ( + "/tmp/override.sock".to_string(), + ProxyNetworkUnixSocketPermission::None, + ), + ]), + }) + ); +} diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index e4c5039588..df33665956 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -42,7 +42,11 @@ pub use codex_config::LoaderOverrides; pub use codex_config::McpServerIdentity; pub use codex_config::McpServerRequirement; pub use codex_config::NetworkConstraints; +pub use codex_config::NetworkDomainPermissionToml; +pub use codex_config::NetworkDomainPermissionsToml; pub use codex_config::NetworkRequirementsToml; +pub use codex_config::NetworkUnixSocketPermissionToml; +pub use codex_config::NetworkUnixSocketPermissionsToml; pub use codex_config::RequirementSource; pub use codex_config::ResidencyRequirement; pub use codex_config::SandboxModeRequirement; diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 6a2bc0b6b9..77e69a53c9 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -797,7 +797,7 @@ async fn load_config_layers_fails_when_cloud_requirements_loader_fails() -> anyh CloudRequirementsLoader::new(async { Err(CloudRequirementsLoadError::new( codex_config::CloudRequirementsLoadErrorCode::RequestFailed, - None, + /*status_code*/ None, "cloud requirements failed", )) }), @@ -833,7 +833,13 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> { let codex_home = tmp.path().join("home"); tokio::fs::create_dir_all(&codex_home).await?; - make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &codex_home, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let cwd = AbsolutePathBuf::from_absolute_path(&nested)?; let layers = load_config_layers_state( &codex_home, @@ -899,7 +905,13 @@ model_instructions_file = "child.txt" let codex_home = tmp.path().join("home"); tokio::fs::create_dir_all(&codex_home).await?; - make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &codex_home, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let config = ConfigBuilder::default() .codex_home(codex_home) @@ -965,7 +977,13 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s let codex_home = tmp.path().join("home"); tokio::fs::create_dir_all(&codex_home).await?; - make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &codex_home, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let cwd = AbsolutePathBuf::from_absolute_path(&nested)?; let layers = load_config_layers_state( &codex_home, @@ -1018,7 +1036,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R let project_layers: Vec<_> = layers .get_layers( super::ConfigLayerStackOrdering::HighestPrecedenceFirst, - true, + /*include_disabled*/ true, ) .into_iter() .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) @@ -1046,7 +1064,13 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul tokio::fs::write(nested_dot_codex.join(CONFIG_TOML_FILE), "foo = \"child\"\n").await?; tokio::fs::create_dir_all(&project_dot_codex).await?; - make_config_for_test(&project_dot_codex, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &project_dot_codex, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let user_config_path = project_dot_codex.join(CONFIG_TOML_FILE); let user_config_contents = tokio::fs::read_to_string(&user_config_path).await?; tokio::fs::write( @@ -1068,7 +1092,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul let project_layers: Vec<_> = layers .get_layers( super::ConfigLayerStackOrdering::HighestPrecedenceFirst, - true, + /*include_disabled*/ true, ) .into_iter() .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) @@ -1115,7 +1139,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< &codex_home_untrusted, &project_root, TrustLevel::Untrusted, - None, + /*project_root_markers*/ None, ) .await?; let untrusted_config_path = codex_home_untrusted.join(CONFIG_TOML_FILE); @@ -1137,7 +1161,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< let project_layers_untrusted: Vec<_> = layers_untrusted .get_layers( super::ConfigLayerStackOrdering::HighestPrecedenceFirst, - true, + /*include_disabled*/ true, ) .into_iter() .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) @@ -1175,7 +1199,7 @@ async fn project_layers_disabled_when_untrusted_or_unknown() -> std::io::Result< let project_layers_unknown: Vec<_> = layers_unknown .get_layers( super::ConfigLayerStackOrdering::HighestPrecedenceFirst, - true, + /*include_disabled*/ true, ) .into_iter() .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) @@ -1218,7 +1242,13 @@ enabled = false "#, ) .await?; - make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &codex_home, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let config = ConfigBuilder::default() .codex_home(codex_home) @@ -1303,7 +1333,13 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io:: let config_path = codex_home.join(CONFIG_TOML_FILE); if let Some(trust_level) = trust_level { - make_config_for_test(&codex_home, &project_root, trust_level, None).await?; + make_config_for_test( + &codex_home, + &project_root, + trust_level, + /*project_root_markers*/ None, + ) + .await?; let config_contents = tokio::fs::read_to_string(&config_path).await?; tokio::fs::write(&config_path, format!("foo = \"user\"\n{config_contents}")).await?; } else { @@ -1321,7 +1357,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io:: let project_layers: Vec<_> = layers .get_layers( super::ConfigLayerStackOrdering::HighestPrecedenceFirst, - true, + /*include_disabled*/ true, ) .into_iter() .filter(|layer| matches!(layer.name, super::ConfigLayerSource::Project { .. })) @@ -1358,7 +1394,13 @@ async fn cli_overrides_with_relative_paths_do_not_break_trust_check() -> std::io let codex_home = tmp.path().join("home"); tokio::fs::create_dir_all(&codex_home).await?; - make_config_for_test(&codex_home, &project_root, TrustLevel::Trusted, None).await?; + make_config_for_test( + &codex_home, + &project_root, + TrustLevel::Trusted, + /*project_root_markers*/ None, + ) + .await?; let cwd = AbsolutePathBuf::from_absolute_path(&nested)?; let cli_overrides = vec![( diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index e4999b9b41..a15138f6fb 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -18,6 +18,7 @@ use codex_connectors::AllConnectorsCacheKey; use codex_connectors::DirectoryListResponse; use codex_login::token_data::TokenData; use codex_protocol::protocol::SandboxPolicy; +use codex_tools::DiscoverableTool; use rmcp::model::ToolAnnotations; use serde::Deserialize; use serde::de::DeserializeOwned; @@ -44,8 +45,6 @@ use crate::mcp_connection_manager::codex_apps_tools_cache_key; use crate::plugins::AppConnectorId; use crate::plugins::PluginsManager; use crate::plugins::list_tool_suggest_discoverable_plugins; -use crate::tools::discoverable::DiscoverablePluginInfo; -use crate::tools::discoverable::DiscoverableTool; use codex_features::Feature; pub use codex_connectors::CONNECTORS_CACHE_TTL; @@ -133,7 +132,6 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth( .map(DiscoverableTool::from); let discoverable_plugins = list_tool_suggest_discoverable_plugins(config)? .into_iter() - .map(DiscoverablePluginInfo::from) .map(DiscoverableTool::from); Ok(discoverable_connectors .chain(discoverable_plugins) diff --git a/codex-rs/core/src/connectors_tests.rs b/codex-rs/core/src/connectors_tests.rs index 2a98621a8b..98515921ef 100644 --- a/codex-rs/core/src/connectors_tests.rs +++ b/codex-rs/core/src/connectors_tests.rs @@ -171,7 +171,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() { codex_app_tool( "calendar_list_events", "calendar", - None, + /*connector_name*/ None, &["sample", "sample"], ), ), @@ -229,8 +229,8 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal .build() .await .expect("config should load"); - let _ = config.features.set_enabled(Feature::Apps, true); - let cache_key = accessible_connectors_cache_key(&config, None); + let _ = config.features.set_enabled(Feature::Apps, /*enabled*/ true); + let cache_key = accessible_connectors_cache_key(&config, /*auth*/ None); let tools = HashMap::from([ ( "mcp__codex_apps__calendar_list_events".to_string(), @@ -253,7 +253,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal ]); let cached = with_accessible_connectors_cache_cleared(|| { - refresh_accessible_connectors_cache_from_mcp_tools(&config, None, &tools); + refresh_accessible_connectors_cache_from_mcp_tools(&config, /*auth*/ None, &tools); read_cached_accessible_connectors(&cache_key).expect("cache should be populated") }); @@ -367,8 +367,8 @@ fn app_tool_policy_uses_global_defaults_for_destructive_hints() { Some(&apps_config), Some("calendar"), "events/create", - None, - Some(&annotations(Some(true), None)), + /*tool_title*/ None, + Some(&annotations(Some(true), /*open_world_hint*/ None)), ); assert_eq!( @@ -392,7 +392,7 @@ fn app_is_enabled_uses_default_for_unconfigured_apps() { }; assert!(!app_is_enabled(&apps_config, Some("calendar"))); - assert!(!app_is_enabled(&apps_config, None)); + assert!(!app_is_enabled(&apps_config, /*connector_id*/ None)); } #[test] @@ -518,7 +518,13 @@ enabled = true .await .expect("config should build"); - let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None); + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "events.list", + /*tool_title*/ None, + /*annotations*/ None, + ); assert_eq!( policy, AppToolPolicy { @@ -555,7 +561,13 @@ async fn cloud_requirements_disable_connector_applies_without_user_apps_table() .await .expect("config should build"); - let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None); + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "events.list", + /*tool_title*/ None, + /*annotations*/ None, + ); assert_eq!( policy, AppToolPolicy { @@ -602,7 +614,13 @@ enabled = true .expect("apps config"), ); - let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None); + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "events.list", + /*tool_title*/ None, + /*annotations*/ None, + ); assert_eq!( policy, AppToolPolicy { @@ -637,7 +655,13 @@ async fn local_requirements_disable_connector_applies_without_user_apps_table() ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements) .expect("requirements stack"); - let policy = app_tool_policy(&config, Some("connector_123123"), "events.list", None, None); + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "events.list", + /*tool_title*/ None, + /*annotations*/ None, + ); assert_eq!( policy, AppToolPolicy { @@ -699,8 +723,10 @@ fn app_tool_policy_honors_default_app_enabled_false() { Some(&apps_config), Some("calendar"), "events/list", - None, - Some(&annotations(None, None)), + /*tool_title*/ None, + Some(&annotations( + /*destructive_hint*/ None, /*open_world_hint*/ None, + )), ); assert_eq!( @@ -737,8 +763,10 @@ fn app_tool_policy_allows_per_app_enable_when_default_is_disabled() { Some(&apps_config), Some("calendar"), "events/list", - None, - Some(&annotations(None, None)), + /*tool_title*/ None, + Some(&annotations( + /*destructive_hint*/ None, /*open_world_hint*/ None, + )), ); assert_eq!( @@ -779,7 +807,7 @@ fn app_tool_policy_per_tool_enabled_true_overrides_app_level_disable_flags() { Some(&apps_config), Some("calendar"), "events/create", - None, + /*tool_title*/ None, Some(&annotations(Some(true), Some(true))), ); @@ -813,7 +841,7 @@ fn app_tool_policy_default_tools_enabled_true_overrides_app_level_tool_hints() { Some(&apps_config), Some("calendar"), "events/create", - None, + /*tool_title*/ None, Some(&annotations(Some(true), Some(true))), ); @@ -847,8 +875,10 @@ fn app_tool_policy_default_tools_enabled_false_overrides_app_level_tool_hints() Some(&apps_config), Some("calendar"), "events/list", - None, - Some(&annotations(None, None)), + /*tool_title*/ None, + Some(&annotations( + /*destructive_hint*/ None, /*open_world_hint*/ None, + )), ); assert_eq!( @@ -883,8 +913,10 @@ fn app_tool_policy_uses_default_tools_approval_mode() { Some(&apps_config), Some("calendar"), "events/list", - None, - Some(&annotations(None, None)), + /*tool_title*/ None, + Some(&annotations( + /*destructive_hint*/ None, /*open_world_hint*/ None, + )), ); assert_eq!( diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index 3c508e05ff..9720492c1f 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -51,7 +51,7 @@ fn inter_agent_assistant_msg(text: &str) -> ResponseItem { AgentPath::root().join("worker").unwrap(), Vec::new(), text.to_string(), - true, + /*trigger_turn*/ true, ); ResponseItem::Message { id: None, @@ -246,7 +246,8 @@ fn filters_non_api_messages() { #[test] fn non_last_reasoning_tokens_return_zero_when_no_user_messages() { - let history = create_history_with_items(vec![reasoning_with_encrypted_content(800)]); + let history = + create_history_with_items(vec![reasoning_with_encrypted_content(/*len*/ 800)]); assert_eq!(history.get_non_last_reasoning_items_tokens(), 0); } @@ -254,11 +255,11 @@ fn non_last_reasoning_tokens_return_zero_when_no_user_messages() { #[test] fn non_last_reasoning_tokens_ignore_entries_after_last_user() { let history = create_history_with_items(vec![ - reasoning_with_encrypted_content(900), + reasoning_with_encrypted_content(/*len*/ 900), user_msg("first"), - reasoning_with_encrypted_content(1_000), + reasoning_with_encrypted_content(/*len*/ 1_000), user_msg("second"), - reasoning_with_encrypted_content(2_000), + reasoning_with_encrypted_content(/*len*/ 2_000), ]); // first: (900 * 0.75 - 650) / 4 = 6.25 tokens // second: (1000 * 0.75 - 650) / 4 = 25 tokens @@ -330,7 +331,7 @@ fn drop_last_n_user_turns_treats_inter_agent_assistant_messages_as_instruction_t inter_agent_reply, ]); - history.drop_last_n_user_turns(1); + history.drop_last_n_user_turns(/*num_turns*/ 1); assert_eq!(history.raw_items(), &vec![first_turn, first_reply]); } @@ -352,7 +353,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() { total_tokens: 100, ..Default::default() }, - None, + /*model_context_window*/ None, ); let added_user = user_msg("new user message"); let added_tool_output = custom_tool_call_output("tool-tail", "new tool output"); @@ -362,7 +363,7 @@ fn total_token_usage_includes_all_items_after_last_model_generated_item() { ); assert_eq!( - history.get_total_token_usage(true), + history.get_total_token_usage(/*server_reasoning_included*/ true), 100 + estimate_item_token_count(&added_user) + estimate_item_token_count(&added_tool_output) ); @@ -606,7 +607,12 @@ fn for_prompt_clears_image_generation_result_when_images_are_unsupported() { #[test] fn get_history_for_prompt_drops_ghost_commits() { let items = vec![ResponseItem::GhostSnapshot { - ghost_commit: GhostCommit::new("ghost-1".to_string(), None, Vec::new(), Vec::new()), + ghost_commit: GhostCommit::new( + "ghost-1".to_string(), + /*parent*/ None, + Vec::new(), + Vec::new(), + ), }]; let history = create_history_with_items(items); let modalities = default_input_modalities(); @@ -792,7 +798,7 @@ fn drop_last_n_user_turns_preserves_prefix() { let modalities = default_input_modalities(); let mut history = create_history_with_items(items); - history.drop_last_n_user_turns(1); + history.drop_last_n_user_turns(/*num_turns*/ 1); assert_eq!( history.for_prompt(&modalities), vec![ @@ -809,7 +815,7 @@ fn drop_last_n_user_turns_preserves_prefix() { user_msg("u2"), assistant_msg("a2"), ]); - history.drop_last_n_user_turns(99); + history.drop_last_n_user_turns(/*num_turns*/ 99); assert_eq!( history.for_prompt(&modalities), vec![assistant_msg("session prefix item")] @@ -838,7 +844,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() { let modalities = default_input_modalities(); let mut history = create_history_with_items(items); - history.drop_last_n_user_turns(1); + history.drop_last_n_user_turns(/*num_turns*/ 1); let expected_prefix_and_first_turn = vec![ user_input_text_msg("ctx"), @@ -892,7 +898,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() { user_input_text_msg("turn 2 user"), assistant_msg("turn 2 assistant"), ]); - history.drop_last_n_user_turns(2); + history.drop_last_n_user_turns(/*num_turns*/ 2); assert_eq!(history.for_prompt(&modalities), expected_prefix_only); let mut history = create_history_with_items(vec![ @@ -912,7 +918,7 @@ fn drop_last_n_user_turns_ignores_session_prefix_user_messages() { user_input_text_msg("turn 2 user"), assistant_msg("turn 2 assistant"), ]); - history.drop_last_n_user_turns(3); + history.drop_last_n_user_turns(/*num_turns*/ 3); assert_eq!(history.for_prompt(&modalities), expected_prefix_only); } @@ -935,7 +941,7 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() { let mut history = create_history_with_items(items); let reference_context_item = reference_context_item(); history.set_reference_context_item(Some(reference_context_item.clone())); - history.drop_last_n_user_turns(1); + history.drop_last_n_user_turns(/*num_turns*/ 1); assert_eq!( history.clone().for_prompt(&modalities), @@ -973,7 +979,7 @@ fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_b let modalities = default_input_modalities(); let mut history = create_history_with_items(items); history.set_reference_context_item(Some(reference_context_item())); - history.drop_last_n_user_turns(1); + history.drop_last_n_user_turns(/*num_turns*/ 1); assert_eq!( history.clone().for_prompt(&modalities), @@ -1165,7 +1171,7 @@ fn format_exec_output_truncates_large_error() { let truncated = truncate_exec_output(&large_error); - assert_truncated_message_matches(&truncated, line, 36250); + assert_truncated_message_matches(&truncated, line, /*expected_removed*/ 36250); assert_ne!(truncated, large_error); } @@ -1174,7 +1180,7 @@ fn format_exec_output_marks_byte_truncation_without_omitted_lines() { let long_line = "a".repeat(EXEC_FORMAT_MAX_BYTES + 10000); let truncated = truncate_exec_output(&long_line); assert_ne!(truncated, long_line); - assert_truncated_message_matches(&truncated, "a", 2500); + assert_truncated_message_matches(&truncated, "a", /*expected_removed*/ 2500); assert!( !truncated.contains("omitted"), "line omission marker should not appear when no lines were dropped: {truncated}" @@ -1196,7 +1202,7 @@ fn format_exec_output_reports_omitted_lines_and_keeps_head_and_tail() { .collect(); let truncated = truncate_exec_output(&content); - assert_truncated_message_matches(&truncated, "line-0-", 34_723); + assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 34_723); assert!( truncated.contains("line-0-"), "expected head line to remain: {truncated}" @@ -1219,7 +1225,7 @@ fn format_exec_output_prefers_line_marker_when_both_limits_exceeded() { let truncated = truncate_exec_output(&content); - assert_truncated_message_matches(&truncated, "line-0-", 17_423); + assert_truncated_message_matches(&truncated, "line-0-", /*expected_removed*/ 17_423); } #[cfg(not(debug_assertions))] diff --git a/codex-rs/core/src/contextual_user_message_tests.rs b/codex-rs/core/src/contextual_user_message_tests.rs index 93db860ea9..406f03c27f 100644 --- a/codex-rs/core/src/contextual_user_message_tests.rs +++ b/codex-rs/core/src/contextual_user_message_tests.rs @@ -86,8 +86,8 @@ fn detects_hook_prompt_fragment_and_roundtrips_escaping() { let ContentItem::InputText { text } = content_item else { panic!("expected input text content item"); }; - let parsed = - parse_visible_hook_prompt_message(None, content.as_slice()).expect("visible hook prompt"); + let parsed = parse_visible_hook_prompt_message(/*id*/ None, content.as_slice()) + .expect("visible hook prompt"); assert_eq!( parsed.fragments, vec![HookPromptFragment { diff --git a/codex-rs/core/src/custom_prompts.rs b/codex-rs/core/src/custom_prompts.rs deleted file mode 100644 index 54ccaa62fe..0000000000 --- a/codex-rs/core/src/custom_prompts.rs +++ /dev/null @@ -1,149 +0,0 @@ -use codex_protocol::custom_prompts::CustomPrompt; -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; -use tokio::fs; - -/// Return the default prompts directory: `$CODEX_HOME/prompts`. -/// If `CODEX_HOME` cannot be resolved, returns `None`. -pub fn default_prompts_dir() -> Option { - crate::config::find_codex_home() - .ok() - .map(|home| home.join("prompts")) -} - -/// Discover prompt files in the given directory, returning entries sorted by name. -/// Non-files are ignored. If the directory does not exist or cannot be read, returns empty. -pub async fn discover_prompts_in(dir: &Path) -> Vec { - discover_prompts_in_excluding(dir, &HashSet::new()).await -} - -/// Discover prompt files in the given directory, excluding any with names in `exclude`. -/// Returns entries sorted by name. Non-files are ignored. Missing/unreadable dir yields empty. -pub async fn discover_prompts_in_excluding( - dir: &Path, - exclude: &HashSet, -) -> Vec { - let mut out: Vec = Vec::new(); - let mut entries = match fs::read_dir(dir).await { - Ok(entries) => entries, - Err(_) => return out, - }; - - while let Ok(Some(entry)) = entries.next_entry().await { - let path = entry.path(); - let is_file_like = fs::metadata(&path) - .await - .map(|m| m.is_file()) - .unwrap_or(false); - if !is_file_like { - continue; - } - // Only include Markdown files with a .md extension. - let is_md = path - .extension() - .and_then(|s| s.to_str()) - .map(|ext| ext.eq_ignore_ascii_case("md")) - .unwrap_or(false); - if !is_md { - continue; - } - let Some(name) = path - .file_stem() - .and_then(|s| s.to_str()) - .map(str::to_string) - else { - continue; - }; - if exclude.contains(&name) { - continue; - } - let content = match fs::read_to_string(&path).await { - Ok(s) => s, - Err(_) => continue, - }; - let (description, argument_hint, body) = parse_frontmatter(&content); - out.push(CustomPrompt { - name, - path, - content: body, - description, - argument_hint, - }); - } - out.sort_by(|a, b| a.name.cmp(&b.name)); - out -} - -/// Parse optional YAML-like frontmatter at the beginning of `content`. -/// Supported keys: -/// - `description`: short description shown in the slash popup -/// - `argument-hint` or `argument_hint`: brief hint string shown after the description -/// Returns (description, argument_hint, body_without_frontmatter). -fn parse_frontmatter(content: &str) -> (Option, Option, String) { - let mut segments = content.split_inclusive('\n'); - let Some(first_segment) = segments.next() else { - return (None, None, String::new()); - }; - let first_line = first_segment.trim_end_matches(['\r', '\n']); - if first_line.trim() != "---" { - return (None, None, content.to_string()); - } - - let mut desc: Option = None; - let mut hint: Option = None; - let mut frontmatter_closed = false; - let mut consumed = first_segment.len(); - - for segment in segments { - let line = segment.trim_end_matches(['\r', '\n']); - let trimmed = line.trim(); - - if trimmed == "---" { - frontmatter_closed = true; - consumed += segment.len(); - break; - } - - if trimmed.is_empty() || trimmed.starts_with('#') { - consumed += segment.len(); - continue; - } - - if let Some((k, v)) = trimmed.split_once(':') { - let key = k.trim().to_ascii_lowercase(); - let mut val = v.trim().to_string(); - if val.len() >= 2 { - let bytes = val.as_bytes(); - let first = bytes[0]; - let last = bytes[bytes.len() - 1]; - if (first == b'\"' && last == b'\"') || (first == b'\'' && last == b'\'') { - val = val[1..val.len().saturating_sub(1)].to_string(); - } - } - match key.as_str() { - "description" => desc = Some(val), - "argument-hint" | "argument_hint" => hint = Some(val), - _ => {} - } - } - - consumed += segment.len(); - } - - if !frontmatter_closed { - // Unterminated frontmatter: treat input as-is. - return (None, None, content.to_string()); - } - - let body = if consumed >= content.len() { - String::new() - } else { - content[consumed..].to_string() - }; - (desc, hint, body) -} - -#[cfg(test)] -#[path = "custom_prompts_tests.rs"] -mod tests; diff --git a/codex-rs/core/src/custom_prompts_tests.rs b/codex-rs/core/src/custom_prompts_tests.rs deleted file mode 100644 index b1208a04e0..0000000000 --- a/codex-rs/core/src/custom_prompts_tests.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::*; -use std::fs; -use tempfile::tempdir; - -#[tokio::test] -async fn empty_when_dir_missing() { - let tmp = tempdir().expect("create TempDir"); - let missing = tmp.path().join("nope"); - let found = discover_prompts_in(&missing).await; - assert!(found.is_empty()); -} - -#[tokio::test] -async fn discovers_and_sorts_files() { - let tmp = tempdir().expect("create TempDir"); - let dir = tmp.path(); - fs::write(dir.join("b.md"), b"b").unwrap(); - fs::write(dir.join("a.md"), b"a").unwrap(); - fs::create_dir(dir.join("subdir")).unwrap(); - let found = discover_prompts_in(dir).await; - let names: Vec = found.into_iter().map(|e| e.name).collect(); - assert_eq!(names, vec!["a", "b"]); -} - -#[tokio::test] -async fn excludes_builtins() { - let tmp = tempdir().expect("create TempDir"); - let dir = tmp.path(); - fs::write(dir.join("init.md"), b"ignored").unwrap(); - fs::write(dir.join("foo.md"), b"ok").unwrap(); - let mut exclude = HashSet::new(); - exclude.insert("init".to_string()); - let found = discover_prompts_in_excluding(dir, &exclude).await; - let names: Vec = found.into_iter().map(|e| e.name).collect(); - assert_eq!(names, vec!["foo"]); -} - -#[tokio::test] -async fn skips_non_utf8_files() { - let tmp = tempdir().expect("create TempDir"); - let dir = tmp.path(); - // Valid UTF-8 file - fs::write(dir.join("good.md"), b"hello").unwrap(); - // Invalid UTF-8 content in .md file (e.g., lone 0xFF byte) - fs::write(dir.join("bad.md"), vec![0xFF, 0xFE, b'\n']).unwrap(); - let found = discover_prompts_in(dir).await; - let names: Vec = found.into_iter().map(|e| e.name).collect(); - assert_eq!(names, vec!["good"]); -} - -#[tokio::test] -#[cfg(unix)] -async fn discovers_symlinked_md_files() { - let tmp = tempdir().expect("create TempDir"); - let dir = tmp.path(); - - // Create a real file - fs::write(dir.join("real.md"), b"real content").unwrap(); - - // Create a symlink to the real file - std::os::unix::fs::symlink(dir.join("real.md"), dir.join("link.md")).unwrap(); - - let found = discover_prompts_in(dir).await; - let names: Vec = found.into_iter().map(|e| e.name).collect(); - - // Both real and link should be discovered, sorted alphabetically - assert_eq!(names, vec!["link", "real"]); -} - -#[tokio::test] -async fn parses_frontmatter_and_strips_from_body() { - let tmp = tempdir().expect("create TempDir"); - let dir = tmp.path(); - let file = dir.join("withmeta.md"); - let text = "---\nname: ignored\ndescription: \"Quick review command\"\nargument-hint: \"[file] [priority]\"\n---\nActual body with $1 and $ARGUMENTS"; - fs::write(&file, text).unwrap(); - - let found = discover_prompts_in(dir).await; - assert_eq!(found.len(), 1); - let p = &found[0]; - assert_eq!(p.name, "withmeta"); - assert_eq!(p.description.as_deref(), Some("Quick review command")); - assert_eq!(p.argument_hint.as_deref(), Some("[file] [priority]")); - // Body should not include the frontmatter delimiters. - assert_eq!(p.content, "Actual body with $1 and $ARGUMENTS"); -} - -#[test] -fn parse_frontmatter_preserves_body_newlines() { - let content = "---\r\ndescription: \"Line endings\"\r\nargument_hint: \"[arg]\"\r\n---\r\nFirst line\r\nSecond line\r\n"; - let (desc, hint, body) = parse_frontmatter(content); - assert_eq!(desc.as_deref(), Some("Line endings")); - assert_eq!(hint.as_deref(), Some("[arg]")); - assert_eq!(body, "First line\r\nSecond line\r\n"); -} diff --git a/codex-rs/core/src/environment_context.rs b/codex-rs/core/src/environment_context.rs index bbb39da113..df4e49cf4e 100644 --- a/codex-rs/core/src/environment_context.rs +++ b/codex-rs/core/src/environment_context.rs @@ -130,8 +130,16 @@ impl EnvironmentContext { .as_ref()?; Some(NetworkContext { - allowed_domains: network.allowed_domains.clone().unwrap_or_default(), - denied_domains: network.denied_domains.clone().unwrap_or_default(), + allowed_domains: network + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains) + .unwrap_or_default(), + denied_domains: network + .domains + .as_ref() + .and_then(codex_config::NetworkDomainPermissionsToml::denied_domains) + .unwrap_or_default(), }) } diff --git a/codex-rs/core/src/environment_context_tests.rs b/codex-rs/core/src/environment_context_tests.rs index 5718c09de4..073f1fe169 100644 --- a/codex-rs/core/src/environment_context_tests.rs +++ b/codex-rs/core/src/environment_context_tests.rs @@ -20,8 +20,8 @@ fn serialize_workspace_write_environment_context() { fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, - None, + /*network*/ None, + /*subagents*/ None, ); let expected = format!( @@ -49,7 +49,7 @@ fn serialize_environment_context_with_network() { Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), Some(network), - None, + /*subagents*/ None, ); let expected = format!( @@ -73,12 +73,12 @@ fn serialize_environment_context_with_network() { #[test] fn serialize_read_only_environment_context() { let context = EnvironmentContext::new( - None, + /*cwd*/ None, fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, - None, + /*network*/ None, + /*subagents*/ None, ); let expected = r#" @@ -93,12 +93,12 @@ fn serialize_read_only_environment_context() { #[test] fn serialize_external_sandbox_environment_context() { let context = EnvironmentContext::new( - None, + /*cwd*/ None, fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, - None, + /*network*/ None, + /*subagents*/ None, ); let expected = r#" @@ -113,12 +113,12 @@ fn serialize_external_sandbox_environment_context() { #[test] fn serialize_external_sandbox_with_restricted_network_environment_context() { let context = EnvironmentContext::new( - None, + /*cwd*/ None, fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, - None, + /*network*/ None, + /*subagents*/ None, ); let expected = r#" @@ -133,12 +133,12 @@ fn serialize_external_sandbox_with_restricted_network_environment_context() { #[test] fn serialize_full_access_environment_context() { let context = EnvironmentContext::new( - None, + /*cwd*/ None, fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, - None, + /*network*/ None, + /*subagents*/ None, ); let expected = r#" @@ -155,18 +155,18 @@ fn equals_except_shell_compares_cwd() { let context1 = EnvironmentContext::new( Some(PathBuf::from("/repo")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); let context2 = EnvironmentContext::new( Some(PathBuf::from("/repo")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); assert!(context1.equals_except_shell(&context2)); } @@ -176,18 +176,18 @@ fn equals_except_shell_ignores_sandbox_policy() { let context1 = EnvironmentContext::new( Some(PathBuf::from("/repo")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); let context2 = EnvironmentContext::new( Some(PathBuf::from("/repo")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); assert!(context1.equals_except_shell(&context2)); @@ -198,18 +198,18 @@ fn equals_except_shell_compares_cwd_differences() { let context1 = EnvironmentContext::new( Some(PathBuf::from("/repo1")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); let context2 = EnvironmentContext::new( Some(PathBuf::from("/repo2")), fake_shell(), - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); assert!(!context1.equals_except_shell(&context2)); @@ -224,10 +224,10 @@ fn equals_except_shell_ignores_shell() { shell_path: "/bin/bash".into(), shell_snapshot: crate::shell::empty_shell_snapshot_receiver(), }, - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); let context2 = EnvironmentContext::new( Some(PathBuf::from("/repo")), @@ -236,10 +236,10 @@ fn equals_except_shell_ignores_shell() { shell_path: "/bin/zsh".into(), shell_snapshot: crate::shell::empty_shell_snapshot_receiver(), }, - None, - None, - None, - None, + /*current_date*/ None, + /*timezone*/ None, + /*network*/ None, + /*subagents*/ None, ); assert!(context1.equals_except_shell(&context2)); @@ -252,7 +252,7 @@ fn serialize_environment_context_with_subagents() { fake_shell(), Some("2026-02-26".to_string()), Some("America/Los_Angeles".to_string()), - None, + /*network*/ None, Some("- agent-1: atlas\n- agent-2".to_string()), ); diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 1fef42e0ad..f8058915b8 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -439,7 +439,12 @@ impl std::fmt::Display for UsageLimitReachedError { "You've hit your usage limit. Upgrade to Pro (https://chatgpt.com/explore/pro), visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", retry_suffix_after_or(self.resets_at.as_ref()) ), - Some(PlanType::Known(KnownPlan::Team)) | Some(PlanType::Known(KnownPlan::Business)) => { + Some(PlanType::Known( + KnownPlan::Team + | KnownPlan::SelfServeBusinessUsageBased + | KnownPlan::Business + | KnownPlan::EnterpriseCbpUsageBased, + )) => { format!( "You've hit your usage limit. To get more access now, send a request to your admin{}", retry_suffix_after_or(self.resets_at.as_ref()) @@ -460,21 +465,6 @@ impl std::fmt::Display for UsageLimitReachedError { "You've hit your usage limit.{}", retry_suffix(self.resets_at.as_ref()) ), - Some(PlanType::Unknown(plan)) - if plan.eq_ignore_ascii_case("self_serve_business_usage_based") => - { - match self - .rate_limits - .as_ref() - .and_then(|snapshot| snapshot.credits.as_ref()) - .map(|credits| credits.has_credits) - { - Some(true) => "You've hit your usage limit. Contact your admin to increase spend limits to continue." - .to_string(), - Some(false) | None => "You've hit your usage limit. Contact your admin to add credits to continue." - .to_string(), - } - } Some(PlanType::Unknown(_)) | None => format!( "You've hit your usage limit.{}", retry_suffix(self.resets_at.as_ref()) diff --git a/codex-rs/core/src/error_tests.rs b/codex-rs/core/src/error_tests.rs index 51cbf42dde..623831b2cf 100644 --- a/codex-rs/core/src/error_tests.rs +++ b/codex-rs/core/src/error_tests.rs @@ -243,6 +243,34 @@ fn usage_limit_reached_error_formats_business_plan_without_reset() { ); } +#[test] +fn usage_limit_reached_error_formats_self_serve_business_usage_based_plan() { + let err = UsageLimitReachedError { + plan_type: Some(PlanType::Known(KnownPlan::SelfServeBusinessUsageBased)), + resets_at: None, + rate_limits: Some(Box::new(rate_limit_snapshot())), + promo_message: None, + }; + assert_eq!( + err.to_string(), + "You've hit your usage limit. To get more access now, send a request to your admin or try again later." + ); +} + +#[test] +fn usage_limit_reached_error_formats_enterprise_cbp_usage_based_plan() { + let err = UsageLimitReachedError { + plan_type: Some(PlanType::Known(KnownPlan::EnterpriseCbpUsageBased)), + resets_at: None, + rate_limits: Some(Box::new(rate_limit_snapshot())), + promo_message: None, + }; + assert_eq!( + err.to_string(), + "You've hit your usage limit. To get more access now, send a request to your admin or try again later." + ); +} + #[test] fn usage_limit_reached_error_formats_default_for_other_plans() { let err = UsageLimitReachedError { diff --git a/codex-rs/core/src/event_mapping_tests.rs b/codex-rs/core/src/event_mapping_tests.rs index 65c2e7d97d..a06111fd9a 100644 --- a/codex-rs/core/src/event_mapping_tests.rs +++ b/codex-rs/core/src/event_mapping_tests.rs @@ -56,7 +56,7 @@ fn parses_user_message_with_text_and_two_images() { #[test] fn skips_local_image_label_text() { let image_url = "data:image/png;base64,abc".to_string(); - let label = codex_protocol::models::local_image_open_tag_text(1); + let label = codex_protocol::models::local_image_open_tag_text(/*label_number*/ 1); let user_text = "Please review this image.".to_string(); let item = ResponseItem::Message { diff --git a/codex-rs/core/src/exec_env_tests.rs b/codex-rs/core/src/exec_env_tests.rs index 6f001b5828..ec6b5b5bb5 100644 --- a/codex-rs/core/src/exec_env_tests.rs +++ b/codex-rs/core/src/exec_env_tests.rs @@ -121,7 +121,7 @@ fn populate_env_inserts_thread_id() { fn populate_env_omits_thread_id_when_missing() { let vars = make_vars(&[("PATH", "/usr/bin")]); let policy = ShellEnvironmentPolicy::default(); - let result = populate_env(vars, &policy, None); + let result = populate_env(vars, &policy, /*thread_id*/ None); let expected: HashMap = hashmap! { "PATH".to_string() => "/usr/bin".to_string(), diff --git a/codex-rs/core/src/exec_policy_tests.rs b/codex-rs/core/src/exec_policy_tests.rs index 200f302a20..03a5862141 100644 --- a/codex-rs/core/src/exec_policy_tests.rs +++ b/codex-rs/core/src/exec_policy_tests.rs @@ -115,7 +115,10 @@ async fn child_uses_parent_exec_policy_when_non_exec_policy_layers_differ() { let mut child_config = parent_config.clone(); let mut layers: Vec<_> = child_config .config_layer_stack - .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ true, + ) .into_iter() .cloned() .collect(); @@ -156,7 +159,10 @@ async fn child_does_not_use_parent_exec_policy_when_requirements_exec_policy_dif child_config.config_layer_stack = ConfigLayerStack::new( child_config .config_layer_stack - .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .get_layers( + ConfigLayerStackOrdering::LowestPrecedenceFirst, + /*include_disabled*/ true, + ) .into_iter() .cloned() .collect(), @@ -291,7 +297,7 @@ async fn merges_requirements_exec_policy_network_rules() -> anyhow::Result<()> { "blocked.example.com", codex_execpolicy::NetworkRuleProtocol::Https, Decision::Forbidden, - None, + /*justification*/ None, )?; let requirements = ConfigRequirements { @@ -338,7 +344,7 @@ host_executable(name = "git", paths = ["{git_path_literal}"]) "blocked.example.com", codex_execpolicy::NetworkRuleProtocol::Https, Decision::Forbidden, - None, + /*justification*/ None, )?; let requirements = ConfigRequirements { @@ -805,7 +811,7 @@ fn unmatched_granular_policy_still_prompts_for_restricted_sandbox_escalation() { &read_only_file_system_sandbox_policy(), &command, SandboxPermissions::RequireEscalated, - false, + /*used_complex_parsing*/ false, ) ); } @@ -823,7 +829,7 @@ fn unmatched_on_request_uses_split_filesystem_policy_for_escalation_prompts() { &restricted_file_system_policy, &command, SandboxPermissions::RequireEscalated, - false, + /*used_complex_parsing*/ false, ) ); } @@ -1348,7 +1354,7 @@ fn derive_requested_execpolicy_amendment_for_test( fn derive_requested_execpolicy_amendment_returns_none_for_missing_prefix_rule() { assert_eq!( None, - derive_requested_execpolicy_amendment_for_test(None, &[]) + derive_requested_execpolicy_amendment_for_test(/*prefix_rule*/ None, &[]) ); } diff --git a/codex-rs/core/src/exec_tests.rs b/codex-rs/core/src/exec_tests.rs index 07532e5a18..cf00d70501 100644 --- a/codex-rs/core/src/exec_tests.rs +++ b/codex-rs/core/src/exec_tests.rs @@ -24,7 +24,7 @@ fn make_exec_output( #[test] fn sandbox_detection_requires_keywords() { - let output = make_exec_output(1, "", "", ""); + let output = make_exec_output(/*exit_code*/ 1, "", "", ""); assert!(!is_likely_sandbox_denied( SandboxType::LinuxSeccomp, &output @@ -33,13 +33,13 @@ fn sandbox_detection_requires_keywords() { #[test] fn sandbox_detection_identifies_keyword_in_stderr() { - let output = make_exec_output(1, "", "Operation not permitted", ""); + let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", ""); assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output)); } #[test] fn sandbox_detection_respects_quick_reject_exit_codes() { - let output = make_exec_output(127, "", "command not found", ""); + let output = make_exec_output(/*exit_code*/ 127, "", "command not found", ""); assert!(!is_likely_sandbox_denied( SandboxType::LinuxSeccomp, &output @@ -48,14 +48,14 @@ fn sandbox_detection_respects_quick_reject_exit_codes() { #[test] fn sandbox_detection_ignores_non_sandbox_mode() { - let output = make_exec_output(1, "", "Operation not permitted", ""); + let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", ""); assert!(!is_likely_sandbox_denied(SandboxType::None, &output)); } #[test] fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() { let output = make_exec_output( - 0, + /*exit_code*/ 0, "", "", r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","reason":"not_allowed","source":"decider","protocol":"http","host":"google.com","port":80}"#, @@ -66,7 +66,7 @@ fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() { #[test] fn sandbox_detection_uses_aggregated_output() { let output = make_exec_output( - 101, + /*exit_code*/ 101, "", "", "cargo failed: Read-only file system when writing target", @@ -80,7 +80,7 @@ fn sandbox_detection_uses_aggregated_output() { #[test] fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() { let output = make_exec_output( - 0, + /*exit_code*/ 0, "", "", r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","source":"decider","protocol":"http","host":"google.com","port":80}"#, @@ -100,9 +100,14 @@ async fn read_output_limits_retained_bytes_for_shell_capture() { writer.write_all(&bytes).await.expect("write"); }); - let out = read_output(reader, None, false, Some(EXEC_OUTPUT_MAX_BYTES)) - .await - .expect("read"); + let out = read_output( + reader, + /*stream*/ None, + /*is_stderr*/ false, + Some(EXEC_OUTPUT_MAX_BYTES), + ) + .await + .expect("read"); assert_eq!(out.text.len(), EXEC_OUTPUT_MAX_BYTES); } @@ -196,7 +201,11 @@ async fn read_output_retains_all_bytes_for_full_buffer_capture() { writer.write_all(&bytes).await.expect("write"); }); - let out = read_output(reader, None, false, None).await.expect("read"); + let out = read_output( + reader, /*stream*/ None, /*is_stderr*/ false, /*max_bytes*/ None, + ) + .await + .expect("read"); assert_eq!(out.text.len(), expected_len); } @@ -211,7 +220,7 @@ fn aggregate_output_keeps_all_bytes_when_uncapped() { truncated_after_lines: None, }; - let aggregated = aggregate_output(&stdout, &stderr, None); + let aggregated = aggregate_output(&stdout, &stderr, /*max_bytes*/ None); assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES * 2); assert_eq!( @@ -362,8 +371,8 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result NetworkSandboxPolicy::Enabled, cwd.as_path(), &None, - false, - None, + /*use_legacy_landlock*/ false, + /*stdout_stream*/ None, ) .await?; @@ -675,14 +684,15 @@ fn windows_elevated_rejects_split_write_read_carveouts() { #[test] fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions() { - let expected = codex_sandboxing::get_platform_sandbox(false).unwrap_or(SandboxType::None); + let expected = codex_sandboxing::get_platform_sandbox(/*windows_sandbox_enabled*/ false) + .unwrap_or(SandboxType::None); assert_eq!( select_process_exec_tool_sandbox_type( &FileSystemSandboxPolicy::unrestricted(), NetworkSandboxPolicy::Restricted, codex_protocol::config_types::WindowsSandboxLevel::Disabled, - false, + /*enforce_managed_network*/ false, ), expected ); @@ -736,8 +746,8 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()> &FileSystemSandboxPolicy::from(&SandboxPolicy::new_read_only_policy()), None, NetworkSandboxPolicy::Restricted, - None, - None, + /*stdout_stream*/ None, + /*after_spawn*/ None, ) .await?; assert!(output.timed_out); @@ -798,8 +808,8 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> { NetworkSandboxPolicy::Enabled, cwd.as_path(), &None, - false, - None, + /*use_legacy_landlock*/ false, + /*stdout_stream*/ None, ) .await; let output = match result { diff --git a/codex-rs/core/src/external_agent_config_tests.rs b/codex-rs/core/src/external_agent_config_tests.rs index a760f73e19..7baadbec01 100644 --- a/codex-rs/core/src/external_agent_config_tests.rs +++ b/codex-rs/core/src/external_agent_config_tests.rs @@ -390,7 +390,7 @@ fn import_skills_returns_only_new_skill_directory_count() { fs::create_dir_all(agents_skills.join("skill-a")).expect("create existing target"); let copied_count = service_for_paths(claude_home, codex_home) - .import_skills(None) + .import_skills(/*cwd*/ None) .expect("import skills"); assert_eq!(copied_count, 1); diff --git a/codex-rs/core/src/file_watcher_tests.rs b/codex-rs/core/src/file_watcher_tests.rs index 53f6391f57..11bb37c8c6 100644 --- a/codex-rs/core/src/file_watcher_tests.rs +++ b/codex-rs/core/src/file_watcher_tests.rs @@ -115,10 +115,10 @@ fn is_mutating_event_filters_non_mutating_event_kinds() { fn register_dedupes_by_path_and_scope() { let watcher = Arc::new(FileWatcher::noop()); let (subscriber, _rx) = watcher.add_subscriber(); - let _first = subscriber.register_path(path("/tmp/skills"), false); - let _second = subscriber.register_path(path("/tmp/skills"), false); - let _third = subscriber.register_path(path("/tmp/skills"), true); - let _fourth = subscriber.register_path(path("/tmp/other-skills"), true); + let _first = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false); + let _second = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false); + let _third = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true); + let _fourth = subscriber.register_path(path("/tmp/other-skills"), /*recursive*/ true); assert_eq!( watcher.watch_counts_for_test(&path("/tmp/skills")), @@ -134,7 +134,7 @@ fn register_dedupes_by_path_and_scope() { fn watch_registration_drop_unregisters_paths() { let watcher = Arc::new(FileWatcher::noop()); let (subscriber, _rx) = watcher.add_subscriber(); - let registration = subscriber.register_path(path("/tmp/skills"), true); + let registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true); drop(registration); @@ -146,7 +146,7 @@ fn subscriber_drop_unregisters_paths() { let watcher = Arc::new(FileWatcher::noop()); let registration = { let (subscriber, _rx) = watcher.add_subscriber(); - subscriber.register_path(path("/tmp/skills"), true) + subscriber.register_path(path("/tmp/skills"), /*recursive*/ true) }; assert_eq!(watcher.watch_counts_for_test(&path("/tmp/skills")), None); @@ -174,8 +174,8 @@ fn recursive_registration_downgrades_to_non_recursive_after_drop() { let watcher = Arc::new(FileWatcher::new().expect("watcher")); let (subscriber, _rx) = watcher.add_subscriber(); - let non_recursive = subscriber.register_path(root.clone(), false); - let recursive = subscriber.register_path(root.clone(), true); + let non_recursive = subscriber.register_path(root.clone(), /*recursive*/ false); + let recursive = subscriber.register_path(root.clone(), /*recursive*/ true); { let inner = watcher.inner.as_ref().expect("watcher inner"); @@ -209,7 +209,7 @@ fn unregister_holds_state_lock_until_unwatch_finishes() { let watcher = Arc::new(FileWatcher::new().expect("watcher")); let (unregister_subscriber, _unregister_rx) = watcher.add_subscriber(); let (register_subscriber, _register_rx) = watcher.add_subscriber(); - let registration = unregister_subscriber.register_path(root.clone(), true); + let registration = unregister_subscriber.register_path(root.clone(), /*recursive*/ true); let inner = watcher.inner.as_ref().expect("watcher inner"); let inner_guard = inner.lock().expect("inner lock"); @@ -229,7 +229,8 @@ fn unregister_holds_state_lock_until_unwatch_finishes() { let register_root = root.clone(); let register_thread = std::thread::spawn(move || { - let registration = register_subscriber.register_path(register_root, false); + let registration = + register_subscriber.register_path(register_root, /*recursive*/ false); (register_subscriber, registration) }); @@ -257,8 +258,8 @@ async fn matching_subscribers_are_notified() { let watcher = Arc::new(FileWatcher::noop()); let (skills_subscriber, skills_rx) = watcher.add_subscriber(); let (plugins_subscriber, plugins_rx) = watcher.add_subscriber(); - let _skills = skills_subscriber.register_path(path("/tmp/skills"), true); - let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), true); + let _skills = skills_subscriber.register_path(path("/tmp/skills"), /*recursive*/ true); + let _plugins = plugins_subscriber.register_path(path("/tmp/plugins"), /*recursive*/ true); let mut skills_rx = ThrottledWatchReceiver::new(skills_rx, TEST_THROTTLE_INTERVAL); let mut plugins_rx = ThrottledWatchReceiver::new(plugins_rx, TEST_THROTTLE_INTERVAL); @@ -285,7 +286,7 @@ async fn matching_subscribers_are_notified() { async fn non_recursive_watch_ignores_grandchildren() { let watcher = Arc::new(FileWatcher::noop()); let (subscriber, rx) = watcher.add_subscriber(); - let _registration = subscriber.register_path(path("/tmp/skills"), false); + let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ false); let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL); watcher @@ -300,7 +301,8 @@ async fn non_recursive_watch_ignores_grandchildren() { async fn ancestor_events_notify_child_watches() { let watcher = Arc::new(FileWatcher::noop()); let (subscriber, rx) = watcher.add_subscriber(); - let _registration = subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), false); + let _registration = + subscriber.register_path(path("/tmp/skills/rust/SKILL.md"), /*recursive*/ false); let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL); watcher.send_paths_for_test(vec![path("/tmp/skills")]).await; @@ -321,7 +323,7 @@ async fn ancestor_events_notify_child_watches() { async fn spawn_event_loop_filters_non_mutating_events() { let watcher = Arc::new(FileWatcher::noop()); let (subscriber, rx) = watcher.add_subscriber(); - let _registration = subscriber.register_path(path("/tmp/skills"), true); + let _registration = subscriber.register_path(path("/tmp/skills"), /*recursive*/ true); let mut rx = ThrottledWatchReceiver::new(rx, TEST_THROTTLE_INTERVAL); let (raw_tx, raw_rx) = mpsc::unbounded_channel(); watcher.spawn_event_loop_for_test(raw_rx); diff --git a/codex-rs/core/src/git_info_tests.rs b/codex-rs/core/src/git_info_tests.rs index f6f77d1666..4cb50f02fd 100644 --- a/codex-rs/core/src/git_info_tests.rs +++ b/codex-rs/core/src/git_info_tests.rs @@ -72,7 +72,7 @@ async fn create_test_git_repo(temp_dir: &TempDir) -> PathBuf { #[tokio::test] async fn test_recent_commits_non_git_directory_returns_empty() { let temp_dir = TempDir::new().expect("Failed to create temp dir"); - let entries = recent_commits(temp_dir.path(), 10).await; + let entries = recent_commits(temp_dir.path(), /*limit*/ 10).await; assert!(entries.is_empty(), "expected no commits outside a git repo"); } @@ -133,7 +133,7 @@ async fn test_recent_commits_orders_and_limits() { .expect("git commit 3"); // Request the latest 3 commits; should be our three changes in reverse time order. - let entries = recent_commits(&repo_path, 3).await; + let entries = recent_commits(&repo_path, /*limit*/ 3).await; assert_eq!(entries.len(), 3); assert_eq!(entries[0].subject, "third change"); assert_eq!(entries[1].subject, "second change"); diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 64e27c42cd..17993a9743 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -746,9 +746,13 @@ mod tests { #[test] fn guardian_review_session_config_change_invalidates_cached_session() { let parent_config = crate::config::test_config(); - let cached_spawn_config = - build_guardian_review_session_config(&parent_config, None, "active-model", None) - .expect("cached guardian config"); + let cached_spawn_config = build_guardian_review_session_config( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect("cached guardian config"); let cached_reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(&cached_spawn_config); @@ -757,9 +761,9 @@ mod tests { Some("https://guardian.example.invalid/v1".to_string()); let next_spawn_config = build_guardian_review_session_config( &changed_parent_config, - None, + /*live_network_config*/ None, "active-model", - None, + /*reasoning_effort*/ None, ) .expect("next guardian config"); let next_reuse_key = GuardianReviewSessionReuseKey::from_spawn_config(&next_spawn_config); @@ -775,7 +779,7 @@ mod tests { async fn run_before_review_deadline_times_out_before_future_completes() { let outcome = run_before_review_deadline( tokio::time::Instant::now() + Duration::from_millis(10), - None, + /*external_cancel*/ None, async { tokio::time::sleep(Duration::from_millis(50)).await; }, @@ -816,7 +820,7 @@ mod tests { let outcome = run_before_review_deadline_with_cancel( tokio::time::Instant::now() + Duration::from_millis(10), - None, + /*external_cancel*/ None, &cancel_token, async { tokio::time::sleep(Duration::from_millis(50)).await; @@ -862,7 +866,7 @@ mod tests { let outcome = run_before_review_deadline_with_cancel( tokio::time::Instant::now() + Duration::from_secs(1), - None, + /*external_cancel*/ None, &cancel_token, async { 42usize }, ) diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 3e6d409e76..c0df326ebe 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -11,6 +11,8 @@ use crate::config::test_config; use crate::config_loader::ConfigLayerStack; use crate::config_loader::FeatureRequirementsToml; use crate::config_loader::NetworkConstraints; +use crate::config_loader::NetworkDomainPermissionToml; +use crate::config_loader::NetworkDomainPermissionsToml; use crate::config_loader::RequirementSource; use crate::config_loader::Sourced; use crate::protocol::SandboxPolicy; @@ -249,7 +251,7 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() { fn guardian_truncate_text_keeps_prefix_suffix_and_xml_marker() { let content = "prefix ".repeat(200) + &" suffix".repeat(200); - let truncated = guardian_truncate_text(&content, 20); + let truncated = guardian_truncate_text(&content, /*token_cap*/ 20); assert!(truncated.starts_with("prefix")); assert!(truncated.contains(" anyhow: Arc::clone(&turn), first_prompt, guardian_output_schema(), - None, + /*external_cancel*/ None, ) .await; let second_prompt = build_guardian_prompt_items( @@ -657,7 +659,7 @@ async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow: Arc::clone(&turn), second_prompt, guardian_output_schema(), - None, + /*external_cancel*/ None, ) .await; @@ -770,7 +772,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() -> additional_permissions: None, justification: Some("Need to push the reviewed docs fix.".to_string()), }, - None, + /*retry_reason*/ None, ) .await; @@ -882,7 +884,7 @@ async fn guardian_parallel_reviews_fork_from_last_committed_trunk_history() -> a justification: Some("Inspect repo state before proceeding.".to_string()), }; assert_eq!( - review_approval_request(&session, &turn, initial_request, None).await, + review_approval_request(&session, &turn, initial_request, /*retry_reason*/ None).await, ReviewDecision::Approved ); @@ -971,7 +973,12 @@ fn guardian_review_session_config_preserves_parent_network_proxy() { NetworkProxyConfig::default(), Some(NetworkConstraints { enabled: Some(true), - allowed_domains: Some(vec!["github.com".to_string()]), + domains: Some(NetworkDomainPermissionsToml { + entries: std::collections::BTreeMap::from([( + "github.com".to_string(), + NetworkDomainPermissionToml::Allow, + )]), + }), ..Default::default() }), parent_config.permissions.sandbox_policy.get(), @@ -981,7 +988,7 @@ fn guardian_review_session_config_preserves_parent_network_proxy() { let guardian_config = build_guardian_review_session_config_for_test( &parent_config, - None, + /*live_network_config*/ None, "parent-active-model", Some(codex_protocol::openai_models::ReasoningEffort::Low), ) @@ -1012,9 +1019,13 @@ fn guardian_review_session_config_overrides_parent_developer_instructions() { parent_config.developer_instructions = Some("parent or managed config should not replace guardian policy".to_string()); - let guardian_config = - build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None) - .expect("guardian config"); + let guardian_config = build_guardian_review_session_config_for_test( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect("guardian config"); assert_eq!( guardian_config.developer_instructions, @@ -1027,11 +1038,13 @@ fn guardian_review_session_config_uses_live_network_proxy_state() { let mut parent_config = test_config(); let mut parent_network = NetworkProxyConfig::default(); parent_network.network.enabled = true; - parent_network.network.allowed_domains = vec!["parent.example".to_string()]; + parent_network + .network + .set_allowed_domains(vec!["parent.example".to_string()]); parent_config.permissions.network = Some( NetworkProxySpec::from_config_and_constraints( parent_network, - None, + /*requirements*/ None, parent_config.permissions.sandbox_policy.get(), ) .expect("parent network proxy spec"), @@ -1039,13 +1052,15 @@ fn guardian_review_session_config_uses_live_network_proxy_state() { let mut live_network = NetworkProxyConfig::default(); live_network.network.enabled = true; - live_network.network.allowed_domains = vec!["github.com".to_string()]; + live_network + .network + .set_allowed_domains(vec!["github.com".to_string()]); let guardian_config = build_guardian_review_session_config_for_test( &parent_config, Some(live_network.clone()), "active-model", - None, + /*reasoning_effort*/ None, ) .expect("guardian config"); @@ -1054,7 +1069,7 @@ fn guardian_review_session_config_uses_live_network_proxy_state() { Some( NetworkProxySpec::from_config_and_constraints( live_network, - None, + /*requirements*/ None, &SandboxPolicy::new_read_only_policy(), ) .expect("live network proxy spec") @@ -1076,9 +1091,13 @@ fn guardian_review_session_config_rejects_pinned_collab_feature() { ) .expect("managed features"); - let err = - build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None) - .expect_err("guardian config should fail when collab is pinned on"); + let err = build_guardian_review_session_config_for_test( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect_err("guardian config should fail when collab is pinned on"); assert!( err.to_string() @@ -1091,9 +1110,13 @@ fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_ let mut parent_config = test_config(); parent_config.model = Some("configured-model".to_string()); - let guardian_config = - build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None) - .expect("guardian config"); + let guardian_config = build_guardian_review_session_config_for_test( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect("guardian config"); assert_eq!(guardian_config.model, Some("active-model".to_string())); } @@ -1124,9 +1147,13 @@ fn guardian_review_session_config_uses_requirements_guardian_override() { ) .expect("load config"); - let guardian_config = - build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None) - .expect("guardian config"); + let guardian_config = build_guardian_review_session_config_for_test( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect("guardian config"); assert_eq!( guardian_config.developer_instructions, @@ -1152,9 +1179,13 @@ fn guardian_review_session_config_uses_default_guardian_policy_without_requireme ) .expect("load config"); - let guardian_config = - build_guardian_review_session_config_for_test(&parent_config, None, "active-model", None) - .expect("guardian config"); + let guardian_config = build_guardian_review_session_config_for_test( + &parent_config, + /*live_network_config*/ None, + "active-model", + /*reasoning_effort*/ None, + ) + .expect("guardian config"); assert_eq!( guardian_config.developer_instructions, diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index f0390c805a..5276b09de3 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -30,7 +30,6 @@ pub mod config_loader; pub mod connectors; mod context_manager; mod contextual_user_message; -pub mod custom_prompts; pub use codex_utils_path::env; mod environment_context; pub mod error; diff --git a/codex-rs/core/src/mcp/auth.rs b/codex-rs/core/src/mcp/auth.rs index 06ddbdd51e..6b146f129b 100644 --- a/codex-rs/core/src/mcp/auth.rs +++ b/codex-rs/core/src/mcp/auth.rs @@ -209,7 +209,7 @@ mod tests { #[test] fn resolve_oauth_scopes_prefers_configured_over_discovered() { let resolved = resolve_oauth_scopes( - None, + /*explicit_scopes*/ None, Some(vec!["configured".to_string()]), Some(vec!["discovered".to_string()]), ); @@ -225,7 +225,11 @@ mod tests { #[test] fn resolve_oauth_scopes_uses_discovered_when_needed() { - let resolved = resolve_oauth_scopes(None, None, Some(vec!["discovered".to_string()])); + let resolved = resolve_oauth_scopes( + /*explicit_scopes*/ None, + /*configured_scopes*/ None, + Some(vec!["discovered".to_string()]), + ); assert_eq!( resolved, @@ -238,7 +242,11 @@ mod tests { #[test] fn resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes() { - let resolved = resolve_oauth_scopes(None, Some(Vec::new()), Some(vec!["ignored".into()])); + let resolved = resolve_oauth_scopes( + /*explicit_scopes*/ None, + Some(Vec::new()), + Some(vec!["ignored".into()]), + ); assert_eq!( resolved, @@ -251,7 +259,10 @@ mod tests { #[test] fn resolve_oauth_scopes_falls_back_to_empty() { - let resolved = resolve_oauth_scopes(None, None, None); + let resolved = resolve_oauth_scopes( + /*explicit_scopes*/ None, /*configured_scopes*/ None, + /*discovered_scopes*/ None, + ); assert_eq!( resolved, diff --git a/codex-rs/core/src/mcp/mod.rs b/codex-rs/core/src/mcp/mod.rs index a9d2388f70..afeb5647ea 100644 --- a/codex-rs/core/src/mcp/mod.rs +++ b/codex-rs/core/src/mcp/mod.rs @@ -33,6 +33,32 @@ const MCP_TOOL_NAME_DELIMITER: &str = "__"; pub(crate) const CODEX_APPS_MCP_SERVER_NAME: &str = "codex_apps"; const CODEX_CONNECTORS_TOKEN_ENV_VAR: &str = "CODEX_CONNECTORS_TOKEN"; +/// The Responses API requires tool names to match `^[a-zA-Z0-9_-]+$`. +/// MCP server/tool names are user-controlled, so sanitize the fully-qualified +/// name we expose to the model by replacing any disallowed character with `_`. +pub(crate) fn sanitize_responses_api_tool_name(name: &str) -> String { + let mut sanitized = String::with_capacity(name.len()); + for c in name.chars() { + if c.is_ascii_alphanumeric() || c == '_' { + sanitized.push(c); + } else { + sanitized.push('_'); + } + } + + if sanitized.is_empty() { + "_".to_string() + } else { + sanitized + } +} + +pub fn qualified_mcp_tool_name_prefix(server_name: &str) -> String { + sanitize_responses_api_tool_name(&format!( + "{MCP_TOOL_NAME_PREFIX}{MCP_TOOL_NAME_DELIMITER}{server_name}{MCP_TOOL_NAME_DELIMITER}" + )) +} + #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct ToolPluginProvenance { plugin_display_names_by_connector_id: HashMap>, diff --git a/codex-rs/core/src/mcp/mod_tests.rs b/codex-rs/core/src/mcp/mod_tests.rs index 855e71e1f2..6821fbce32 100644 --- a/codex-rs/core/src/mcp/mod_tests.rs +++ b/codex-rs/core/src/mcp/mod_tests.rs @@ -52,6 +52,14 @@ fn split_qualified_tool_name_returns_server_and_tool() { ); } +#[test] +fn qualified_mcp_tool_name_prefix_sanitizes_server_names_without_lowercasing() { + assert_eq!( + qualified_mcp_tool_name_prefix("Some-Server"), + "mcp__Some_Server__".to_string() + ); +} + #[test] fn split_qualified_tool_name_rejects_invalid_names() { assert_eq!(split_qualified_tool_name("other__alpha__do_thing"), None); @@ -159,7 +167,12 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() { let mut config = crate::config::test_config(); config.chatgpt_base_url = "https://chatgpt.com".to_string(); - let mut servers = with_codex_apps_mcp(HashMap::new(), false, None, &config); + let mut servers = with_codex_apps_mcp( + HashMap::new(), + /*connectors_enabled*/ false, + /*auth*/ None, + &config, + ); assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME)); config @@ -167,7 +180,9 @@ fn codex_apps_server_config_uses_legacy_codex_apps_path() { .enable(Feature::Apps) .expect("test config should allow apps"); - servers = with_codex_apps_mcp(servers, true, None, &config); + servers = with_codex_apps_mcp( + servers, /*connectors_enabled*/ true, /*auth*/ None, &config, + ); let server = servers .get(CODEX_APPS_MCP_SERVER_NAME) .expect("codex apps should be present when apps is enabled"); @@ -244,7 +259,7 @@ async fn effective_mcp_servers_include_plugins_without_overriding_user_config() .expect("test config should accept MCP servers"); let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone()))); - let effective = mcp_manager.effective_servers(&config, None); + let effective = mcp_manager.effective_servers(&config, /*auth*/ None); let sample = effective.get("sample").expect("user server should exist"); let docs = effective.get("docs").expect("plugin server should exist"); diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index eac8485048..63eea7ebc5 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -22,6 +22,7 @@ use std::time::Instant; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp::ToolPluginProvenance; use crate::mcp::auth::McpAuthStatusEntry; +use crate::mcp::sanitize_responses_api_tool_name; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; @@ -93,7 +94,7 @@ const MCP_TOOL_NAME_DELIMITER: &str = "__"; const MAX_TOOL_NAME_LENGTH: usize = 64; /// Default timeout for initializing MCP server & initially listing tools. -pub const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(10); +pub const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); /// Default timeout for individual tool calls. const DEFAULT_TOOL_TIMEOUT: Duration = Duration::from_secs(120); @@ -104,26 +105,6 @@ const MCP_TOOLS_LIST_DURATION_METRIC: &str = "codex.mcp.tools.list.duration_ms"; const MCP_TOOLS_FETCH_UNCACHED_DURATION_METRIC: &str = "codex.mcp.tools.fetch_uncached.duration_ms"; const MCP_TOOLS_CACHE_WRITE_DURATION_METRIC: &str = "codex.mcp.tools.cache_write.duration_ms"; -/// The Responses API requires tool names to match `^[a-zA-Z0-9_-]+$`. -/// MCP server/tool names are user-controlled, so sanitize the fully-qualified -/// name we expose to the model by replacing any disallowed character with `_`. -fn sanitize_responses_api_tool_name(name: &str) -> String { - let mut sanitized = String::with_capacity(name.len()); - for c in name.chars() { - if c.is_ascii_alphanumeric() || c == '_' { - sanitized.push(c); - } else { - sanitized.push('_'); - } - } - - if sanitized.is_empty() { - "_".to_string() - } else { - sanitized - } -} - fn sha1_hex(s: &str) -> String { let mut hasher = Sha1::new(); hasher.update(s.as_bytes()); diff --git a/codex-rs/core/src/mcp_connection_manager_tests.rs b/codex-rs/core/src/mcp_connection_manager_tests.rs index 2331d0c1fe..b34a5cc043 100644 --- a/codex-rs/core/src/mcp_connection_manager_tests.rs +++ b/codex-rs/core/src/mcp_connection_manager_tests.rs @@ -562,7 +562,7 @@ fn mcp_init_error_display_prompts_for_login_when_auth_required() { let server_name = "example"; let err: StartupOutcomeError = anyhow::anyhow!("Auth required for server").into(); - let display = mcp_init_error_display(server_name, None, &err); + let display = mcp_init_error_display(server_name, /*entry*/ None, &err); let expected = format!( "The {server_name} MCP server is not logged in. Run `codex mcp login {server_name}`." @@ -609,10 +609,10 @@ fn mcp_init_error_display_includes_startup_timeout_hint() { let server_name = "slow"; let err: StartupOutcomeError = anyhow::anyhow!("request timed out").into(); - let display = mcp_init_error_display(server_name, None, &err); + let display = mcp_init_error_display(server_name, /*entry*/ None, &err); assert_eq!( - "MCP client for `slow` timed out after 10 seconds. Add or adjust `startup_timeout_sec` in your config.toml:\n[mcp_servers.slow]\nstartup_timeout_sec = XX", + "MCP client for `slow` timed out after 30 seconds. Add or adjust `startup_timeout_sec` in your config.toml:\n[mcp_servers.slow]\nstartup_timeout_sec = XX", display ); } diff --git a/codex-rs/core/src/mcp_tool_approval_templates.rs b/codex-rs/core/src/mcp_tool_approval_templates.rs index b1e0ee27d9..66002ab5ab 100644 --- a/codex-rs/core/src/mcp_tool_approval_templates.rs +++ b/codex-rs/core/src/mcp_tool_approval_templates.rs @@ -330,7 +330,7 @@ mod tests { &templates, "codex_apps", Some("github"), - None, + /*connector_name*/ None, Some("add_comment"), Some(&json!({})), ); @@ -361,7 +361,7 @@ mod tests { &templates, "codex_apps", Some("calendar"), - None, + /*connector_name*/ None, Some("create_event"), Some(&json!({})), ), diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 128e7a30ba..f0b1fcb1c8 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -700,14 +700,14 @@ async fn maybe_request_mcp_tool_approval( let annotations = metadata.and_then(|metadata| metadata.annotations.as_ref()); let approval_required = requires_mcp_tool_approval(annotations); + if !approval_required && approval_mode != AppToolApproval::Prompt { + return None; + } + let mut monitor_reason = None; let auto_approved_by_policy = approval_mode == AppToolApproval::Approve; if auto_approved_by_policy { - if !approval_required { - return None; - } - match maybe_monitor_auto_approved_mcp_tool_call( sess, turn_context, @@ -729,10 +729,6 @@ async fn maybe_request_mcp_tool_approval( } } - if approval_mode == AppToolApproval::Auto && !approval_required { - return None; - } - let session_approval_key = session_mcp_tool_approval_key(invocation, metadata, approval_mode); let persistent_approval_key = persistent_mcp_tool_approval_key(invocation, metadata, approval_mode); diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index cf3c761faf..d7fcb374f3 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::codex::make_session_and_context; +use crate::codex::make_session_and_context_with_rx; use crate::config::ApprovalsReviewer; use crate::config::ConfigBuilder; use crate::config::ConfigToml; @@ -9,6 +10,7 @@ use crate::config::types::AppToolsConfig; use crate::config::types::AppsConfigToml; use crate::config::types::McpServerConfig; use crate::config::types::McpServerToolConfig; +use crate::state::ActiveTurn; use codex_config::CONFIG_TOML_FILE; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; @@ -70,13 +72,13 @@ fn prompt_options( #[test] fn approval_required_when_read_only_false_and_destructive() { - let annotations = annotations(Some(false), Some(true), None); + let annotations = annotations(Some(false), Some(true), /*open_world*/ None); assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true); } #[test] fn approval_required_when_read_only_false_and_open_world() { - let annotations = annotations(Some(false), None, Some(true)); + let annotations = annotations(Some(false), /*destructive*/ None, Some(true)); assert_eq!(requires_mcp_tool_approval(Some(&annotations)), true); } @@ -88,12 +90,16 @@ fn approval_required_when_destructive_even_if_read_only_true() { #[test] fn approval_required_when_annotations_are_absent() { - assert_eq!(requires_mcp_tool_approval(None), true); + assert_eq!(requires_mcp_tool_approval(/*annotations*/ None), true); } #[test] fn approval_not_required_when_read_only_and_other_hints_are_absent() { - let annotations = annotations(Some(true), None, None); + let annotations = annotations( + Some(true), + /*destructive*/ None, + /*open_world*/ None, + ); assert_eq!(requires_mcp_tool_approval(Some(&annotations)), false); } @@ -185,7 +191,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p CODEX_APPS_MCP_SERVER_NAME, "create_event", Some("Calendar"), - prompt_options(true, true), + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true, + ), Some("Allow Calendar to create an event?"), ); @@ -219,7 +227,9 @@ async fn approval_elicitation_request_uses_message_override_and_preserves_tool_p ]), question, message_override: Some("Allow Calendar to create an event?"), - prompt_options: prompt_options(true, true), + prompt_options: prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true, + ), }, ); @@ -277,9 +287,11 @@ fn custom_mcp_tool_question_mentions_server_name() { "q".to_string(), "custom_server", "run_action", - None, - prompt_options(false, false), - None, + /*connector_name*/ None, + prompt_options( + /*allow_session_remember*/ false, /*allow_persistent_approval*/ false, + ), + /*question_override*/ None, ); assert_eq!(question.header, "Approve app tool call?"); @@ -303,9 +315,11 @@ fn codex_apps_tool_question_uses_fallback_app_label() { "q".to_string(), CODEX_APPS_MCP_SERVER_NAME, "run_action", - None, - prompt_options(true, true), - None, + /*connector_name*/ None, + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true, + ), + /*question_override*/ None, ); assert_eq!( @@ -321,8 +335,10 @@ fn trusted_codex_apps_tool_question_offers_always_allow() { CODEX_APPS_MCP_SERVER_NAME, "run_action", Some("Calendar"), - prompt_options(true, true), - None, + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true, + ), + /*question_override*/ None, ); let options = question.options.expect("options"); @@ -361,8 +377,12 @@ fn codex_apps_tool_question_without_elicitation_omits_always_allow() { CODEX_APPS_MCP_SERVER_NAME, "run_action", Some("Calendar"), - mcp_tool_approval_prompt_options(Some(&session_key), Some(&persistent_key), false), - None, + mcp_tool_approval_prompt_options( + Some(&session_key), + Some(&persistent_key), + /*tool_call_mcp_elicitation_enabled*/ false, + ), + /*question_override*/ None, ); assert_eq!( @@ -386,9 +406,11 @@ fn custom_mcp_tool_question_offers_session_remember_and_always_allow() { "q".to_string(), "custom_server", "run_action", - None, - prompt_options(true, true), - None, + /*connector_name*/ None, + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true, + ), + /*question_override*/ None, ); assert_eq!( @@ -421,11 +443,15 @@ fn custom_servers_support_session_and_persistent_approval() { }; assert_eq!( - session_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto), + session_mcp_tool_approval_key(&invocation, /*metadata*/ None, AppToolApproval::Auto), Some(expected.clone()) ); assert_eq!( - persistent_mcp_tool_approval_key(&invocation, None, AppToolApproval::Auto), + persistent_mcp_tool_approval_key( + &invocation, + /*metadata*/ None, + AppToolApproval::Auto + ), Some(expected) ); } @@ -437,7 +463,13 @@ fn codex_apps_connectors_support_persistent_approval() { tool: "calendar/list_events".to_string(), arguments: None, }; - let metadata = approval_metadata(Some("calendar"), Some("Calendar"), None, None, None); + let metadata = approval_metadata( + Some("calendar"), + Some("Calendar"), + /*connector_description*/ None, + /*tool_title*/ None, + /*tool_description*/ None, + ); let expected = McpToolApprovalKey { server: CODEX_APPS_MCP_SERVER_NAME.to_string(), connector_id: Some("calendar".to_string()), @@ -473,7 +505,8 @@ fn sanitize_mcp_tool_result_for_model_rewrites_image_content() { meta: None, }); - let got = sanitize_mcp_tool_result_for_model(false, result).expect("sanitized result"); + let got = sanitize_mcp_tool_result_for_model(/*supports_image_input*/ false, result) + .expect("sanitized result"); assert_eq!( got.content, @@ -503,8 +536,11 @@ fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported() { meta: Some(serde_json::json!({"k": "v"})), }; - let got = - sanitize_mcp_tool_result_for_model(true, Ok(original.clone())).expect("unsanitized result"); + let got = sanitize_mcp_tool_result_for_model( + /*supports_image_input*/ true, + Ok(original.clone()), + ) + .expect("unsanitized result"); assert_eq!(got, original); } @@ -604,10 +640,12 @@ fn approval_elicitation_meta_marks_tool_approvals() { assert_eq!( build_mcp_tool_approval_elicitation_meta( "custom_server", - None, - None, - None, - prompt_options(false, false), + /*metadata*/ None, + /*tool_params*/ None, + /*tool_params_display*/ None, + prompt_options( + /*allow_session_remember*/ false, /*allow_persistent_approval*/ false + ), ), Some(serde_json::json!({ MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL, @@ -621,15 +659,17 @@ fn approval_elicitation_meta_merges_session_and_always_persist_for_custom_server build_mcp_tool_approval_elicitation_meta( "custom_server", Some(&approval_metadata( - None, - None, - None, + /*connector_id*/ None, + /*connector_name*/ None, + /*connector_description*/ None, Some("Run Action"), Some("Runs the selected action."), )), Some(&serde_json::json!({"id": 1})), - None, - prompt_options(true, true), + /*tool_params_display*/ None, + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true + ), ), Some(serde_json::json!({ MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL, @@ -740,11 +780,11 @@ fn prepare_arc_request_action_serializes_mcp_tool_call_shape() { let action = prepare_arc_request_action( &invocation, Some(&approval_metadata( - None, + /*connector_id*/ None, Some("Playwright"), - None, + /*connector_description*/ None, Some("Navigate"), - None, + /*tool_description*/ None, )), ); @@ -794,8 +834,10 @@ fn approval_elicitation_meta_includes_connector_source_for_codex_apps() { Some(&serde_json::json!({ "calendar_id": "primary", })), - None, - prompt_options(false, false), + /*tool_params_display*/ None, + prompt_options( + /*allow_session_remember*/ false, /*allow_persistent_approval*/ false + ), ), Some(serde_json::json!({ MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL, @@ -827,8 +869,10 @@ fn approval_elicitation_meta_merges_session_and_always_persist_with_connector_so Some(&serde_json::json!({ "calendar_id": "primary", })), - None, - prompt_options(true, true), + /*tool_params_display*/ None, + prompt_options( + /*allow_session_remember*/ true, /*allow_persistent_approval*/ true + ), ), Some(serde_json::json!({ MCP_TOOL_APPROVAL_KIND_KEY: MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL, @@ -1168,7 +1212,11 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() { arguments: None, }; let metadata = McpToolApprovalMetadata { - annotations: Some(annotations(Some(true), None, None)), + annotations: Some(annotations( + Some(true), + /*destructive*/ None, + /*open_world*/ None, + )), connector_id: None, connector_name: None, connector_description: None, @@ -1190,6 +1238,124 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() { assert_eq!(decision, None); } +#[tokio::test] +async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() { + use wiremock::Mock; + use wiremock::ResponseTemplate; + use wiremock::matchers::method; + use wiremock::matchers::path; + + let server = start_mock_server().await; + Mock::given(method("POST")) + .and(path("/v1/responses")) + .respond_with(ResponseTemplate::new(200)) + .expect(0) + .mount(&server) + .await; + + let (mut session, mut turn_context) = make_session_and_context().await; + turn_context + .approval_policy + .set(AskForApproval::OnRequest) + .expect("test setup should allow updating approval policy"); + let mut config = (*turn_context.config).clone(); + config.model_provider.base_url = Some(format!("{}/v1", server.uri())); + config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent; + let config = Arc::new(config); + let models_manager = Arc::new(crate::test_support::models_manager_with_provider( + config.codex_home.clone(), + Arc::clone(&session.services.auth_manager), + config.model_provider.clone(), + )); + session.services.models_manager = models_manager; + turn_context.config = Arc::clone(&config); + turn_context.provider = config.model_provider.clone(); + + let session = Arc::new(session); + let turn_context = Arc::new(turn_context); + let invocation = McpInvocation { + server: "custom_server".to_string(), + tool: "read_only_tool".to_string(), + arguments: None, + }; + let metadata = McpToolApprovalMetadata { + annotations: Some(annotations( + Some(true), + /*destructive*/ None, + /*open_world*/ None, + )), + connector_id: None, + connector_name: None, + connector_description: None, + tool_title: Some("Read Only Tool".to_string()), + tool_description: None, + codex_apps_meta: None, + }; + + let decision = maybe_request_mcp_tool_approval( + &session, + &turn_context, + "call-guardian", + &invocation, + Some(&metadata), + AppToolApproval::Auto, + ) + .await; + + assert_eq!(decision, None); +} + +#[tokio::test] +async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval() { + let (session, turn_context, _rx_event) = make_session_and_context_with_rx().await; + { + let mut active_turn = session.active_turn.lock().await; + *active_turn = Some(ActiveTurn::default()); + } + let invocation = McpInvocation { + server: "custom_server".to_string(), + tool: "read_only_tool".to_string(), + arguments: None, + }; + let metadata = McpToolApprovalMetadata { + annotations: Some(annotations( + Some(true), + /*destructive*/ None, + /*open_world*/ None, + )), + connector_id: None, + connector_name: None, + connector_description: None, + tool_title: Some("Read Only Tool".to_string()), + tool_description: None, + codex_apps_meta: None, + }; + + let mut approval_task = { + let session = Arc::clone(&session); + let turn_context = Arc::clone(&turn_context); + tokio::spawn(async move { + maybe_request_mcp_tool_approval( + &session, + &turn_context, + "call-prompt", + &invocation, + Some(&metadata), + AppToolApproval::Prompt, + ) + .await + }) + }; + + assert!( + tokio::time::timeout(std::time::Duration::from_millis(200), &mut approval_task) + .await + .is_err(), + "prompt mode should wait for approval instead of auto-allowing" + ); + approval_task.abort(); +} + #[tokio::test] async fn approve_mode_blocks_when_arc_returns_interrupt_for_model() { use wiremock::Mock; diff --git a/codex-rs/core/src/memories/phase2.rs b/codex-rs/core/src/memories/phase2.rs index 321b314abb..78f762ed0e 100644 --- a/codex-rs/core/src/memories/phase2.rs +++ b/codex-rs/core/src/memories/phase2.rs @@ -132,7 +132,7 @@ pub(super) async fn run(session: &Arc, config: Arc) { let thread_id = match session .services .agent_control - .spawn_agent(agent_config, prompt, Some(source)) + .spawn_agent(agent_config, prompt.into(), Some(source)) .await { Ok(thread_id) => thread_id, diff --git a/codex-rs/core/src/memories/storage_tests.rs b/codex-rs/core/src/memories/storage_tests.rs index 5e0f2ce89c..fe28219766 100644 --- a/codex-rs/core/src/memories/storage_tests.rs +++ b/codex-rs/core/src/memories/storage_tests.rs @@ -29,7 +29,7 @@ fn fixed_thread_id() -> ThreadId { #[test] fn rollout_summary_file_stem_uses_uuid_timestamp_and_hash_when_slug_missing() { let thread_id = fixed_thread_id(); - let memory = stage1_output_with_slug(thread_id, None); + let memory = stage1_output_with_slug(thread_id, /*rollout_slug*/ None); assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX); assert_eq!( diff --git a/codex-rs/core/src/memories/tests.rs b/codex-rs/core/src/memories/tests.rs index 03dbe71851..8eae9db861 100644 --- a/codex-rs/core/src/memories/tests.rs +++ b/codex-rs/core/src/memories/tests.rs @@ -526,8 +526,8 @@ mod phase2 { thread_id, self.session.conversation_id, source_updated_at, - 3_600, - 64, + /*lease_seconds*/ 3_600, + /*max_running_jobs*/ 64, ) .await .expect("claim stage-1 job"); @@ -543,7 +543,7 @@ mod phase2 { source_updated_at, "raw memory", "rollout summary", - None, + /*rollout_slug*/ None, ) .await .expect("mark stage-1 success"), @@ -571,24 +571,24 @@ mod phase2 { #[test] fn completion_watermark_never_regresses_below_claimed_input_watermark() { - let stage1_output = stage1_output_with_source_updated_at(123); + let stage1_output = stage1_output_with_source_updated_at(/*source_updated_at*/ 123); - let completion = phase2::get_watermark(1_000, &[stage1_output]); + let completion = phase2::get_watermark(/*claimed_watermark*/ 1_000, &[stage1_output]); pretty_assertions::assert_eq!(completion, 1_000); } #[test] fn completion_watermark_uses_claimed_watermark_when_there_are_no_memories() { - let completion = phase2::get_watermark(777, &[]); + let completion = phase2::get_watermark(/*claimed_watermark*/ 777, &[]); pretty_assertions::assert_eq!(completion, 777); } #[test] fn completion_watermark_uses_latest_memory_timestamp_when_it_is_newer() { - let older = stage1_output_with_source_updated_at(123); - let newer = stage1_output_with_source_updated_at(456); + let older = stage1_output_with_source_updated_at(/*source_updated_at*/ 123); + let newer = stage1_output_with_source_updated_at(/*source_updated_at*/ 456); - let completion = phase2::get_watermark(200, &[older, newer]); + let completion = phase2::get_watermark(/*claimed_watermark*/ 200, &[older, newer]); pretty_assertions::assert_eq!(completion, 456); } @@ -608,12 +608,12 @@ mod phase2 { let harness = DispatchHarness::new().await; harness .state_db - .enqueue_global_consolidation(123) + .enqueue_global_consolidation(/*input_watermark*/ 123) .await .expect("enqueue global consolidation"); let claimed = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim running global lock"); assert!( @@ -625,7 +625,7 @@ mod phase2 { let running_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim while lock is still running"); pretty_assertions::assert_eq!(running_claim, Phase2JobClaimOutcome::SkippedRunning); @@ -641,7 +641,7 @@ mod phase2 { let stale_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 0) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 0) .await .expect("claim stale global lock"); assert!( @@ -653,7 +653,7 @@ mod phase2 { let post_dispatch_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim after stale lock dispatch"); assert!( @@ -759,7 +759,7 @@ mod phase2 { harness .state_db - .enqueue_global_consolidation(999) + .enqueue_global_consolidation(/*input_watermark*/ 999) .await .expect("enqueue global consolidation"); @@ -801,7 +801,7 @@ mod phase2 { ); let next_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim global job after empty consolidation success"); pretty_assertions::assert_eq!(next_claim, Phase2JobClaimOutcome::SkippedNotDirty); @@ -817,7 +817,7 @@ mod phase2 { let harness = DispatchHarness::new().await; harness .state_db - .enqueue_global_consolidation(99) + .enqueue_global_consolidation(/*input_watermark*/ 99) .await .expect("enqueue global consolidation"); let mut constrained_config = harness.config.as_ref().clone(); @@ -828,7 +828,7 @@ mod phase2 { let retry_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim global job after sandbox policy failure"); pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty); @@ -840,7 +840,7 @@ mod phase2 { #[tokio::test] async fn dispatch_marks_job_for_retry_when_syncing_artifacts_fails() { let harness = DispatchHarness::new().await; - harness.seed_stage1_output(100).await; + harness.seed_stage1_output(/*source_updated_at*/ 100).await; let root = memory_root(&harness.config.codex_home); tokio::fs::write(&root, "not a directory") .await @@ -850,7 +850,7 @@ mod phase2 { let retry_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim global job after sync failure"); pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty); @@ -862,7 +862,7 @@ mod phase2 { #[tokio::test] async fn dispatch_marks_job_for_retry_when_rebuilding_raw_memories_fails() { let harness = DispatchHarness::new().await; - harness.seed_stage1_output(100).await; + harness.seed_stage1_output(/*source_updated_at*/ 100).await; let root = memory_root(&harness.config.codex_home); tokio::fs::create_dir_all(raw_memories_file(&root)) .await @@ -872,7 +872,7 @@ mod phase2 { let retry_claim = harness .state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim global job after rebuild failure"); pretty_assertions::assert_eq!(retry_claim, Phase2JobClaimOutcome::SkippedNotDirty); @@ -917,7 +917,13 @@ mod phase2 { .expect("upsert thread metadata"); let claim = state_db - .try_claim_stage1_job(thread_id, session.conversation_id, 100, 3_600, 64) + .try_claim_stage1_job( + thread_id, + session.conversation_id, + /*source_updated_at*/ 100, + /*lease_seconds*/ 3_600, + /*max_running_jobs*/ 64, + ) .await .expect("claim stage-1 job"); let ownership_token = match claim { @@ -929,10 +935,10 @@ mod phase2 { .mark_stage1_job_succeeded( thread_id, &ownership_token, - 100, + /*source_updated_at*/ 100, "raw memory", "rollout summary", - None, + /*rollout_slug*/ None, ) .await .expect("mark stage-1 success"), @@ -942,7 +948,7 @@ mod phase2 { phase2::run(&session, Arc::clone(&config)).await; let retry_claim = state_db - .try_claim_global_phase2_job(ThreadId::new(), 3_600) + .try_claim_global_phase2_job(ThreadId::new(), /*lease_seconds*/ 3_600) .await .expect("claim global job after spawn failure"); pretty_assertions::assert_eq!( diff --git a/codex-rs/core/src/message_history_tests.rs b/codex-rs/core/src/message_history_tests.rs index 59b9c8c7b7..de89a3eb9c 100644 --- a/codex-rs/core/src/message_history_tests.rs +++ b/codex-rs/core/src/message_history_tests.rs @@ -37,8 +37,8 @@ async fn lookup_reads_history_entries() { let (log_id, count) = history_metadata_for_file(&history_path).await; assert_eq!(count, entries.len()); - let second_entry = - lookup_history_entry(&history_path, log_id, 1).expect("fetch second history entry"); + let second_entry = lookup_history_entry(&history_path, log_id, /*offset*/ 1) + .expect("fetch second history entry"); assert_eq!(second_entry, entries[1]); } @@ -80,8 +80,8 @@ async fn lookup_uses_stable_log_id_after_appends() { ) .expect("append history entry"); - let fetched = - lookup_history_entry(&history_path, log_id, 1).expect("lookup appended history entry"); + let fetched = lookup_history_entry(&history_path, log_id, /*offset*/ 1) + .expect("lookup appended history entry"); assert_eq!(fetched, appended); } diff --git a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs index dceab9f3bd..afb002bce8 100644 --- a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs +++ b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs @@ -2,13 +2,19 @@ use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES; use codex_protocol::openai_models::ReasoningEffort; +use codex_utils_template::Template; +use std::sync::LazyLock; const COLLABORATION_MODE_PLAN: &str = include_str!("../../templates/collaboration_mode/plan.md"); const COLLABORATION_MODE_DEFAULT: &str = include_str!("../../templates/collaboration_mode/default.md"); -const KNOWN_MODE_NAMES_PLACEHOLDER: &str = "{{KNOWN_MODE_NAMES}}"; -const REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER: &str = "{{REQUEST_USER_INPUT_AVAILABILITY}}"; -const ASKING_QUESTIONS_GUIDANCE_PLACEHOLDER: &str = "{{ASKING_QUESTIONS_GUIDANCE}}"; +const KNOWN_MODE_NAMES_TEMPLATE_KEY: &str = "KNOWN_MODE_NAMES"; +const REQUEST_USER_INPUT_AVAILABILITY_TEMPLATE_KEY: &str = "REQUEST_USER_INPUT_AVAILABILITY"; +const ASKING_QUESTIONS_GUIDANCE_TEMPLATE_KEY: &str = "ASKING_QUESTIONS_GUIDANCE"; +static COLLABORATION_MODE_DEFAULT_TEMPLATE: LazyLock