mirror of
https://github.com/openai/codex.git
synced 2026-04-04 04:44:47 +00:00
Compare commits
2 Commits
main
...
codex-appl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37ed4ddc98 | ||
|
|
2e009ba30a |
1
.bazelrc
1
.bazelrc
@@ -124,6 +124,7 @@ build:argument-comment-lint --@rules_rust//rust/toolchain/channel=nightly
|
||||
common:ci-windows --config=ci-bazel
|
||||
common:ci-windows --build_metadata=TAG_os=windows
|
||||
common:ci-windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache
|
||||
common:ci-windows --repository_cache=D:/a/.cache/bazel-repo-cache
|
||||
|
||||
# We prefer to run the build actions entirely remotely so we can dial up the concurrency.
|
||||
# We have platform-specific tests, so we want to execute the tests on all platforms using the strongest sandboxing available on each platform.
|
||||
|
||||
31
.github/actions/setup-bazel-ci/action.yml
vendored
31
.github/actions/setup-bazel-ci/action.yml
vendored
@@ -9,9 +9,9 @@ inputs:
|
||||
required: false
|
||||
default: "false"
|
||||
outputs:
|
||||
repository-cache-path:
|
||||
description: Filesystem path used for the Bazel repository cache.
|
||||
value: ${{ steps.configure_bazel_repository_cache.outputs.repository-cache-path }}
|
||||
cache-hit:
|
||||
description: Whether the Bazel repository cache key was restored exactly.
|
||||
value: ${{ steps.cache_bazel_repository_restore.outputs.cache-hit }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -41,16 +41,17 @@ runs:
|
||||
- name: Set up Bazel
|
||||
uses: bazelbuild/setup-bazelisk@v3
|
||||
|
||||
- name: Configure Bazel repository cache
|
||||
id: configure_bazel_repository_cache
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Keep the repository cache under HOME on all runners. Windows `D:\a`
|
||||
# cache paths match `.bazelrc`, but `actions/cache/restore` currently
|
||||
# returns HTTP 400 for that path in the Windows clippy job.
|
||||
$repositoryCachePath = Join-Path $HOME '.cache/bazel-repo-cache'
|
||||
"repository-cache-path=$repositoryCachePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
|
||||
"BAZEL_REPOSITORY_CACHE=$repositoryCachePath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
# 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'
|
||||
@@ -64,6 +65,10 @@ runs:
|
||||
$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'
|
||||
|
||||
30
.github/scripts/run-bazel-ci.sh
vendored
30
.github/scripts/run-bazel-ci.sh
vendored
@@ -5,7 +5,6 @@ set -euo pipefail
|
||||
print_failed_bazel_test_logs=0
|
||||
use_node_test_env=0
|
||||
remote_download_toplevel=0
|
||||
windows_msvc_host_platform=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -21,10 +20,6 @@ while [[ $# -gt 0 ]]; do
|
||||
remote_download_toplevel=1
|
||||
shift
|
||||
;;
|
||||
--windows-msvc-host-platform)
|
||||
windows_msvc_host_platform=1
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
@@ -37,7 +32,7 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] [--remote-download-toplevel] [--windows-msvc-host-platform] -- <bazel args> -- <targets>" >&2
|
||||
echo "Usage: $0 [--print-failed-test-logs] [--use-node-test-env] [--remote-download-toplevel] -- <bazel args> -- <targets>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -126,35 +121,14 @@ if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $use_node_test_env -eq 1 ]]; then
|
||||
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)"
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
node_bin="$(cygpath -w "${node_bin}")"
|
||||
fi
|
||||
bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}")
|
||||
fi
|
||||
|
||||
post_config_bazel_args=()
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" && $windows_msvc_host_platform -eq 1 ]]; then
|
||||
has_host_platform_override=0
|
||||
for arg in "${bazel_args[@]}"; do
|
||||
if [[ "$arg" == --host_platform=* ]]; then
|
||||
has_host_platform_override=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $has_host_platform_override -eq 0 ]]; then
|
||||
# Keep Windows Bazel targets on `windows-gnullvm` for cfg coverage, but opt
|
||||
# specific jobs into an MSVC exec platform when they need helper binaries
|
||||
# like Rust test wrappers and V8 generators to resolve a compatible host
|
||||
# toolchain.
|
||||
post_config_bazel_args+=("--host_platform=//:local_windows_msvc")
|
||||
fi
|
||||
fi
|
||||
|
||||
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.
|
||||
|
||||
6
.github/workflows/README.md
vendored
6
.github/workflows/README.md
vendored
@@ -5,15 +5,15 @@ The workflows in this directory are split so that pull requests get fast, review
|
||||
## 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,
|
||||
including the generated Rust test binaries needed to lint inline `#[cfg(test)]`
|
||||
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`.
|
||||
|
||||
78
.github/workflows/bazel.yml
vendored
78
.github/workflows/bazel.yml
vendored
@@ -58,20 +58,6 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
install-test-prereqs: "true"
|
||||
|
||||
# Restore the Bazel repository cache explicitly so external dependencies
|
||||
# do not need to be re-downloaded on every CI run. Keep restore failures
|
||||
# non-fatal so transient cache-service errors degrade to a cold build
|
||||
# instead of failing the job.
|
||||
- name: Restore bazel repository cache
|
||||
id: cache_bazel_repository_restore
|
||||
continue-on-error: true
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ steps.setup_bazel.outputs.repository-cache-path }}
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
bazel-cache-${{ matrix.target }}
|
||||
|
||||
- name: Check MODULE.bazel.lock is up to date
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
shell: bash
|
||||
@@ -96,16 +82,9 @@ jobs:
|
||||
-//third_party/v8:all
|
||||
)
|
||||
|
||||
bazel_wrapper_args=(
|
||||
--print-failed-test-logs
|
||||
--use-node-test-env
|
||||
)
|
||||
if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
bazel_wrapper_args+=(--windows-msvc-host-platform)
|
||||
fi
|
||||
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
"${bazel_wrapper_args[@]}" \
|
||||
--print-failed-test-logs \
|
||||
--use-node-test-env \
|
||||
-- \
|
||||
test \
|
||||
--test_tag_filters=-argument-comment-lint \
|
||||
@@ -126,11 +105,12 @@ jobs:
|
||||
# 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.setup_bazel.outputs.repository-cache-path }}
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
|
||||
clippy:
|
||||
@@ -161,55 +141,28 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
# Restore the Bazel repository cache explicitly so external dependencies
|
||||
# do not need to be re-downloaded on every CI run. Keep restore failures
|
||||
# non-fatal so transient cache-service errors degrade to a cold build
|
||||
# instead of failing the job.
|
||||
- name: Restore bazel repository cache
|
||||
id: cache_bazel_repository_restore
|
||||
continue-on-error: true
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ steps.setup_bazel.outputs.repository-cache-path }}
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
bazel-cache-${{ matrix.target }}
|
||||
|
||||
- name: Set up Bazel execution logs
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/bazel-execution-logs"
|
||||
echo "CODEX_BAZEL_EXECUTION_LOG_COMPACT_DIR=${RUNNER_TEMP}/bazel-execution-logs" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: bazel build --config=clippy lint targets
|
||||
- name: bazel build --config=clippy //codex-rs/...
|
||||
env:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
bazel_clippy_args=(
|
||||
--config=clippy
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
|
||||
--build_metadata=TAG_job=clippy
|
||||
)
|
||||
if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
# Some explicit targets pulled in through //codex-rs/... are
|
||||
# intentionally incompatible with `//:local_windows`, but the lint
|
||||
# aspect still traverses their compatible Rust deps.
|
||||
bazel_clippy_args+=(--skip_incompatible_explicit_targets)
|
||||
fi
|
||||
|
||||
bazel_target_lines="$(./scripts/list-bazel-clippy-targets.sh)"
|
||||
bazel_targets=()
|
||||
while IFS= read -r target; do
|
||||
bazel_targets+=("${target}")
|
||||
done <<< "${bazel_target_lines}"
|
||||
|
||||
# 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 \
|
||||
"${bazel_clippy_args[@]}" \
|
||||
--config=clippy \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
--build_metadata=TAG_job=clippy \
|
||||
-- \
|
||||
"${bazel_targets[@]}"
|
||||
//codex-rs/... \
|
||||
-//codex-rs/v8-poc:all
|
||||
|
||||
- name: Upload Bazel execution logs
|
||||
if: always() && !cancelled()
|
||||
@@ -223,9 +176,10 @@ jobs:
|
||||
# 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ${{ steps.setup_bazel.outputs.repository-cache-path }}
|
||||
path: |
|
||||
~/.cache/bazel-repo-cache
|
||||
key: bazel-cache-${{ matrix.target }}-${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }}
|
||||
|
||||
7
.github/workflows/rust-release.yml
vendored
7
.github/workflows/rust-release.yml
vendored
@@ -584,11 +584,14 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
# Node 24 bundles npm >= 11.5.1, which trusted publishing requires.
|
||||
node-version: 24
|
||||
node-version: 22
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
scope: "@openai"
|
||||
|
||||
# Trusted publishing requires npm CLI version 11.5.1 or later.
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Download npm tarballs from release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
31
MODULE.bazel
31
MODULE.bazel
@@ -72,7 +72,6 @@ single_version_override(
|
||||
patches = [
|
||||
"//patches:rules_rs_windows_gnullvm_exec.patch",
|
||||
"//patches:rules_rs_delete_git_worktree_pointer.patch",
|
||||
"//patches:rules_rs_windows_exec_linker.patch",
|
||||
],
|
||||
version = "0.0.43",
|
||||
)
|
||||
@@ -86,9 +85,7 @@ rules_rust.patch(
|
||||
"//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_build_script_runner_paths.patch",
|
||||
"//patches:rules_rust_windows_msvc_direct_link_args.patch",
|
||||
"//patches:rules_rust_windows_process_wrapper_skip_temp_outputs.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",
|
||||
@@ -192,18 +189,8 @@ bazel_dep(name = "zstd", version = "1.5.7")
|
||||
|
||||
crate.annotation(
|
||||
crate = "zstd-sys",
|
||||
gen_build_script = "on",
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
"//patches:zstd-sys_windows_msvc_include_dirs.patch",
|
||||
],
|
||||
)
|
||||
crate.annotation(
|
||||
crate = "ring",
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
"//patches:ring_windows_msvc_include_dirs.patch",
|
||||
],
|
||||
gen_build_script = "off",
|
||||
deps = ["@zstd"],
|
||||
)
|
||||
crate.annotation(
|
||||
build_script_env = {
|
||||
@@ -231,7 +218,8 @@ bazel_dep(name = "bzip2", version = "1.0.8.bcr.3")
|
||||
|
||||
crate.annotation(
|
||||
crate = "bzip2-sys",
|
||||
gen_build_script = "on",
|
||||
gen_build_script = "off",
|
||||
deps = ["@bzip2//:bz2"],
|
||||
)
|
||||
|
||||
inject_repo(crate, "bzip2")
|
||||
@@ -240,19 +228,24 @@ bazel_dep(name = "zlib", version = "1.3.1.bcr.8")
|
||||
|
||||
crate.annotation(
|
||||
crate = "libz-sys",
|
||||
gen_build_script = "on",
|
||||
gen_build_script = "off",
|
||||
deps = ["@zlib"],
|
||||
)
|
||||
|
||||
inject_repo(crate, "zlib")
|
||||
|
||||
# TODO(zbarsky): Enable annotation after fixing windows arm64 builds.
|
||||
bazel_dep(name = "xz", version = "5.4.5.bcr.8")
|
||||
|
||||
crate.annotation(
|
||||
crate = "lzma-sys",
|
||||
gen_build_script = "on",
|
||||
gen_build_script = "off",
|
||||
deps = ["@xz//:lzma"],
|
||||
)
|
||||
|
||||
bazel_dep(name = "openssl", version = "3.5.4.bcr.0")
|
||||
|
||||
inject_repo(crate, "xz")
|
||||
|
||||
crate.annotation(
|
||||
build_script_data = [
|
||||
"@openssl//:gen_dir",
|
||||
|
||||
6
MODULE.bazel.lock
generated
6
MODULE.bazel.lock
generated
@@ -228,6 +228,8 @@
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/with_cfg.bzl/0.12.0/MODULE.bazel": "b573395fe63aef4299ba095173e2f62ccfee5ad9bbf7acaa95dba73af9fc2b38",
|
||||
"https://bcr.bazel.build/modules/with_cfg.bzl/0.12.0/source.json": "3f3fbaeafecaf629877ad152a2c9def21f8d330d91aa94c5dc75bbb98c10b8b8",
|
||||
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.8/MODULE.bazel": "e48a69bd54053c2ec5fffc2a29fb70122afd3e83ab6c07068f63bc6553fa57cc",
|
||||
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.8/source.json": "bd7e928ccd63505b44f4784f7bbf12cc11f9ff23bf3ca12ff2c91cd74846099e",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.8/MODULE.bazel": "772c674bb78a0342b8caf32ab5c25085c493ca4ff08398208dcbe4375fe9f776",
|
||||
@@ -923,7 +925,7 @@
|
||||
"hyper-rustls_0.27.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"hyper\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"server-auto\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.4\"},{\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rustls\",\"req\":\"^0.23\"},{\"default_features\":false,\"features\":[\"tls12\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"rustls-platform-verifier\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"io-std\",\"macros\",\"net\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"aws-lc-rs\":[\"rustls/aws_lc_rs\"],\"default\":[\"native-tokio\",\"http1\",\"tls12\",\"logging\",\"aws-lc-rs\"],\"fips\":[\"aws-lc-rs\",\"rustls/fips\"],\"http1\":[\"hyper-util/http1\"],\"http2\":[\"hyper-util/http2\"],\"logging\":[\"log\",\"tokio-rustls/logging\",\"rustls/logging\"],\"native-tokio\":[\"rustls-native-certs\"],\"ring\":[\"rustls/ring\"],\"tls12\":[\"tokio-rustls/tls12\",\"rustls/tls12\"],\"webpki-tokio\":[\"webpki-roots\"]}}",
|
||||
"hyper-timeout_0.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"hyper\",\"req\":\"^1.1\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"hyper-tls\",\"req\":\"^0.6\"},{\"features\":[\"client-legacy\",\"http1\"],\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"features\":[\"client-legacy\",\"http1\",\"server\",\"server-graceful\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"name\":\"tokio\",\"req\":\"^1.35\"},{\"features\":[\"io-std\",\"io-util\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.35\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{}}",
|
||||
"hyper-tls_0.6.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"name\":\"native-tls\",\"req\":\"^0.2.1\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io-std\",\"macros\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"name\":\"tokio-native-tls\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{\"alpn\":[\"native-tls/alpn\"],\"vendored\":[\"native-tls/vendored\"]}}",
|
||||
"hyper-util_0.1.20": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.8.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-layer\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\">=0.3, <0.7\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-pool\":[\"client\",\"dep:futures-util\",\"dep:tower-layer\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-pool\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}",
|
||||
"hyper-util_0.1.19": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.8.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\">=0.5, <0.7\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-layer\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\">=0.3, <0.7\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-pool\":[\"client\",\"dep:futures-util\",\"dep:tower-layer\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-pool\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}",
|
||||
"hyper_1.8.1": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"sink\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"net\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7.10\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"want\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"capi\":[],\"client\":[\"dep:want\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"default\":[],\"ffi\":[\"dep:http-body-util\",\"dep:futures-util\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\"],\"http1\":[\"dep:atomic-waker\",\"dep:futures-channel\",\"dep:futures-core\",\"dep:httparse\",\"dep:itoa\",\"dep:pin-utils\"],\"http2\":[\"dep:futures-channel\",\"dep:futures-core\",\"dep:h2\"],\"nightly\":[],\"server\":[\"dep:httpdate\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"tracing\":[\"dep:tracing\"]}}",
|
||||
"i18n-config_0.4.8": "{\"dependencies\":[{\"name\":\"basic-toml\",\"req\":\"^0.1\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"serde\"],\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}",
|
||||
"i18n-embed-fl_0.9.4": "{\"dependencies\":[{\"name\":\"dashmap\",\"optional\":true,\"req\":\"^6.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"find-crate\",\"req\":\"^0.6\"},{\"name\":\"fluent\",\"req\":\"^0.16\"},{\"name\":\"fluent-syntax\",\"req\":\"^0.11\"},{\"name\":\"i18n-config\",\"req\":\"^0.4.7\"},{\"features\":[\"fluent-system\",\"filesystem-assets\"],\"name\":\"i18n-embed\",\"req\":\"^0.15.4\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"name\":\"proc-macro-error2\",\"req\":\"^2.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rust-embed\",\"req\":\"^8.0\"},{\"name\":\"strsim\",\"req\":\"^0.11\"},{\"features\":[\"derive\",\"proc-macro\",\"parsing\",\"printing\",\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{\"dashmap\":[\"dep:dashmap\"]}}",
|
||||
@@ -1367,7 +1369,7 @@
|
||||
"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\"]}}",
|
||||
"sys-locale_0.3.2": "{\"dependencies\":[{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"features\":[\"Window\",\"WorkerGlobalScope\",\"Navigator\",\"WorkerNavigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"}],\"features\":{\"js\":[\"js-sys\",\"wasm-bindgen\",\"web-sys\"]}}",
|
||||
"system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}",
|
||||
"system-configuration_0.7.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"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.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\":[]}}",
|
||||
|
||||
137
codex-rs/Cargo.lock
generated
137
codex-rs/Cargo.lock
generated
@@ -411,7 +411,6 @@ dependencies = [
|
||||
"codex-core",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-models-manager",
|
||||
"codex-protocol",
|
||||
"codex-utils-cargo-bin",
|
||||
"core_test_support",
|
||||
@@ -448,7 +447,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
@@ -1368,9 +1367,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"codex-client",
|
||||
"codex-protocol",
|
||||
"codex-utils-rustls-provider",
|
||||
@@ -1419,7 +1416,6 @@ dependencies = [
|
||||
"codex-git-utils",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-models-manager",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-rmcp-client",
|
||||
@@ -1791,10 +1787,6 @@ dependencies = [
|
||||
"v8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-collaboration-mode-templates"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "codex-config"
|
||||
version = "0.0.0"
|
||||
@@ -1845,6 +1837,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bm25",
|
||||
"chardetng",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-analytics",
|
||||
@@ -1860,19 +1853,15 @@ dependencies = [
|
||||
"codex-exec-server",
|
||||
"codex-execpolicy",
|
||||
"codex-features",
|
||||
"codex-feedback",
|
||||
"codex-git-utils",
|
||||
"codex-hooks",
|
||||
"codex-instructions",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-network-proxy",
|
||||
"codex-otel",
|
||||
"codex-plugin",
|
||||
"codex-protocol",
|
||||
"codex-response-debug-context",
|
||||
"codex-rmcp-client",
|
||||
"codex-rollout",
|
||||
"codex-sandboxing",
|
||||
@@ -1902,6 +1891,7 @@ dependencies = [
|
||||
"ctor 0.6.3",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"encoding_rs",
|
||||
"env-flags",
|
||||
"eventsource-stream",
|
||||
"futures",
|
||||
@@ -1910,6 +1900,7 @@ dependencies = [
|
||||
"image",
|
||||
"indexmap 2.13.0",
|
||||
"insta",
|
||||
"landlock",
|
||||
"libc",
|
||||
"maplit",
|
||||
"notify",
|
||||
@@ -1924,6 +1915,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"rmcp",
|
||||
"schemars 0.8.22",
|
||||
"seccompiler",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
@@ -2010,7 +2002,6 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-git-utils",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -2128,7 +2119,6 @@ name = "codex-feedback"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"codex-login",
|
||||
"codex-protocol",
|
||||
"pretty_assertions",
|
||||
"sentry",
|
||||
@@ -2234,7 +2224,6 @@ name = "codex-lmstudio"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-model-provider-info",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2251,13 +2240,10 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-api",
|
||||
"codex-app-server-protocol",
|
||||
"codex-client",
|
||||
"codex-config",
|
||||
"codex-keyring-store",
|
||||
"codex-model-provider-info",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-terminal-detection",
|
||||
"codex-utils-template",
|
||||
@@ -2325,7 +2311,6 @@ dependencies = [
|
||||
"codex-exec-server",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-models-manager",
|
||||
"codex-protocol",
|
||||
"codex-shell-command",
|
||||
"codex-utils-cli",
|
||||
@@ -2346,53 +2331,6 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-model-provider-info"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-api",
|
||||
"codex-app-server-protocol",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"http 1.4.0",
|
||||
"maplit",
|
||||
"pretty_assertions",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-models-manager"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-api",
|
||||
"codex-app-server-protocol",
|
||||
"codex-collaboration-mode-templates",
|
||||
"codex-feedback",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-response-debug-context",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-output-truncation",
|
||||
"codex-utils-template",
|
||||
"core_test_support",
|
||||
"http 1.4.0",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-network-proxy"
|
||||
version = "0.0.0"
|
||||
@@ -2432,7 +2370,6 @@ dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"codex-core",
|
||||
"codex-model-provider-info",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
@@ -2497,27 +2434,18 @@ name = "codex-protocol"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chardetng",
|
||||
"chrono",
|
||||
"codex-async-utils",
|
||||
"codex-execpolicy",
|
||||
"codex-git-utils",
|
||||
"codex-network-proxy",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-image",
|
||||
"codex-utils-string",
|
||||
"codex-utils-template",
|
||||
"encoding_rs",
|
||||
"http 1.4.0",
|
||||
"icu_decimal",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"landlock",
|
||||
"pretty_assertions",
|
||||
"quick-xml",
|
||||
"reqwest",
|
||||
"schemars 0.8.22",
|
||||
"seccompiler",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
@@ -2525,24 +2453,11 @@ dependencies = [
|
||||
"strum_macros 0.28.0",
|
||||
"sys-locale",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-response-debug-context"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"codex-api",
|
||||
"http 1.4.0",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-responses-api-proxy"
|
||||
version = "0.0.0"
|
||||
@@ -2552,7 +2467,6 @@ dependencies = [
|
||||
"codex-process-hardening",
|
||||
"ctor 0.6.3",
|
||||
"libc",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2791,8 +2705,6 @@ dependencies = [
|
||||
"codex-git-utils",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-rollout",
|
||||
@@ -2956,7 +2868,6 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-lmstudio",
|
||||
"codex-model-provider-info",
|
||||
"codex-ollama",
|
||||
]
|
||||
|
||||
@@ -3028,7 +2939,6 @@ name = "codex-utils-sandbox-summary"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
"codex-model-provider-info",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"pretty_assertions",
|
||||
@@ -3300,8 +3210,6 @@ dependencies = [
|
||||
"codex-exec-server",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-cargo-bin",
|
||||
@@ -3956,7 +3864,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4201,7 +4109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5107,13 +5015,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http-body",
|
||||
@@ -5122,7 +5031,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@@ -5628,7 +5537,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6406,7 +6315,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6582,7 +6491,7 @@ version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"getrandom 0.2.17",
|
||||
"http 1.4.0",
|
||||
@@ -7050,7 +6959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7596,7 +7505,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -7633,9 +7542,9 @@ dependencies = [
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8471,7 +8380,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9870,9 +9779,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.7.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -9905,7 +9814,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11351,7 +11260,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -20,7 +20,6 @@ members = [
|
||||
"cloud-tasks-client",
|
||||
"cloud-tasks-mock-client",
|
||||
"cli",
|
||||
"collaboration-mode-templates",
|
||||
"connectors",
|
||||
"config",
|
||||
"shell-command",
|
||||
@@ -42,8 +41,6 @@ members = [
|
||||
"login",
|
||||
"codex-mcp",
|
||||
"mcp-server",
|
||||
"model-provider-info",
|
||||
"models-manager",
|
||||
"network-proxy",
|
||||
"ollama",
|
||||
"process-hardening",
|
||||
@@ -51,7 +48,6 @@ members = [
|
||||
"rollout",
|
||||
"rmcp-client",
|
||||
"responses-api-proxy",
|
||||
"response-debug-context",
|
||||
"sandboxing",
|
||||
"stdio-to-uds",
|
||||
"otel",
|
||||
@@ -116,7 +112,6 @@ codex-backend-client = { path = "backend-client" }
|
||||
codex-chatgpt = { path = "chatgpt" }
|
||||
codex-cli = { path = "cli" }
|
||||
codex-client = { path = "codex-client" }
|
||||
codex-collaboration-mode-templates = { path = "collaboration-mode-templates" }
|
||||
codex-cloud-requirements = { path = "cloud-requirements" }
|
||||
codex-cloud-tasks-client = { path = "cloud-tasks-client" }
|
||||
codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
|
||||
@@ -141,8 +136,6 @@ codex-lmstudio = { path = "lmstudio" }
|
||||
codex-login = { path = "login" }
|
||||
codex-mcp = { path = "codex-mcp" }
|
||||
codex-mcp-server = { path = "mcp-server" }
|
||||
codex-model-provider-info = { path = "model-provider-info" }
|
||||
codex-models-manager = { path = "models-manager" }
|
||||
codex-network-proxy = { path = "network-proxy" }
|
||||
codex-ollama = { path = "ollama" }
|
||||
codex-otel = { path = "otel" }
|
||||
@@ -150,7 +143,6 @@ codex-plugin = { path = "plugin" }
|
||||
codex-process-hardening = { path = "process-hardening" }
|
||||
codex-protocol = { path = "protocol" }
|
||||
codex-responses-api-proxy = { path = "responses-api-proxy" }
|
||||
codex-response-debug-context = { path = "response-debug-context" }
|
||||
codex-rmcp-client = { path = "rmcp-client" }
|
||||
codex-rollout = { path = "rollout" }
|
||||
codex-sandboxing = { path = "sandboxing" }
|
||||
|
||||
@@ -3,6 +3,5 @@ load("//:defs.bzl", "codex_rust_crate")
|
||||
codex_rust_crate(
|
||||
name = "app-server",
|
||||
crate_name = "codex_app_server",
|
||||
integration_test_timeout = "long",
|
||||
test_tags = ["no-sandbox"],
|
||||
)
|
||||
|
||||
@@ -46,7 +46,6 @@ codex-file-search = { workspace = true }
|
||||
codex-chatgpt = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-mcp = { workspace = true }
|
||||
codex-models-manager = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
|
||||
@@ -273,7 +273,7 @@ Experimental API: `thread/start`, `thread/resume`, and `thread/fork` accept `per
|
||||
- `modelProviders` — restrict results to specific providers; unset, null, or an empty array will include all providers.
|
||||
- `sourceKinds` — restrict results to specific sources; omit or pass `[]` for interactive sessions only (`cli`, `vscode`).
|
||||
- `archived` — when `true`, list archived threads only. When `false` or `null`, list non-archived threads (default).
|
||||
- `cwd` — restrict results to threads whose session cwd exactly matches this path. Relative paths are resolved against the app-server process cwd before matching.
|
||||
- `cwd` — restrict results to threads whose session cwd exactly matches this path.
|
||||
- `searchTerm` — restrict results to threads whose extracted title contains this substring (case-sensitive).
|
||||
- Responses include `agentNickname` and `agentRole` for AgentControl-spawned thread sub-agents when available.
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ use codex_app_server_protocol::AppsListParams;
|
||||
use codex_app_server_protocol::AppsListResponse;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_app_server_protocol::AuthMode as CoreAuthMode;
|
||||
use codex_app_server_protocol::CancelLoginAccountParams;
|
||||
use codex_app_server_protocol::CancelLoginAccountResponse;
|
||||
use codex_app_server_protocol::CancelLoginAccountStatus;
|
||||
@@ -203,6 +202,8 @@ use codex_core::config_loader::CloudRequirementsLoadErrorCode;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
use codex_core::config_loader::load_config_layers_state;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::error::Result as CodexResult;
|
||||
use codex_core::exec::ExecCapturePolicy;
|
||||
use codex_core::exec::ExecExpiration;
|
||||
use codex_core::exec::ExecParams;
|
||||
@@ -211,6 +212,7 @@ use codex_core::find_archived_thread_path_by_id_str;
|
||||
use codex_core::find_thread_name_by_id;
|
||||
use codex_core::find_thread_names_by_ids;
|
||||
use codex_core::find_thread_path_by_id_str;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::parse_cursor;
|
||||
use codex_core::plugins::MarketplaceError;
|
||||
use codex_core::plugins::MarketplacePluginSource;
|
||||
@@ -235,6 +237,7 @@ use codex_feedback::CodexFeedback;
|
||||
use codex_git_utils::git_diff_to_remote;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::AuthMode as CoreAuthMode;
|
||||
use codex_login::CLIENT_ID;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::ServerOptions as LoginServerOptions;
|
||||
@@ -248,9 +251,7 @@ use codex_login::run_login_server;
|
||||
use codex_mcp::mcp::auth::discover_supported_scopes;
|
||||
use codex_mcp::mcp::auth::resolve_oauth_scopes;
|
||||
use codex_mcp::mcp::collect_mcp_snapshot;
|
||||
use codex_mcp::mcp::effective_mcp_servers;
|
||||
use codex_mcp::mcp::qualified_mcp_tool_name_prefix;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_mcp::mcp::group_tools_by_server;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
@@ -258,8 +259,6 @@ use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec as CoreDynamicToolSpec;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
@@ -2215,28 +2214,14 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
};
|
||||
|
||||
// The user may have requested WorkspaceWrite or DangerFullAccess via
|
||||
// the command line, though in the process of deriving the Config, it
|
||||
// could be downgraded to ReadOnly (perhaps there is no sandbox
|
||||
// available on Windows or the enterprise config disallows it). The cwd
|
||||
// should still be considered "trusted" in this case.
|
||||
let requested_sandbox_trusts_project = matches!(
|
||||
typesafe_overrides.sandbox_mode,
|
||||
Some(
|
||||
codex_protocol::config_types::SandboxMode::WorkspaceWrite
|
||||
| codex_protocol::config_types::SandboxMode::DangerFullAccess
|
||||
)
|
||||
);
|
||||
|
||||
if requested_cwd.is_some()
|
||||
&& !config.active_project.is_trusted()
|
||||
&& (requested_sandbox_trusts_project
|
||||
|| matches!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
| codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. }
|
||||
))
|
||||
&& matches!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
| codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. }
|
||||
)
|
||||
{
|
||||
let trust_target = resolve_root_git_project_for_trust(config.cwd.as_path())
|
||||
.unwrap_or_else(|| config.cwd.to_path_buf());
|
||||
@@ -3364,13 +3349,6 @@ impl CodexMessageProcessor {
|
||||
cwd,
|
||||
search_term,
|
||||
} = params;
|
||||
let cwd = match normalize_thread_list_cwd_filter(cwd) {
|
||||
Ok(cwd) => cwd,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let requested_page_size = limit
|
||||
.map(|value| value as usize)
|
||||
@@ -3389,7 +3367,7 @@ impl CodexMessageProcessor {
|
||||
model_providers,
|
||||
source_kinds,
|
||||
archived: archived.unwrap_or(false),
|
||||
cwd,
|
||||
cwd: cwd.map(PathBuf::from),
|
||||
search_term,
|
||||
},
|
||||
)
|
||||
@@ -5156,56 +5134,12 @@ impl CodexMessageProcessor {
|
||||
)
|
||||
.await;
|
||||
|
||||
// Rebuild the tool list per original server name instead of using
|
||||
// `group_tools_by_server()`: qualified tool names are sanitized for the
|
||||
// Responses API, so a config key like `some-server` is encoded as the
|
||||
// `mcp__some_server__` prefix. Matching with the original server name's
|
||||
// sanitized prefix preserves `/mcp` output for hyphenated names.
|
||||
let effective_servers = effective_mcp_servers(&mcp_config, auth.as_ref());
|
||||
let mut sanitized_prefix_counts = HashMap::<String, usize>::new();
|
||||
for name in effective_servers.keys() {
|
||||
let prefix = qualified_mcp_tool_name_prefix(name);
|
||||
*sanitized_prefix_counts.entry(prefix).or_default() += 1;
|
||||
}
|
||||
let tools_by_server = effective_servers
|
||||
.keys()
|
||||
.map(|name| {
|
||||
let prefix = qualified_mcp_tool_name_prefix(name);
|
||||
// If multiple server names normalize to the same prefix, the
|
||||
// qualified tool namespace is ambiguous (for example
|
||||
// `some-server` and `some_server` both become
|
||||
// `mcp__some_server__`). In that case, avoid attributing the
|
||||
// same tools to multiple servers.
|
||||
let tools = if sanitized_prefix_counts
|
||||
.get(&prefix)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
== 1
|
||||
{
|
||||
snapshot
|
||||
.tools
|
||||
.iter()
|
||||
.filter_map(|(qualified_name, tool)| {
|
||||
qualified_name
|
||||
.strip_prefix(&prefix)
|
||||
.map(|tool_name| (tool_name.to_string(), tool.clone()))
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
(name.clone(), tools)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let tools_by_server = group_tools_by_server(&snapshot.tools);
|
||||
|
||||
let mut server_names: Vec<String> = config
|
||||
.mcp_servers
|
||||
.keys()
|
||||
.cloned()
|
||||
// Include built-in/plugin MCP servers that are present in the
|
||||
// effective runtime config even when they are not user-declared in
|
||||
// `config.mcp_servers`.
|
||||
.chain(effective_servers.keys().cloned())
|
||||
.chain(snapshot.auth_statuses.keys().cloned())
|
||||
.chain(snapshot.resources.keys().cloned())
|
||||
.chain(snapshot.resource_templates.keys().cloned())
|
||||
@@ -7728,57 +7662,6 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_thread_list_cwd_filter(
|
||||
cwd: Option<String>,
|
||||
) -> Result<Option<PathBuf>, JSONRPCErrorError> {
|
||||
let Some(cwd) = cwd else {
|
||||
return Ok(None);
|
||||
};
|
||||
AbsolutePathBuf::relative_to_current_dir(cwd.as_str())
|
||||
.map(AbsolutePathBuf::into_path_buf)
|
||||
.map(Some)
|
||||
.map_err(|err| JSONRPCErrorError {
|
||||
code: INVALID_PARAMS_ERROR_CODE,
|
||||
message: format!("invalid thread/list cwd filter `{cwd}`: {err}"),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod thread_list_cwd_filter_tests {
|
||||
use super::normalize_thread_list_cwd_filter;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn normalize_thread_list_cwd_filter_preserves_absolute_paths() {
|
||||
let cwd = if cfg!(windows) {
|
||||
String::from(r"C:\srv\repo-b")
|
||||
} else {
|
||||
String::from("/srv/repo-b")
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
normalize_thread_list_cwd_filter(Some(cwd.clone())).expect("cwd filter should parse"),
|
||||
Some(PathBuf::from(cwd))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_thread_list_cwd_filter_resolves_relative_paths_against_server_cwd()
|
||||
-> std::io::Result<()> {
|
||||
let expected = AbsolutePathBuf::relative_to_current_dir("repo-b")?.to_path_buf();
|
||||
|
||||
assert_eq!(
|
||||
normalize_thread_list_cwd_filter(Some(String::from("repo-b")))
|
||||
.expect("cwd filter should parse"),
|
||||
Some(expected)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_thread_listener_command(
|
||||
conversation_id: ThreadId,
|
||||
|
||||
@@ -18,12 +18,12 @@ use codex_app_server_protocol::CommandExecWriteParams;
|
||||
use codex_app_server_protocol::CommandExecWriteResponse;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_core::bytes_to_string_smart;
|
||||
use codex_core::config::StartedNetworkProxy;
|
||||
use codex_core::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS;
|
||||
use codex_core::exec::ExecExpiration;
|
||||
use codex_core::exec::IO_DRAIN_TIMEOUT_MS;
|
||||
use codex_core::sandboxing::ExecRequest;
|
||||
use codex_protocol::exec_output::bytes_to_string_smart;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP;
|
||||
use codex_utils_pty::ProcessHandle;
|
||||
|
||||
@@ -457,9 +457,7 @@ pub async fn run_main_with_transport(
|
||||
range: None,
|
||||
});
|
||||
}
|
||||
if let Some(warning) =
|
||||
codex_core::config::system_bwrap_warning(config.permissions.sandbox_policy.get())
|
||||
{
|
||||
if let Some(warning) = codex_core::config::system_bwrap_warning() {
|
||||
config_warnings.push(ConfigWarningNotification {
|
||||
summary: warning,
|
||||
details: None,
|
||||
|
||||
@@ -23,7 +23,6 @@ use async_trait::async_trait;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_analytics::AppServerRpcTransport;
|
||||
use codex_app_server_protocol::AppListUpdatedNotification;
|
||||
use codex_app_server_protocol::AuthMode as LoginAuthMode;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshParams;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshReason;
|
||||
use codex_app_server_protocol::ChatgptAuthTokensRefreshResponse;
|
||||
@@ -62,10 +61,12 @@ use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::config_loader::LoaderOverrides;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_features::Feature;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::AuthMode as LoginAuthMode;
|
||||
use codex_login::auth::ExternalAuth;
|
||||
use codex_login::auth::ExternalAuthRefreshContext;
|
||||
use codex_login::auth::ExternalAuthRefreshReason;
|
||||
@@ -75,7 +76,6 @@ use codex_login::default_client::USER_AGENT_SUFFIX;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_login::default_client::set_default_client_residency_requirement;
|
||||
use codex_login::default_client::set_default_originator;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
|
||||
@@ -4,7 +4,7 @@ use codex_app_server_protocol::Model;
|
||||
use codex_app_server_protocol::ModelUpgradeInfo;
|
||||
use codex_app_server_protocol::ReasoningEffortOption;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_core::models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ codex-app-server-protocol = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-models-manager = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -45,7 +45,6 @@ use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCRequest;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::ListMcpServerStatusParams;
|
||||
use codex_app_server_protocol::LoginAccountParams;
|
||||
use codex_app_server_protocol::MockExperimentalMethodParams;
|
||||
use codex_app_server_protocol::ModelListParams;
|
||||
@@ -527,15 +526,6 @@ impl McpProcess {
|
||||
self.send_request("plugin/read", params).await
|
||||
}
|
||||
|
||||
/// Send an `mcpServerStatus/list` JSON-RPC request.
|
||||
pub async fn send_list_mcp_server_status_request(
|
||||
&mut self,
|
||||
params: ListMcpServerStatusParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("mcpServerStatus/list", params).await
|
||||
}
|
||||
|
||||
/// Send a JSON-RPC request with raw params for protocol-level validation tests.
|
||||
pub async fn send_raw_request(
|
||||
&mut self,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_core::test_support::all_model_presets;
|
||||
use codex_models_manager::client_version_to_whole;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -85,7 +84,7 @@ pub fn write_models_cache_with_models(
|
||||
let cache_path = codex_home.join("models_cache.json");
|
||||
// DateTime<Utc> serializes to RFC3339 format by default with serde
|
||||
let fetched_at: DateTime<Utc> = Utc::now();
|
||||
let client_version = client_version_to_whole();
|
||||
let client_version = codex_core::models_manager::client_version_to_whole();
|
||||
let cache = json!({
|
||||
"fetched_at": fetched_at,
|
||||
"etag": null,
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::create_mock_responses_server_sequence_unchecked;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_mock_responses_config_toml;
|
||||
use axum::Router;
|
||||
use codex_app_server_protocol::ListMcpServerStatusParams;
|
||||
use codex_app_server_protocol::ListMcpServerStatusResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::handler::server::ServerHandler;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::ListToolsResult;
|
||||
use rmcp::model::ServerCapabilities;
|
||||
use rmcp::model::ServerInfo;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use rmcp::transport::StreamableHttpServerConfig;
|
||||
use rmcp::transport::StreamableHttpService;
|
||||
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_server_status_list_returns_tools_for_hyphenated_server_names() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let (mcp_server_url, mcp_server_handle) = start_mcp_server("lookup").await?;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_mock_responses_config_toml(
|
||||
codex_home.path(),
|
||||
&server.uri(),
|
||||
&BTreeMap::new(),
|
||||
/*auto_compact_limit*/ 1024,
|
||||
/*requires_openai_auth*/ None,
|
||||
"mock_provider",
|
||||
"compact",
|
||||
)?;
|
||||
|
||||
let config_path = codex_home.path().join("config.toml");
|
||||
let mut config_toml = std::fs::read_to_string(&config_path)?;
|
||||
config_toml.push_str(&format!(
|
||||
r#"
|
||||
[mcp_servers.some-server]
|
||||
url = "{mcp_server_url}/mcp"
|
||||
"#
|
||||
));
|
||||
std::fs::write(config_path, config_toml)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_list_mcp_server_status_request(ListMcpServerStatusParams {
|
||||
cursor: None,
|
||||
limit: None,
|
||||
})
|
||||
.await?;
|
||||
let response = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: ListMcpServerStatusResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(response.next_cursor, None);
|
||||
assert_eq!(response.data.len(), 1);
|
||||
let status = &response.data[0];
|
||||
assert_eq!(status.name, "some-server");
|
||||
assert_eq!(
|
||||
status.tools.keys().cloned().collect::<BTreeSet<_>>(),
|
||||
BTreeSet::from(["lookup".to_string()])
|
||||
);
|
||||
|
||||
mcp_server_handle.abort();
|
||||
let _ = mcp_server_handle.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct McpStatusServer {
|
||||
tool_name: Arc<String>,
|
||||
}
|
||||
|
||||
impl ServerHandler for McpStatusServer {
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
||||
..ServerInfo::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_request: Option<rmcp::model::PaginatedRequestParams>,
|
||||
_context: rmcp::service::RequestContext<rmcp::service::RoleServer>,
|
||||
) -> Result<ListToolsResult, rmcp::ErrorData> {
|
||||
let input_schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None))?;
|
||||
|
||||
let mut tool = Tool::new(
|
||||
Cow::Owned(self.tool_name.as_ref().clone()),
|
||||
Cow::Borrowed("Look up test data."),
|
||||
Arc::new(input_schema),
|
||||
);
|
||||
tool.annotations = Some(ToolAnnotations::new().read_only(true));
|
||||
|
||||
Ok(ListToolsResult {
|
||||
tools: vec![tool],
|
||||
next_cursor: None,
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_server_status_list_does_not_duplicate_tools_for_sanitized_name_collisions()
|
||||
-> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let (dash_server_url, dash_server_handle) = start_mcp_server("dash_lookup").await?;
|
||||
let (underscore_server_url, underscore_server_handle) =
|
||||
start_mcp_server("underscore_lookup").await?;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_mock_responses_config_toml(
|
||||
codex_home.path(),
|
||||
&server.uri(),
|
||||
&BTreeMap::new(),
|
||||
/*auto_compact_limit*/ 1024,
|
||||
/*requires_openai_auth*/ None,
|
||||
"mock_provider",
|
||||
"compact",
|
||||
)?;
|
||||
|
||||
let config_path = codex_home.path().join("config.toml");
|
||||
let mut config_toml = std::fs::read_to_string(&config_path)?;
|
||||
config_toml.push_str(&format!(
|
||||
r#"
|
||||
[mcp_servers.some-server]
|
||||
url = "{dash_server_url}/mcp"
|
||||
|
||||
[mcp_servers.some_server]
|
||||
url = "{underscore_server_url}/mcp"
|
||||
"#
|
||||
));
|
||||
std::fs::write(config_path, config_toml)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_list_mcp_server_status_request(ListMcpServerStatusParams {
|
||||
cursor: None,
|
||||
limit: None,
|
||||
})
|
||||
.await?;
|
||||
let response = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: ListMcpServerStatusResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(response.next_cursor, None);
|
||||
assert_eq!(response.data.len(), 2);
|
||||
let status_tools = response
|
||||
.data
|
||||
.iter()
|
||||
.map(|status| (status.name.as_str(), status.tools.keys().count()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
assert_eq!(
|
||||
status_tools,
|
||||
BTreeMap::from([("some-server", 0), ("some_server", 0)])
|
||||
);
|
||||
|
||||
dash_server_handle.abort();
|
||||
let _ = dash_server_handle.await;
|
||||
underscore_server_handle.abort();
|
||||
let _ = underscore_server_handle.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_mcp_server(tool_name: &str) -> Result<(String, JoinHandle<()>)> {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let addr = listener.local_addr()?;
|
||||
let tool_name = Arc::new(tool_name.to_string());
|
||||
let mcp_service = StreamableHttpService::new(
|
||||
move || {
|
||||
Ok(McpStatusServer {
|
||||
tool_name: Arc::clone(&tool_name),
|
||||
})
|
||||
},
|
||||
Arc::new(LocalSessionManager::default()),
|
||||
StreamableHttpServerConfig::default(),
|
||||
);
|
||||
let router = Router::new().nest_service("/mcp", mcp_service);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, router).await;
|
||||
});
|
||||
|
||||
Ok((format!("http://{addr}"), handle))
|
||||
}
|
||||
@@ -15,7 +15,6 @@ mod experimental_feature_list;
|
||||
mod fs;
|
||||
mod initialize;
|
||||
mod mcp_server_elicitation;
|
||||
mod mcp_server_status;
|
||||
mod model_list;
|
||||
mod output_schema;
|
||||
mod plan_item;
|
||||
|
||||
@@ -95,10 +95,7 @@ async fn thread_shell_command_runs_as_standalone_turn_and_persists_history() ->
|
||||
assert_eq!(status, &CommandExecutionStatus::InProgress);
|
||||
|
||||
let delta = wait_for_command_execution_output_delta(&mut mcp, &command_id).await?;
|
||||
assert_eq!(
|
||||
delta.delta.trim_end_matches(['\r', '\n']),
|
||||
expected_output.trim_end_matches(['\r', '\n'])
|
||||
);
|
||||
assert_eq!(delta.delta, expected_output);
|
||||
|
||||
let completed = wait_for_command_execution_completed(&mut mcp, Some(&command_id)).await?;
|
||||
let ThreadItem::CommandExecution {
|
||||
|
||||
@@ -644,7 +644,7 @@ model_reasoning_effort = "high"
|
||||
let config_toml = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
|
||||
let trusted_root = resolve_root_git_project_for_trust(workspace.path())
|
||||
.unwrap_or_else(|| workspace.path().to_path_buf());
|
||||
assert!(config_toml.contains(&persisted_trust_path(&trusted_root)));
|
||||
assert!(config_toml.contains(&trusted_root.display().to_string()));
|
||||
assert!(config_toml.contains("trust_level = \"trusted\""));
|
||||
|
||||
Ok(())
|
||||
@@ -681,8 +681,8 @@ async fn thread_start_with_nested_git_cwd_trusts_repo_root() -> Result<()> {
|
||||
let config_toml = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
|
||||
let trusted_root =
|
||||
resolve_root_git_project_for_trust(&nested).expect("git root should resolve");
|
||||
assert!(config_toml.contains(&persisted_trust_path(&trusted_root)));
|
||||
assert!(!config_toml.contains(&persisted_trust_path(&nested)));
|
||||
assert!(config_toml.contains(&trusted_root.display().to_string()));
|
||||
assert!(!config_toml.contains(&nested.display().to_string()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -776,21 +776,6 @@ fn create_config_toml_without_approval_policy(
|
||||
)
|
||||
}
|
||||
|
||||
fn persisted_trust_path(project_path: &Path) -> String {
|
||||
let project_path =
|
||||
std::fs::canonicalize(project_path).unwrap_or_else(|_| project_path.to_path_buf());
|
||||
let project_path = project_path.display().to_string();
|
||||
|
||||
if let Some(project_path) = project_path.strip_prefix(r"\\?\UNC\") {
|
||||
return format!(r"\\{project_path}");
|
||||
}
|
||||
|
||||
project_path
|
||||
.strip_prefix(r"\\?\")
|
||||
.unwrap_or(&project_path)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn create_config_toml_with_optional_approval_policy(
|
||||
codex_home: &Path,
|
||||
server_uri: &str,
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
//! into a one-shot CLI command while still producing a durable `codex-login.log` artifact that
|
||||
//! support can request from users.
|
||||
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_core::config::Config;
|
||||
use codex_login::AuthCredentialsStoreMode;
|
||||
use codex_login::AuthMode;
|
||||
use codex_login::CLIENT_ID;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::ServerOptions;
|
||||
|
||||
@@ -51,8 +51,6 @@ use codex_core::config::find_codex_home;
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Stage;
|
||||
use codex_features::is_known_feature_key;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
|
||||
/// Codex CLI
|
||||
@@ -172,9 +170,6 @@ enum DebugSubcommand {
|
||||
/// Tooling: helps debug the app server.
|
||||
AppServer(DebugAppServerCommand),
|
||||
|
||||
/// Render the model-visible prompt input list as JSON.
|
||||
PromptInput(DebugPromptInputCommand),
|
||||
|
||||
/// Internal: reset local memory state for a fresh start.
|
||||
#[clap(hide = true)]
|
||||
ClearMemories,
|
||||
@@ -198,17 +193,6 @@ struct DebugAppServerSendMessageV2Command {
|
||||
user_message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct DebugPromptInputCommand {
|
||||
/// Optional user prompt to append after session context.
|
||||
#[arg(value_name = "PROMPT")]
|
||||
prompt: Option<String>,
|
||||
|
||||
/// Optional image(s) to attach to the user prompt.
|
||||
#[arg(long = "image", short = 'i', value_name = "FILE", value_delimiter = ',', num_args = 1..)]
|
||||
images: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct ResumeCommand {
|
||||
/// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses.
|
||||
@@ -303,8 +287,6 @@ struct LoginCommand {
|
||||
|
||||
#[arg(
|
||||
long = "api-key",
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "",
|
||||
value_name = "API_KEY",
|
||||
help = "(deprecated) Previously accepted the API key directly; now exits with guidance to use --with-api-key",
|
||||
hide = true
|
||||
@@ -931,20 +913,6 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
)?;
|
||||
run_debug_app_server_command(cmd).await?;
|
||||
}
|
||||
DebugSubcommand::PromptInput(cmd) => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"debug prompt-input",
|
||||
)?;
|
||||
run_debug_prompt_input_command(
|
||||
cmd,
|
||||
root_config_overrides,
|
||||
interactive,
|
||||
arg0_paths.clone(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
DebugSubcommand::ClearMemories => {
|
||||
reject_remote_mode_for_subcommand(
|
||||
root_remote.as_deref(),
|
||||
@@ -1113,72 +1081,6 @@ fn maybe_print_under_development_feature_warning(
|
||||
);
|
||||
}
|
||||
|
||||
async fn run_debug_prompt_input_command(
|
||||
cmd: DebugPromptInputCommand,
|
||||
root_config_overrides: CliConfigOverrides,
|
||||
interactive: TuiCli,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut cli_kv_overrides = root_config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
if interactive.web_search {
|
||||
cli_kv_overrides.push((
|
||||
"web_search".to_string(),
|
||||
toml::Value::String("live".to_string()),
|
||||
));
|
||||
}
|
||||
|
||||
let approval_policy = if interactive.full_auto {
|
||||
Some(AskForApproval::OnRequest)
|
||||
} else if interactive.dangerously_bypass_approvals_and_sandbox {
|
||||
Some(AskForApproval::Never)
|
||||
} else {
|
||||
interactive.approval_policy.map(Into::into)
|
||||
};
|
||||
let sandbox_mode = if interactive.full_auto {
|
||||
Some(codex_protocol::config_types::SandboxMode::WorkspaceWrite)
|
||||
} else if interactive.dangerously_bypass_approvals_and_sandbox {
|
||||
Some(codex_protocol::config_types::SandboxMode::DangerFullAccess)
|
||||
} else {
|
||||
interactive.sandbox_mode.map(Into::into)
|
||||
};
|
||||
let overrides = ConfigOverrides {
|
||||
model: interactive.model,
|
||||
config_profile: interactive.config_profile,
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
cwd: interactive.cwd,
|
||||
codex_self_exe: arg0_paths.codex_self_exe,
|
||||
codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe,
|
||||
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe,
|
||||
show_raw_agent_reasoning: interactive.oss.then_some(true),
|
||||
ephemeral: Some(true),
|
||||
additional_writable_roots: interactive.add_dir,
|
||||
..Default::default()
|
||||
};
|
||||
let config =
|
||||
Config::load_with_cli_overrides_and_harness_overrides(cli_kv_overrides, overrides).await?;
|
||||
|
||||
let mut input = interactive
|
||||
.images
|
||||
.into_iter()
|
||||
.chain(cmd.images)
|
||||
.map(|path| UserInput::LocalImage { path })
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(prompt) = cmd.prompt.or(interactive.prompt) {
|
||||
input.push(UserInput::Text {
|
||||
text: prompt.replace("\r\n", "\n").replace('\r', "\n"),
|
||||
text_elements: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let prompt_input = codex_core::prompt_debug::build_prompt_input(config, input).await?;
|
||||
println!("{}", serde_json::to_string_pretty(&prompt_input)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_debug_clear_memories_command(
|
||||
root_config_overrides: &CliConfigOverrides,
|
||||
interactive: &TuiCli,
|
||||
@@ -1585,32 +1487,6 @@ mod tests {
|
||||
app_server
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_prompt_input_parses_prompt_and_images() {
|
||||
let cli = MultitoolCli::try_parse_from([
|
||||
"codex",
|
||||
"debug",
|
||||
"prompt-input",
|
||||
"hello",
|
||||
"--image",
|
||||
"/tmp/a.png,/tmp/b.png",
|
||||
])
|
||||
.expect("parse");
|
||||
|
||||
let Some(Subcommand::Debug(DebugCommand {
|
||||
subcommand: DebugSubcommand::PromptInput(cmd),
|
||||
})) = cli.subcommand
|
||||
else {
|
||||
panic!("expected debug prompt-input subcommand");
|
||||
};
|
||||
|
||||
assert_eq!(cmd.prompt.as_deref(), Some("hello"));
|
||||
assert_eq!(
|
||||
cmd.images,
|
||||
vec![PathBuf::from("/tmp/a.png"), PathBuf::from("/tmp/b.png")]
|
||||
);
|
||||
}
|
||||
|
||||
fn sample_exit_info(conversation_id: Option<&str>, thread_name: Option<&str>) -> AppExitInfo {
|
||||
let token_usage = TokenUsage {
|
||||
output_tokens: 2,
|
||||
|
||||
@@ -6,9 +6,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod api_bridge;
|
||||
pub mod auth;
|
||||
pub mod common;
|
||||
pub mod endpoint;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "collaboration-mode-templates",
|
||||
crate_name = "codex_collaboration_mode_templates",
|
||||
compile_data = glob(["templates/*.md"]),
|
||||
)
|
||||
|
||||
exports_files(
|
||||
glob(["templates/*.md"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-collaboration-mode-templates"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_collaboration_mode_templates"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,4 +0,0 @@
|
||||
pub const PLAN: &str = include_str!("../templates/plan.md");
|
||||
pub const DEFAULT: &str = include_str!("../templates/default.md");
|
||||
pub const EXECUTE: &str = include_str!("../templates/execute.md");
|
||||
pub const PAIR_PROGRAMMING: &str = include_str!("../templates/pair_programming.md");
|
||||
@@ -1,8 +1,17 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
exports_files(
|
||||
[
|
||||
"templates/collaboration_mode/default.md",
|
||||
"templates/collaboration_mode/plan.md",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "model_availability_nux_fixtures",
|
||||
srcs = [
|
||||
"models.json",
|
||||
"tests/cli_responses_fixture.sse",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -29,8 +38,9 @@ codex_rust_crate(
|
||||
},
|
||||
integration_compile_data_extra = [
|
||||
"//codex-rs/apply-patch:apply_patch_tool_instructions.md",
|
||||
"models.json",
|
||||
"prompt.md",
|
||||
],
|
||||
integration_test_timeout = "long",
|
||||
test_data_extra = [
|
||||
"config.schema.json",
|
||||
] + glob([
|
||||
@@ -47,7 +57,6 @@ codex_rust_crate(
|
||||
"//:AGENTS.md",
|
||||
],
|
||||
test_tags = ["no-sandbox"],
|
||||
unit_test_timeout = "long",
|
||||
extra_binaries = [
|
||||
"//codex-rs/linux-sandbox:codex-linux-sandbox",
|
||||
"//codex-rs/rmcp-client:test_stdio_server",
|
||||
|
||||
@@ -23,6 +23,7 @@ async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bm25 = { workspace = true }
|
||||
chardetng = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-analytics = { workspace = true }
|
||||
@@ -36,11 +37,8 @@ codex-config = { workspace = true }
|
||||
codex-core-skills = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-mcp = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
codex-models-manager = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
@@ -50,7 +48,6 @@ codex-network-proxy = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-response-debug-context = { workspace = true }
|
||||
codex-rollout = { workspace = true }
|
||||
codex-rmcp-client = { workspace = true }
|
||||
codex-sandboxing = { workspace = true }
|
||||
@@ -74,6 +71,7 @@ codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-
|
||||
csv = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
env-flags = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
@@ -119,6 +117,10 @@ uuid = { workspace = true, features = ["serde", "v4", "v5"] }
|
||||
which = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
landlock = { workspace = true }
|
||||
seccompiler = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
|
||||
|
||||
@@ -521,15 +521,6 @@
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_apps_instructions": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_environment_context": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_permissions_instructions": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"js_repl_node_module_dirs": {
|
||||
"description": "Ordered list of directories to search for Node modules in `js_repl`.",
|
||||
"items": {
|
||||
@@ -2277,18 +2268,6 @@
|
||||
"default": null,
|
||||
"description": "Settings that govern if and what will be written to `~/.codex/history.jsonl`."
|
||||
},
|
||||
"include_apps_instructions": {
|
||||
"description": "Whether to inject the `<apps_instructions>` developer block.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_environment_context": {
|
||||
"description": "Whether to inject the `<environment_context>` user block.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_permissions_instructions": {
|
||||
"description": "Whether to inject the `<permissions instructions>` developer block.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"instructions": {
|
||||
"description": "System instructions.",
|
||||
"type": "string"
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(crate) async fn resolve_agent_target(
|
||||
.resolve_agent_reference(session.conversation_id, &turn.session_source, target)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
codex_protocol::error::CodexErr::UnsupportedOperation(message) => {
|
||||
crate::error::CodexErr::UnsupportedOperation(message) => {
|
||||
FunctionCallError::RespondToModel(message)
|
||||
}
|
||||
other => FunctionCallError::RespondToModel(other.to_string()),
|
||||
|
||||
@@ -5,6 +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::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::find_archived_thread_path_by_id_str;
|
||||
use crate::find_thread_path_by_id_str;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
@@ -16,9 +18,7 @@ use crate::thread_rollout_truncation::truncate_rollout_to_last_n_fork_turns;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
@@ -39,6 +39,7 @@ use tokio::sync::watch;
|
||||
use tracing::warn;
|
||||
|
||||
const AGENT_NAMES: &str = include_str!("agent_names.txt");
|
||||
const FORKED_SPAWN_AGENT_OUTPUT_MESSAGE: &str = "You are the newly spawned agent. The prior conversation history was forked from your parent agent. Treat the next user message as your new task, and use the forked history only as background context.";
|
||||
const ROOT_LAST_TASK_MESSAGE: &str = "Main thread";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -92,36 +93,6 @@ fn agent_nickname_candidates(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn keep_forked_rollout_item(item: &RolloutItem) -> bool {
|
||||
match item {
|
||||
RolloutItem::ResponseItem(ResponseItem::Message { role, phase, .. }) => match role.as_str()
|
||||
{
|
||||
"system" | "developer" | "user" => true,
|
||||
"assistant" => *phase == Some(MessagePhase::FinalAnswer),
|
||||
_ => false,
|
||||
},
|
||||
RolloutItem::ResponseItem(
|
||||
ResponseItem::Reasoning { .. }
|
||||
| ResponseItem::LocalShellCall { .. }
|
||||
| ResponseItem::FunctionCall { .. }
|
||||
| ResponseItem::ToolSearchCall { .. }
|
||||
| ResponseItem::FunctionCallOutput { .. }
|
||||
| ResponseItem::CustomToolCall { .. }
|
||||
| ResponseItem::CustomToolCallOutput { .. }
|
||||
| ResponseItem::ToolSearchOutput { .. }
|
||||
| ResponseItem::WebSearchCall { .. }
|
||||
| ResponseItem::ImageGenerationCall { .. }
|
||||
| ResponseItem::GhostSnapshot { .. }
|
||||
| ResponseItem::Compaction { .. }
|
||||
| ResponseItem::Other,
|
||||
) => false,
|
||||
RolloutItem::Compacted(_)
|
||||
| RolloutItem::EventMsg(_)
|
||||
| RolloutItem::SessionMeta(_)
|
||||
| RolloutItem::TurnContext(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Control-plane handle for multi-agent operations.
|
||||
/// `AgentControl` is held by each session (via `SessionServices`). It provides capability to
|
||||
/// spawn new agents and the inter-agent communication layer.
|
||||
@@ -289,11 +260,11 @@ impl AgentControl {
|
||||
inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
|
||||
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
) -> CodexResult<crate::thread_manager::NewThread> {
|
||||
if options.fork_parent_spawn_call_id.is_none() {
|
||||
let Some(call_id) = options.fork_parent_spawn_call_id.as_deref() else {
|
||||
return Err(CodexErr::Fatal(
|
||||
"spawn_agent fork requires a parent spawn call id".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let Some(fork_mode) = options.fork_mode.as_ref() else {
|
||||
return Err(CodexErr::Fatal(
|
||||
"spawn_agent fork requires a fork mode".to_string(),
|
||||
@@ -342,7 +313,16 @@ impl AgentControl {
|
||||
forked_rollout_items =
|
||||
truncate_rollout_to_last_n_fork_turns(&forked_rollout_items, *last_n_turns);
|
||||
}
|
||||
forked_rollout_items.retain(keep_forked_rollout_item);
|
||||
|
||||
let mut output =
|
||||
FunctionCallOutputPayload::from_text(FORKED_SPAWN_AGENT_OUTPUT_MESSAGE.to_string());
|
||||
output.success = Some(true);
|
||||
forked_rollout_items.push(RolloutItem::ResponseItem(
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
output,
|
||||
},
|
||||
));
|
||||
|
||||
state
|
||||
.fork_thread_with_source(
|
||||
|
||||
@@ -14,7 +14,6 @@ use codex_login::CodexAuth;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -63,28 +62,6 @@ fn text_input(text: &str) -> Op {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn assistant_message(text: &str, phase: Option<MessagePhase>) -> ResponseItem {
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: text.to_string(),
|
||||
}],
|
||||
end_turn: None,
|
||||
phase,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_agent_call(call_id: &str) -> ResponseItem {
|
||||
ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "spawn_agent".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: call_id.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
struct AgentControlHarness {
|
||||
_home: TempDir,
|
||||
config: Config,
|
||||
@@ -584,7 +561,7 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
async fn spawn_agent_can_fork_parent_thread_history() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let (parent_thread_id, parent_thread) = harness.start_thread().await;
|
||||
parent_thread
|
||||
@@ -592,32 +569,103 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.await;
|
||||
let turn_context = parent_thread.codex.session.new_default_turn().await;
|
||||
let parent_spawn_call_id = "spawn-call-history".to_string();
|
||||
let trigger_message = InterAgentCommunication::new(
|
||||
AgentPath::root(),
|
||||
AgentPath::try_from("/root/worker").expect("agent path"),
|
||||
Vec::new(),
|
||||
"parent trigger message".to_string(),
|
||||
/*trigger_turn*/ true,
|
||||
);
|
||||
let parent_spawn_call = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "spawn_agent".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: parent_spawn_call_id.clone(),
|
||||
};
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.record_conversation_items(
|
||||
turn_context.as_ref(),
|
||||
&[
|
||||
assistant_message("parent commentary", Some(MessagePhase::Commentary)),
|
||||
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
|
||||
assistant_message("parent unknown phase", /*phase*/ None),
|
||||
ResponseItem::Reasoning {
|
||||
id: "parent-reasoning".to_string(),
|
||||
summary: Vec::new(),
|
||||
content: None,
|
||||
encrypted_content: None,
|
||||
},
|
||||
trigger_message.to_response_input_item().into(),
|
||||
spawn_agent_call(&parent_spawn_call_id),
|
||||
],
|
||||
.record_conversation_items(turn_context.as_ref(), &[parent_spawn_call])
|
||||
.await;
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.ensure_rollout_materialized()
|
||||
.await;
|
||||
parent_thread.codex.session.flush_rollout().await;
|
||||
|
||||
let child_thread_id = harness
|
||||
.control
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("forked spawn should succeed")
|
||||
.thread_id;
|
||||
|
||||
let child_thread = harness
|
||||
.manager
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
assert_ne!(child_thread_id, parent_thread_id);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
assert!(history_contains_text(
|
||||
history.raw_items(),
|
||||
"parent seed context"
|
||||
));
|
||||
|
||||
let expected = (
|
||||
child_thread_id,
|
||||
Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "child task".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
},
|
||||
);
|
||||
let captured = harness
|
||||
.manager
|
||||
.captured_ops()
|
||||
.into_iter()
|
||||
.find(|entry| *entry == expected);
|
||||
assert_eq!(captured, Some(expected));
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
.shutdown_live_agent(child_thread_id)
|
||||
.await
|
||||
.expect("child shutdown should submit");
|
||||
let _ = parent_thread
|
||||
.submit(Op::Shutdown {})
|
||||
.await
|
||||
.expect("parent shutdown should submit");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_fork_injects_output_for_parent_spawn_call() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let (parent_thread_id, parent_thread) = harness.start_thread().await;
|
||||
let turn_context = parent_thread.codex.session.new_default_turn().await;
|
||||
let parent_spawn_call_id = "spawn-call-1".to_string();
|
||||
let parent_spawn_call = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "spawn_agent".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: parent_spawn_call_id.clone(),
|
||||
};
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.record_conversation_items(turn_context.as_ref(), &[parent_spawn_call])
|
||||
.await;
|
||||
parent_thread
|
||||
.codex
|
||||
@@ -652,42 +700,22 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
assert_ne!(child_thread_id, parent_thread_id);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
let expected_history = [
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "parent seed context".to_string(),
|
||||
}],
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
},
|
||||
assistant_message("parent final answer", Some(MessagePhase::FinalAnswer)),
|
||||
];
|
||||
let injected_output = history.raw_items().iter().find_map(|item| match item {
|
||||
ResponseItem::FunctionCallOutput { call_id, output }
|
||||
if call_id == &parent_spawn_call_id =>
|
||||
{
|
||||
Some(output)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let injected_output =
|
||||
injected_output.expect("forked child should contain synthetic tool output");
|
||||
assert_eq!(
|
||||
history.raw_items(),
|
||||
&expected_history,
|
||||
"forked child history should keep only parent user messages and assistant final answers"
|
||||
injected_output.text_content(),
|
||||
Some(FORKED_SPAWN_AGENT_OUTPUT_MESSAGE)
|
||||
);
|
||||
|
||||
let expected = (
|
||||
child_thread_id,
|
||||
Op::UserInput {
|
||||
items: vec![UserInput::Text {
|
||||
text: "child task".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
final_output_json_schema: None,
|
||||
},
|
||||
);
|
||||
let captured = harness
|
||||
.manager
|
||||
.captured_ops()
|
||||
.into_iter()
|
||||
.find(|entry| *entry == expected);
|
||||
assert_eq!(captured, Some(expected));
|
||||
assert_eq!(injected_output.success, Some(true));
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
@@ -706,16 +734,17 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
|
||||
let (parent_thread_id, parent_thread) = harness.start_thread().await;
|
||||
let turn_context = parent_thread.codex.session.new_default_turn().await;
|
||||
let parent_spawn_call_id = "spawn-call-unflushed".to_string();
|
||||
let parent_spawn_call = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "spawn_agent".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: parent_spawn_call_id.clone(),
|
||||
};
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.record_conversation_items(
|
||||
turn_context.as_ref(),
|
||||
&[
|
||||
assistant_message("unflushed final answer", Some(MessagePhase::FinalAnswer)),
|
||||
spawn_agent_call(&parent_spawn_call_id),
|
||||
],
|
||||
)
|
||||
.record_conversation_items(turn_context.as_ref(), &[parent_spawn_call])
|
||||
.await;
|
||||
|
||||
let child_thread_id = harness
|
||||
@@ -745,10 +774,28 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
assert!(
|
||||
history_contains_text(history.raw_items(), "unflushed final answer"),
|
||||
"forked child history should include unflushed assistant final answers after flushing the parent rollout"
|
||||
);
|
||||
|
||||
let mut parent_call_index = None;
|
||||
let mut injected_output_index = None;
|
||||
for (idx, item) in history.raw_items().iter().enumerate() {
|
||||
match item {
|
||||
ResponseItem::FunctionCall { call_id, .. } if call_id == &parent_spawn_call_id => {
|
||||
parent_call_index = Some(idx);
|
||||
}
|
||||
ResponseItem::FunctionCallOutput { call_id, .. }
|
||||
if call_id == &parent_spawn_call_id =>
|
||||
{
|
||||
injected_output_index = Some(idx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let parent_call_index =
|
||||
parent_call_index.expect("forked child should include the parent spawn_agent call");
|
||||
let injected_output_index = injected_output_index
|
||||
.expect("forked child should include synthetic output for the parent spawn_agent call");
|
||||
assert!(parent_call_index < injected_output_index);
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
@@ -802,18 +849,23 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
&[triggered_communication.to_response_input_item().into()],
|
||||
)
|
||||
.await;
|
||||
|
||||
parent_thread
|
||||
.inject_user_message_without_turn("current parent task".to_string())
|
||||
.await;
|
||||
let spawn_turn_context = parent_thread.codex.session.new_default_turn().await;
|
||||
let parent_spawn_call_id = "spawn-call-last-n".to_string();
|
||||
let parent_spawn_call = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "spawn_agent".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: parent_spawn_call_id.clone(),
|
||||
};
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.record_conversation_items(
|
||||
spawn_turn_context.as_ref(),
|
||||
&[spawn_agent_call(&parent_spawn_call_id)],
|
||||
)
|
||||
.record_conversation_items(spawn_turn_context.as_ref(), &[parent_spawn_call])
|
||||
.await;
|
||||
parent_thread
|
||||
.codex
|
||||
@@ -835,7 +887,7 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
agent_role: None,
|
||||
})),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
|
||||
},
|
||||
)
|
||||
@@ -850,22 +902,22 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
.expect("child thread should be registered");
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
|
||||
assert!(
|
||||
!history_contains_text(history.raw_items(), "old parent context"),
|
||||
"forked child history should drop parent context outside the requested last-N turn window"
|
||||
);
|
||||
assert!(
|
||||
!history_contains_text(history.raw_items(), "queued message"),
|
||||
"forked child history should drop queued inter-agent messages outside the requested last-N turn window"
|
||||
);
|
||||
assert!(
|
||||
!history_contains_text(history.raw_items(), "triggered context"),
|
||||
"forked child history should filter assistant inter-agent messages even when they fall inside the requested last-N turn window"
|
||||
);
|
||||
assert!(
|
||||
history_contains_text(history.raw_items(), "current parent task"),
|
||||
"forked child history should keep the parent user message from the requested last-N turn window"
|
||||
);
|
||||
assert!(!history_contains_text(
|
||||
history.raw_items(),
|
||||
"old parent context"
|
||||
));
|
||||
assert!(!history_contains_text(
|
||||
history.raw_items(),
|
||||
"queued message"
|
||||
));
|
||||
assert!(history_contains_text(
|
||||
history.raw_items(),
|
||||
"triggered context"
|
||||
));
|
||||
assert!(history_contains_text(
|
||||
history.raw_items(),
|
||||
"current parent task"
|
||||
));
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use rand::prelude::IndexedRandom;
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
use crate::AuthProvider as ApiAuthProvider;
|
||||
use crate::TransportError;
|
||||
use crate::error::ApiError;
|
||||
use crate::rate_limits::parse_promo_message;
|
||||
use crate::rate_limits::parse_rate_limit_for_limit;
|
||||
use base64::Engine;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use codex_protocol::auth::PlanType;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::RetryLimitReachedError;
|
||||
use codex_protocol::error::UnexpectedResponseError;
|
||||
use codex_protocol::error::UsageLimitReachedError;
|
||||
use codex_api::AuthProvider as ApiAuthProvider;
|
||||
use codex_api::TransportError;
|
||||
use codex_api::error::ApiError;
|
||||
use codex_api::rate_limits::parse_promo_message;
|
||||
use codex_api::rate_limits::parse_rate_limit_for_limit;
|
||||
use codex_login::token_data::PlanType;
|
||||
use http::HeaderMap;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn map_api_error(err: ApiError) -> CodexErr {
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::RetryLimitReachedError;
|
||||
use crate::error::UnexpectedResponseError;
|
||||
use crate::error::UsageLimitReachedError;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use codex_login::CodexAuth;
|
||||
|
||||
pub(crate) fn map_api_error(err: ApiError) -> CodexErr {
|
||||
match err {
|
||||
ApiError::ContextWindowExceeded => CodexErr::ContextWindowExceeded,
|
||||
ApiError::QuotaExceeded => CodexErr::QuotaExceeded,
|
||||
@@ -161,6 +164,38 @@ fn extract_x_error_json_code(headers: Option<&HeaderMap>) -> Option<String> {
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
pub(crate) fn auth_provider_from_auth(
|
||||
auth: Option<CodexAuth>,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> crate::error::Result<CoreAuthProvider> {
|
||||
if let Some(api_key) = provider.api_key()? {
|
||||
return Ok(CoreAuthProvider {
|
||||
token: Some(api_key),
|
||||
account_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(token) = provider.experimental_bearer_token.clone() {
|
||||
return Ok(CoreAuthProvider {
|
||||
token: Some(token),
|
||||
account_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(auth) = auth {
|
||||
let token = auth.get_token()?;
|
||||
Ok(CoreAuthProvider {
|
||||
token: Some(token),
|
||||
account_id: auth.get_account_id(),
|
||||
})
|
||||
} else {
|
||||
Ok(CoreAuthProvider {
|
||||
token: None,
|
||||
account_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UsageErrorResponse {
|
||||
error: UsageErrorBody,
|
||||
@@ -175,23 +210,24 @@ struct UsageErrorBody {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CoreAuthProvider {
|
||||
pub token: Option<String>,
|
||||
pub account_id: Option<String>,
|
||||
pub(crate) struct CoreAuthProvider {
|
||||
token: Option<String>,
|
||||
account_id: Option<String>,
|
||||
}
|
||||
|
||||
impl CoreAuthProvider {
|
||||
pub fn auth_header_attached(&self) -> bool {
|
||||
pub(crate) fn auth_header_attached(&self) -> bool {
|
||||
self.token
|
||||
.as_ref()
|
||||
.is_some_and(|token| http::HeaderValue::from_str(&format!("Bearer {token}")).is_ok())
|
||||
}
|
||||
|
||||
pub fn auth_header_name(&self) -> Option<&'static str> {
|
||||
pub(crate) fn auth_header_name(&self) -> Option<&'static str> {
|
||||
self.auth_header_attached().then_some("authorization")
|
||||
}
|
||||
|
||||
pub fn for_test(token: Option<&str>, account_id: Option<&str>) -> Self {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn for_test(token: Option<&str>, account_id: Option<&str>) -> Self {
|
||||
Self {
|
||||
token: token.map(str::to_string),
|
||||
account_id: account_id.map(str::to_string),
|
||||
@@ -1,22 +1,22 @@
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_otel::AuthEnvTelemetryMetadata;
|
||||
|
||||
use crate::CODEX_API_KEY_ENV_VAR;
|
||||
use crate::OPENAI_API_KEY_ENV_VAR;
|
||||
use crate::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use codex_login::CODEX_API_KEY_ENV_VAR;
|
||||
use codex_login::OPENAI_API_KEY_ENV_VAR;
|
||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct AuthEnvTelemetry {
|
||||
pub openai_api_key_env_present: bool,
|
||||
pub codex_api_key_env_present: bool,
|
||||
pub codex_api_key_env_enabled: bool,
|
||||
pub provider_env_key_name: Option<String>,
|
||||
pub provider_env_key_present: Option<bool>,
|
||||
pub refresh_token_url_override_present: bool,
|
||||
pub(crate) struct AuthEnvTelemetry {
|
||||
pub(crate) openai_api_key_env_present: bool,
|
||||
pub(crate) codex_api_key_env_present: bool,
|
||||
pub(crate) codex_api_key_env_enabled: bool,
|
||||
pub(crate) provider_env_key_name: Option<String>,
|
||||
pub(crate) provider_env_key_present: Option<bool>,
|
||||
pub(crate) refresh_token_url_override_present: bool,
|
||||
}
|
||||
|
||||
impl AuthEnvTelemetry {
|
||||
pub fn to_otel_metadata(&self) -> AuthEnvTelemetryMetadata {
|
||||
pub(crate) fn to_otel_metadata(&self) -> AuthEnvTelemetryMetadata {
|
||||
AuthEnvTelemetryMetadata {
|
||||
openai_api_key_env_present: self.openai_api_key_env_present,
|
||||
codex_api_key_env_present: self.codex_api_key_env_present,
|
||||
@@ -28,7 +28,7 @@ impl AuthEnvTelemetry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_auth_env_telemetry(
|
||||
pub(crate) fn collect_auth_env_telemetry(
|
||||
provider: &ModelProviderInfo,
|
||||
codex_api_key_env_enabled: bool,
|
||||
) -> AuthEnvTelemetry {
|
||||
@@ -36,6 +36,7 @@ pub fn collect_auth_env_telemetry(
|
||||
openai_api_key_env_present: env_var_present(OPENAI_API_KEY_ENV_VAR),
|
||||
codex_api_key_env_present: env_var_present(CODEX_API_KEY_ENV_VAR),
|
||||
codex_api_key_env_enabled,
|
||||
// Custom provider `env_key` is arbitrary config text, so emit only a safe bucket.
|
||||
provider_env_key_name: provider.env_key.as_ref().map(|_| "configured".to_string()),
|
||||
provider_env_key_present: provider.env_key.as_deref().map(env_var_present),
|
||||
refresh_token_url_override_present: env_var_present(REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR),
|
||||
@@ -53,7 +54,6 @@ fn env_var_present(name: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -65,7 +65,7 @@ mod tests {
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
auth: None,
|
||||
wire_api: WireApi::Responses,
|
||||
wire_api: crate::model_provider_info::WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
@@ -30,6 +30,11 @@ use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::api_bridge::CoreAuthProvider;
|
||||
use crate::api_bridge::auth_provider_from_auth;
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::auth_env_telemetry::AuthEnvTelemetry;
|
||||
use crate::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use codex_api::CompactClient as ApiCompactClient;
|
||||
use codex_api::CompactionInput as ApiCompactionInput;
|
||||
use codex_api::MemoriesClient as ApiMemoriesClient;
|
||||
@@ -54,8 +59,8 @@ use codex_api::create_text_param_for_request;
|
||||
use codex_api::error::ApiError;
|
||||
use codex_api::requests::responses::Compression;
|
||||
use codex_api::response_create_client_metadata;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::AuthMode;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::RefreshTokenError;
|
||||
use codex_login::UnauthorizedRecovery;
|
||||
@@ -95,26 +100,19 @@ use tracing::warn;
|
||||
use crate::client_common::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::client_common::ResponseStream;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use crate::provider_auth::auth_manager_for_provider;
|
||||
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::util::FeedbackRequestTags;
|
||||
use crate::util::emit_feedback_auth_recovery_tags;
|
||||
use codex_api::api_bridge::CoreAuthProvider;
|
||||
use codex_api::api_bridge::map_api_error;
|
||||
use codex_feedback::FeedbackRequestTags;
|
||||
use codex_feedback::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_login::api_bridge::auth_provider_from_auth;
|
||||
use codex_login::auth_env_telemetry::AuthEnvTelemetry;
|
||||
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use codex_login::provider_auth::auth_manager_for_provider;
|
||||
#[cfg(test)]
|
||||
use codex_model_provider_info::DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_response_debug_context::extract_response_debug_context;
|
||||
use codex_response_debug_context::extract_response_debug_context_from_api_error;
|
||||
use codex_response_debug_context::telemetry_api_error_message;
|
||||
use codex_response_debug_context::telemetry_transport_error_message;
|
||||
use crate::util::emit_feedback_request_tags_with_auth_env;
|
||||
|
||||
pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta";
|
||||
pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
|
||||
@@ -127,7 +125,7 @@ const RESPONSES_COMPACT_ENDPOINT: &str = "/responses/compact";
|
||||
const MEMORIES_SUMMARIZE_ENDPOINT: &str = "/memories/trace_summarize";
|
||||
#[cfg(test)]
|
||||
pub(crate) const WEBSOCKET_CONNECT_TIMEOUT: Duration =
|
||||
Duration::from_millis(DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS);
|
||||
Duration::from_millis(crate::model_provider_info::DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS);
|
||||
|
||||
/// Session-scoped state shared by all [`ModelClient`] clones.
|
||||
///
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::error::Result;
|
||||
pub use codex_api::common::ResponseEvent;
|
||||
use codex_config::types::Personality;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
@@ -2,10 +2,6 @@ use super::AuthRequestTelemetryContext;
|
||||
use super::ModelClient;
|
||||
use super::PendingUnauthorizedRetry;
|
||||
use super::UnauthorizedRecoveryExecution;
|
||||
use codex_api::api_bridge::CoreAuthProvider;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_model_provider_info::create_oss_provider_with_base_url;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -15,7 +11,10 @@ use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
fn test_model_client(session_source: SessionSource) -> ModelClient {
|
||||
let provider = create_oss_provider_with_base_url("https://example.com/v1", WireApi::Responses);
|
||||
let provider = crate::model_provider_info::create_oss_provider_with_base_url(
|
||||
"https://example.com/v1",
|
||||
crate::model_provider_info::WireApi::Responses,
|
||||
);
|
||||
ModelClient::new(
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::new(),
|
||||
@@ -106,8 +105,8 @@ async fn summarize_memories_returns_empty_for_empty_input() {
|
||||
#[test]
|
||||
fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase() {
|
||||
let auth_context = AuthRequestTelemetryContext::new(
|
||||
Some(AuthMode::Chatgpt),
|
||||
&CoreAuthProvider::for_test(Some("access-token"), Some("workspace-123")),
|
||||
Some(codex_login::AuthMode::Chatgpt),
|
||||
&crate::api_bridge::CoreAuthProvider::for_test(Some("access-token"), Some("workspace-123")),
|
||||
PendingUnauthorizedRetry::from_recovery(UnauthorizedRecoveryExecution {
|
||||
mode: "managed",
|
||||
phase: "refresh_token",
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::agent::MailboxReceiver;
|
||||
use crate::agent::agent_status_from_event;
|
||||
use crate::agent::status::is_final;
|
||||
use crate::apps::render_apps_section;
|
||||
use crate::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use crate::commit_attribution::commit_message_trailer_instruction;
|
||||
use crate::compact;
|
||||
use crate::compact::InitialContextInjection;
|
||||
@@ -22,6 +23,10 @@ use crate::compact_remote::run_inline_remote_auto_compact_task;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::connectors;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
#[cfg(test)]
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::parse_turn_item;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
@@ -64,17 +69,12 @@ use codex_hooks::Hooks;
|
||||
use codex_hooks::HooksConfig;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_mcp::mcp_connection_manager::McpConnectionManager;
|
||||
use codex_mcp::mcp_connection_manager::SandboxState;
|
||||
use codex_mcp::mcp_connection_manager::ToolInfo as McpToolInfo;
|
||||
use codex_mcp::mcp_connection_manager::codex_apps_tools_cache_key;
|
||||
use codex_mcp::mcp_connection_manager::filter_non_codex_apps_mcp_tools_only;
|
||||
#[cfg(test)]
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::normalize_host;
|
||||
@@ -168,6 +168,7 @@ use tracing::trace_span;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::client::ModelClient;
|
||||
use crate::client::ModelClientSession;
|
||||
use crate::client_common::Prompt;
|
||||
@@ -183,14 +184,13 @@ use crate::config::resolve_web_search_mode_for_turn;
|
||||
use crate::context_manager::ContextManager;
|
||||
use crate::context_manager::TotalTokenUsageBreakdown;
|
||||
use crate::environment_context::EnvironmentContext;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
#[cfg(test)]
|
||||
use crate::exec::StreamOutput;
|
||||
use codex_config::CONFIG_TOML_FILE;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::ShellEnvironmentPolicy;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
|
||||
mod rollout_reconstruction;
|
||||
#[cfg(test)]
|
||||
@@ -332,7 +332,6 @@ use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
@@ -552,13 +551,13 @@ impl Codex {
|
||||
|
||||
let config = Arc::new(config);
|
||||
let refresh_strategy = match session_source {
|
||||
SessionSource::SubAgent(_) => codex_models_manager::manager::RefreshStrategy::Offline,
|
||||
_ => codex_models_manager::manager::RefreshStrategy::OnlineIfUncached,
|
||||
SessionSource::SubAgent(_) => crate::models_manager::manager::RefreshStrategy::Offline,
|
||||
_ => crate::models_manager::manager::RefreshStrategy::OnlineIfUncached,
|
||||
};
|
||||
if config.model.is_none()
|
||||
|| !matches!(
|
||||
refresh_strategy,
|
||||
codex_models_manager::manager::RefreshStrategy::Offline
|
||||
crate::models_manager::manager::RefreshStrategy::Offline
|
||||
)
|
||||
{
|
||||
let _ = models_manager.list_models(refresh_strategy).await;
|
||||
@@ -571,9 +570,7 @@ impl Codex {
|
||||
// 1. config.base_instructions override
|
||||
// 2. conversation history => session_meta.base_instructions
|
||||
// 3. base_instructions for current model
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
let model_info = models_manager.get_model_info(model.as_str(), &config).await;
|
||||
let base_instructions = config
|
||||
.base_instructions
|
||||
.clone()
|
||||
@@ -906,9 +903,7 @@ impl TurnContext {
|
||||
pub(crate) async fn with_model(&self, model: String, models_manager: &ModelsManager) -> Self {
|
||||
let mut config = (*self.config).clone();
|
||||
config.model = Some(model.clone());
|
||||
let model_info = models_manager
|
||||
.get_model_info(model.as_str(), &config.to_models_manager_config())
|
||||
.await;
|
||||
let model_info = models_manager.get_model_info(model.as_str(), &config).await;
|
||||
let truncation_policy = model_info.truncation_policy.into();
|
||||
let supported_reasoning_levels = model_info
|
||||
.supported_reasoning_levels
|
||||
@@ -2471,7 +2466,7 @@ impl Session {
|
||||
.models_manager
|
||||
.get_model_info(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
&per_turn_config,
|
||||
)
|
||||
.await;
|
||||
let plugin_outcome = self
|
||||
@@ -3579,24 +3574,22 @@ impl Session {
|
||||
{
|
||||
developer_sections.push(model_switch_message.into_text());
|
||||
}
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.config.approvals_reviewer,
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
&turn_context.cwd,
|
||||
turn_context
|
||||
.features
|
||||
.enabled(Feature::ExecPermissionApprovals),
|
||||
turn_context
|
||||
.features
|
||||
.enabled(Feature::RequestPermissionsTool),
|
||||
)
|
||||
.into_text(),
|
||||
);
|
||||
}
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.config.approvals_reviewer,
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
&turn_context.cwd,
|
||||
turn_context
|
||||
.features
|
||||
.enabled(Feature::ExecPermissionApprovals),
|
||||
turn_context
|
||||
.features
|
||||
.enabled(Feature::RequestPermissionsTool),
|
||||
)
|
||||
.into_text(),
|
||||
);
|
||||
let separate_guardian_developer_message =
|
||||
crate::guardian::is_guardian_reviewer_source(&session_source);
|
||||
// Keep the guardian policy prompt out of the aggregated developer bundle so it
|
||||
@@ -3646,7 +3639,7 @@ impl Session {
|
||||
);
|
||||
}
|
||||
}
|
||||
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
|
||||
if turn_context.apps_enabled() {
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
let accessible_and_enabled_connectors =
|
||||
connectors::list_accessible_and_enabled_connectors_from_manager(
|
||||
@@ -3689,18 +3682,16 @@ impl Session {
|
||||
.serialize_to_text(),
|
||||
);
|
||||
}
|
||||
if turn_context.config.include_environment_context {
|
||||
let subagents = self
|
||||
.services
|
||||
.agent_control
|
||||
.format_environment_context_subagents(self.conversation_id)
|
||||
.await;
|
||||
contextual_user_sections.push(
|
||||
EnvironmentContext::from_turn_context(turn_context, shell.as_ref())
|
||||
.with_subagents(subagents)
|
||||
.serialize_to_xml(),
|
||||
);
|
||||
}
|
||||
let subagents = self
|
||||
.services
|
||||
.agent_control
|
||||
.format_environment_context_subagents(self.conversation_id)
|
||||
.await;
|
||||
contextual_user_sections.push(
|
||||
EnvironmentContext::from_turn_context(turn_context, shell.as_ref())
|
||||
.with_subagents(subagents)
|
||||
.serialize_to_xml(),
|
||||
);
|
||||
|
||||
let mut items = Vec::with_capacity(3);
|
||||
if let Some(developer_message) =
|
||||
@@ -5513,7 +5504,7 @@ async fn spawn_review_thread(
|
||||
let review_model_info = sess
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(&model, &config.to_models_manager_config())
|
||||
.get_model_info(&model, &config)
|
||||
.await;
|
||||
// For reviews, disable web_search and view_image regardless of global settings.
|
||||
let mut review_features = sess.features.clone();
|
||||
@@ -6490,7 +6481,6 @@ pub(crate) fn build_prompt(
|
||||
output_schema: turn_context.final_output_json_schema.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(level = "trace",
|
||||
skip_all,
|
||||
@@ -7427,25 +7417,6 @@ async fn try_run_sampling_request(
|
||||
cancellation_token: cancellation_token.child_token(),
|
||||
};
|
||||
|
||||
let preempt_for_mailbox_mail = match &item {
|
||||
ResponseItem::Message { role, phase, .. } => {
|
||||
role == "assistant" && matches!(phase, Some(MessagePhase::Commentary))
|
||||
}
|
||||
ResponseItem::Reasoning { .. } => true,
|
||||
ResponseItem::LocalShellCall { .. }
|
||||
| ResponseItem::FunctionCall { .. }
|
||||
| ResponseItem::ToolSearchCall { .. }
|
||||
| ResponseItem::FunctionCallOutput { .. }
|
||||
| ResponseItem::CustomToolCall { .. }
|
||||
| ResponseItem::CustomToolCallOutput { .. }
|
||||
| ResponseItem::ToolSearchOutput { .. }
|
||||
| ResponseItem::WebSearchCall { .. }
|
||||
| ResponseItem::ImageGenerationCall { .. }
|
||||
| ResponseItem::GhostSnapshot { .. }
|
||||
| ResponseItem::Compaction { .. }
|
||||
| ResponseItem::Other => false,
|
||||
};
|
||||
|
||||
let output_result = handle_output_item_done(&mut ctx, item, previously_active_item)
|
||||
.instrument(handle_responses)
|
||||
.await?;
|
||||
@@ -7456,13 +7427,6 @@ async fn try_run_sampling_request(
|
||||
last_agent_message = Some(agent_message);
|
||||
}
|
||||
needs_follow_up |= output_result.needs_follow_up;
|
||||
// todo: remove before stabilizing multi-agent v2
|
||||
if preempt_for_mailbox_mail && sess.mailbox_rx.lock().await.has_pending() {
|
||||
break Ok(SamplingRequestResult {
|
||||
needs_follow_up: true,
|
||||
last_agent_message,
|
||||
});
|
||||
}
|
||||
}
|
||||
ResponseEvent::OutputItemAdded(item) => {
|
||||
if let Some(turn_item) = handle_non_tool_response_item(
|
||||
|
||||
@@ -37,6 +37,7 @@ use crate::codex::SUBMISSION_CHANNEL_CAPACITY;
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::error::CodexErr;
|
||||
use crate::guardian::GuardianApprovalRequest;
|
||||
use crate::guardian::review_approval_request_with_cancel;
|
||||
use crate::guardian::routes_approval_to_guardian;
|
||||
@@ -46,9 +47,8 @@ use crate::mcp_tool_call::MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC;
|
||||
use crate::mcp_tool_call::build_guardian_mcp_tool_review_request;
|
||||
use crate::mcp_tool_call::is_mcp_tool_approval_question_id;
|
||||
use crate::mcp_tool_call::lookup_mcp_tool_metadata;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -9,19 +9,17 @@ use crate::config_loader::NetworkDomainPermissionsToml;
|
||||
use crate::config_loader::RequirementSource;
|
||||
use crate::config_loader::Sourced;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::models_manager::model_info;
|
||||
use crate::shell::default_user_shell;
|
||||
use crate::tools::format_exec_output_str;
|
||||
|
||||
use codex_features::Features;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_mcp::mcp_connection_manager::ToolInfo;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_models_manager::model_info;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
@@ -65,6 +63,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::CompactedItem;
|
||||
use codex_protocol::protocol::ConversationAudioParams;
|
||||
@@ -256,7 +255,9 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
/*auth_manager*/ None,
|
||||
ThreadId::try_from("00000000-0000-4000-8000-000000000001")
|
||||
.expect("test thread id should be valid"),
|
||||
ModelProviderInfo::create_openai_provider(/* base_url */ /*base_url*/ None),
|
||||
crate::model_provider_info::ModelProviderInfo::create_openai_provider(
|
||||
/* base_url */ /*base_url*/ None,
|
||||
),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
/*model_verbosity*/ None,
|
||||
/*enable_request_compression*/ false,
|
||||
@@ -283,23 +284,6 @@ fn developer_input_texts(items: &[ResponseItem]) -> Vec<&str> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn user_input_texts(items: &[ResponseItem]) -> Vec<&str> {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
ResponseItem::Message { role, content, .. } if role == "user" => {
|
||||
Some(content.as_slice())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flat_map(|content| content.iter())
|
||||
.filter_map(|item| match item {
|
||||
ContentItem::InputText { text } => Some(text.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> ToolCallRuntime {
|
||||
let router = Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
@@ -548,8 +532,8 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules()
|
||||
async fn get_base_instructions_no_user_content() {
|
||||
let prompt_with_apply_patch_instructions =
|
||||
include_str!("../prompt_with_apply_patch_instructions.md");
|
||||
let models_response = bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
let models_response: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../models.json")).expect("valid models.json");
|
||||
let model_info_for_slug = |slug: &str, config: &Config| {
|
||||
let model = models_response
|
||||
.models
|
||||
@@ -557,7 +541,7 @@ async fn get_base_instructions_no_user_content() {
|
||||
.find(|candidate| candidate.slug == slug)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| panic!("model slug {slug} is missing from models.json"));
|
||||
model_info::with_config_overrides(model, &config.to_models_manager_config())
|
||||
model_info::with_config_overrides(model, config)
|
||||
};
|
||||
let test_cases = vec![
|
||||
InstructionsTestCase {
|
||||
@@ -1805,10 +1789,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -1906,10 +1887,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -2059,10 +2037,7 @@ async fn turn_context_with_model_updates_model_fields() {
|
||||
let expected_model_info = session
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(
|
||||
"gpt-5.1",
|
||||
&updated.config.as_ref().to_models_manager_config(),
|
||||
)
|
||||
.get_model_info("gpt-5.1", updated.config.as_ref())
|
||||
.await;
|
||||
|
||||
assert_eq!(updated.config.model.as_deref(), Some("gpt-5.1"));
|
||||
@@ -2253,10 +2228,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
let config = build_test_config(codex_home.path()).await;
|
||||
let config = Arc::new(config);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -2520,10 +2492,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
CollaborationModesConfig::default(),
|
||||
));
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
settings: Settings {
|
||||
@@ -2619,10 +2588,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let exec_policy = Arc::new(ExecPolicyManager::default());
|
||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -2666,7 +2632,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
&per_turn_config,
|
||||
);
|
||||
let session_telemetry = session_telemetry(
|
||||
conversation_id,
|
||||
@@ -3458,10 +3424,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
let exec_policy = Arc::new(ExecPolicyManager::default());
|
||||
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
|
||||
let model = ModelsManager::get_model_offline_for_tests(config.model.as_deref());
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
model.as_str(),
|
||||
&config.to_models_manager_config(),
|
||||
);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(model.as_str(), &config);
|
||||
let reasoning_effort = config.model_reasoning_effort;
|
||||
let collaboration_mode = CollaborationMode {
|
||||
mode: ModeKind::Default,
|
||||
@@ -3505,7 +3468,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
let per_turn_config = Session::build_per_turn_config(&session_configuration);
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests(
|
||||
session_configuration.collaboration_mode.model(),
|
||||
&per_turn_config.to_models_manager_config(),
|
||||
&per_turn_config,
|
||||
);
|
||||
let session_telemetry = session_telemetry(
|
||||
conversation_id,
|
||||
@@ -3791,9 +3754,17 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
|
||||
let environment_update = user_input_texts(&update_items)
|
||||
.into_iter()
|
||||
.find(|text| text.contains("<environment_context>"))
|
||||
let environment_update = update_items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
ResponseItem::Message { role, content, .. } if role == "user" => {
|
||||
let [ContentItem::InputText { text }] = content.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
text.contains("<environment_context>").then_some(text)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("environment update item should be emitted");
|
||||
assert!(environment_update.contains("<network enabled=\"true\">"));
|
||||
assert!(environment_update.contains("<allowed>api.example.com</allowed>"));
|
||||
@@ -3818,43 +3789,22 @@ async fn build_settings_update_items_emits_environment_item_for_time_changes() {
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
|
||||
let environment_update = user_input_texts(&update_items)
|
||||
.into_iter()
|
||||
.find(|text| text.contains("<environment_context>"))
|
||||
let environment_update = update_items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
ResponseItem::Message { role, content, .. } if role == "user" => {
|
||||
let [ContentItem::InputText { text }] = content.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
text.contains("<environment_context>").then_some(text)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("environment update item should be emitted");
|
||||
assert!(environment_update.contains("<current_date>2026-02-27</current_date>"));
|
||||
assert!(environment_update.contains("<timezone>Europe/Berlin</timezone>"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_settings_update_items_omits_environment_item_when_disabled() {
|
||||
let (session, previous_context) = make_session_and_context().await;
|
||||
let previous_context = Arc::new(previous_context);
|
||||
let mut current_context = previous_context
|
||||
.with_model(
|
||||
previous_context.model_info.slug.clone(),
|
||||
&session.services.models_manager,
|
||||
)
|
||||
.await;
|
||||
let mut config = (*current_context.config).clone();
|
||||
config.include_environment_context = false;
|
||||
current_context.config = Arc::new(config);
|
||||
current_context.current_date = Some("2026-02-27".to_string());
|
||||
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.await;
|
||||
|
||||
let user_texts = user_input_texts(&update_items);
|
||||
assert!(
|
||||
!user_texts
|
||||
.iter()
|
||||
.any(|text| text.contains("<environment_context>")),
|
||||
"did not expect environment context updates when disabled, got {user_texts:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_settings_update_items_emits_realtime_start_when_session_becomes_live() {
|
||||
let (session, previous_context) = make_session_and_context().await;
|
||||
|
||||
@@ -2,13 +2,13 @@ use crate::agent::AgentStatus;
|
||||
use crate::codex::Codex;
|
||||
use crate::codex::SteerInputError;
|
||||
use crate::config::ConstraintResult;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::file_watcher::WatchRegistration;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::Prompt;
|
||||
use crate::client::ModelClientSession;
|
||||
use crate::client_common::ResponseEvent;
|
||||
@@ -8,10 +9,9 @@ use crate::codex::PreviousTurnSettings;
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::codex::get_last_assistant_message_from_turn;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::util::backoff;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::ContentItem;
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::context_manager::ContextManager;
|
||||
use crate::context_manager::TotalTokenUsageBreakdown;
|
||||
use crate::context_manager::estimate_response_item_model_visible_bytes;
|
||||
use crate::context_manager::is_codex_generated_item;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
|
||||
@@ -20,8 +20,6 @@ use codex_config::types::Notifications;
|
||||
use codex_config::types::ToolSuggestDiscoverableType;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -1780,7 +1778,10 @@ fn responses_websocket_features_do_not_change_wire_api() -> std::io::Result<()>
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
assert_eq!(config.model_provider.wire_api, WireApi::Responses);
|
||||
assert_eq!(
|
||||
config.model_provider.wire_api,
|
||||
crate::model_provider_info::WireApi::Responses
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -4241,8 +4242,8 @@ fn load_config_rejects_unsafe_agent_role_nickname_candidates() -> std::io::Resul
|
||||
fn model_catalog_json_loads_from_path() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let catalog_path = codex_home.path().join("catalog.json");
|
||||
let mut catalog = bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
let mut catalog: ModelsResponse =
|
||||
serde_json::from_str(include_str!("../../models.json")).expect("valid models.json");
|
||||
catalog.models = catalog.models.into_iter().take(1).collect();
|
||||
std::fs::write(
|
||||
&catalog_path,
|
||||
@@ -4356,7 +4357,7 @@ model_verbosity = "high"
|
||||
name: "OpenAI custom".to_string(),
|
||||
base_url: Some("https://api.openai.com/v1".to_string()),
|
||||
env_key: Some("OPENAI_API_KEY".to_string()),
|
||||
wire_api: WireApi::Responses,
|
||||
wire_api: crate::WireApi::Responses,
|
||||
env_key_instructions: None,
|
||||
experimental_bearer_token: None,
|
||||
auth: None,
|
||||
@@ -4493,9 +4494,6 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
guardian_developer_instructions: None,
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4638,9 +4636,6 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
guardian_developer_instructions: None,
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4781,9 +4776,6 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
guardian_developer_instructions: None,
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4910,9 +4902,6 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
guardian_developer_instructions: None,
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -5795,35 +5784,6 @@ async fn approvals_reviewer_defaults_to_manual_only_without_guardian_feature() -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prompt_instruction_blocks_can_be_disabled_from_config_and_profiles() -> std::io::Result<()>
|
||||
{
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"include_permissions_instructions = false
|
||||
include_apps_instructions = false
|
||||
include_environment_context = false
|
||||
profile = "chatty"
|
||||
|
||||
[profiles.chatty]
|
||||
include_permissions_instructions = true
|
||||
include_environment_context = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert!(config.include_permissions_instructions);
|
||||
assert!(!config.include_apps_instructions);
|
||||
assert!(config.include_environment_context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn approvals_reviewer_stays_manual_only_when_guardian_feature_is_enabled()
|
||||
-> std::io::Result<()> {
|
||||
|
||||
@@ -12,8 +12,14 @@ use crate::config_loader::McpServerRequirement;
|
||||
use crate::config_loader::ResidencyRequirement;
|
||||
use crate::config_loader::Sourced;
|
||||
use crate::config_loader::load_config_layers_state;
|
||||
use crate::config_loader::project_trust_key;
|
||||
use crate::memories::memory_root;
|
||||
use crate::model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
use crate::model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::model_provider_info::OLLAMA_CHAT_PROVIDER_REMOVED_ERROR;
|
||||
use crate::model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use crate::model_provider_info::OPENAI_PROVIDER_ID;
|
||||
use crate::model_provider_info::built_in_model_providers;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
||||
@@ -59,14 +65,6 @@ use codex_features::FeaturesToml;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_login::AuthCredentialsStoreMode;
|
||||
use codex_mcp::mcp::McpConfig;
|
||||
use codex_model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::OLLAMA_CHAT_PROVIDER_REMOVED_ERROR;
|
||||
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::OPENAI_PROVIDER_ID;
|
||||
use codex_model_provider_info::built_in_model_providers;
|
||||
use codex_models_manager::ModelsManagerConfig;
|
||||
use codex_protocol::config_types::AltScreenMode;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use codex_protocol::config_types::Personality;
|
||||
@@ -93,6 +91,7 @@ use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use similar::DiffableStr;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
@@ -148,6 +147,7 @@ pub(crate) const DEFAULT_AGENT_MAX_DEPTH: i32 = 1;
|
||||
pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option<u64> = None;
|
||||
|
||||
pub const CONFIG_TOML_FILE: &str = "config.toml";
|
||||
const OPENAI_BASE_URL_ENV_VAR: &str = "OPENAI_BASE_URL";
|
||||
const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [
|
||||
OPENAI_PROVIDER_ID,
|
||||
OLLAMA_OSS_PROVIDER_ID,
|
||||
@@ -280,15 +280,6 @@ pub struct Config {
|
||||
/// Guardian-specific developer instructions override from requirements.toml.
|
||||
pub guardian_developer_instructions: Option<String>,
|
||||
|
||||
/// Whether to inject the `<permissions instructions>` developer block.
|
||||
pub include_permissions_instructions: bool,
|
||||
|
||||
/// Whether to inject the `<apps_instructions>` developer block.
|
||||
pub include_apps_instructions: bool,
|
||||
|
||||
/// Whether to inject the `<environment_context>` user block.
|
||||
pub include_environment_context: bool,
|
||||
|
||||
/// Compact prompt override.
|
||||
pub compact_prompt: Option<String>,
|
||||
|
||||
@@ -692,18 +683,6 @@ impl ConfigBuilder {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn to_models_manager_config(&self) -> ModelsManagerConfig {
|
||||
ModelsManagerConfig {
|
||||
model_context_window: self.model_context_window,
|
||||
model_auto_compact_token_limit: self.model_auto_compact_token_limit,
|
||||
tool_output_token_limit: self.tool_output_token_limit,
|
||||
base_instructions: self.base_instructions.clone(),
|
||||
personality_enabled: self.features.enabled(Feature::Personality),
|
||||
model_supports_reasoning_summaries: self.model_supports_reasoning_summaries,
|
||||
model_catalog: self.model_catalog.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mcp_config(&self, plugins_manager: &crate::plugins::PluginsManager) -> McpConfig {
|
||||
let loaded_plugins = plugins_manager.plugins_for_config(self);
|
||||
let mut configured_mcp_servers = self.mcp_servers.get().clone();
|
||||
@@ -1025,7 +1004,7 @@ pub(crate) fn set_project_trust_level_inner(
|
||||
//
|
||||
// [projects]
|
||||
// "/path/to/project" = { trust_level = "trusted" }
|
||||
let project_key = project_trust_key(project_path);
|
||||
let project_key = project_path.to_string_lossy().to_string();
|
||||
|
||||
// Ensure top-level `projects` exists as a non-inline, explicit table. If it
|
||||
// exists but was previously represented as a non-table (e.g., inline),
|
||||
@@ -1192,15 +1171,6 @@ pub struct ConfigToml {
|
||||
#[serde(default)]
|
||||
pub developer_instructions: Option<String>,
|
||||
|
||||
/// Whether to inject the `<permissions instructions>` developer block.
|
||||
pub include_permissions_instructions: Option<bool>,
|
||||
|
||||
/// Whether to inject the `<apps_instructions>` developer block.
|
||||
pub include_apps_instructions: Option<bool>,
|
||||
|
||||
/// Whether to inject the `<environment_context>` user block.
|
||||
pub include_environment_context: Option<bool>,
|
||||
|
||||
/// Optional path to a file containing model instructions that will override
|
||||
/// the built-in instructions for the selected model. Users are STRONGLY
|
||||
/// DISCOURAGED from using this field, as deviating from the instructions
|
||||
@@ -1762,27 +1732,18 @@ impl ConfigToml {
|
||||
pub fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
|
||||
let projects = self.projects.clone().unwrap_or_default();
|
||||
|
||||
let resolved_cwd_key = project_trust_key(resolved_cwd);
|
||||
let resolved_cwd_raw_key = resolved_cwd.to_string_lossy().to_string();
|
||||
if let Some(project_config) = projects
|
||||
.get(&resolved_cwd_key)
|
||||
.or_else(|| projects.get(&resolved_cwd_raw_key))
|
||||
{
|
||||
if let Some(project_config) = projects.get(&resolved_cwd.to_string_lossy().to_string()) {
|
||||
return Some(project_config.clone());
|
||||
}
|
||||
|
||||
// If cwd lives inside a git repo/worktree, check whether the root git project
|
||||
// (the primary repository working directory) is trusted. This lets
|
||||
// worktrees inherit trust from the main project.
|
||||
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd) {
|
||||
let repo_root_key = project_trust_key(repo_root.as_path());
|
||||
let repo_root_raw_key = repo_root.to_string_lossy().to_string();
|
||||
if let Some(project_config_for_root) = projects
|
||||
.get(&repo_root_key)
|
||||
.or_else(|| projects.get(&repo_root_raw_key))
|
||||
{
|
||||
return Some(project_config_for_root.clone());
|
||||
}
|
||||
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd)
|
||||
&& let Some(project_config_for_root) =
|
||||
projects.get(&repo_root.to_string_lossy().to_string_lossy().to_string())
|
||||
{
|
||||
return Some(project_config_for_root.clone());
|
||||
}
|
||||
|
||||
None
|
||||
@@ -2323,8 +2284,24 @@ impl Config {
|
||||
.openai_base_url
|
||||
.clone()
|
||||
.filter(|value| !value.is_empty());
|
||||
let openai_base_url_from_env = std::env::var(OPENAI_BASE_URL_ENV_VAR)
|
||||
.ok()
|
||||
.filter(|value| !value.is_empty());
|
||||
if openai_base_url_from_env.is_some() {
|
||||
if openai_base_url.is_some() {
|
||||
tracing::warn!(
|
||||
env_var = OPENAI_BASE_URL_ENV_VAR,
|
||||
"deprecated env var is ignored because `openai_base_url` is set in config.toml"
|
||||
);
|
||||
} else {
|
||||
startup_warnings.push(format!(
|
||||
"`{OPENAI_BASE_URL_ENV_VAR}` is deprecated. Set `openai_base_url` in config.toml instead."
|
||||
));
|
||||
}
|
||||
}
|
||||
let effective_openai_base_url = openai_base_url.or(openai_base_url_from_env);
|
||||
|
||||
let mut model_providers = built_in_model_providers(openai_base_url);
|
||||
let mut model_providers = built_in_model_providers(effective_openai_base_url);
|
||||
// Merge user-defined providers into the built-in list.
|
||||
for (key, provider) in cfg.model_providers.into_iter() {
|
||||
model_providers.entry(key).or_insert(provider);
|
||||
@@ -2470,18 +2447,6 @@ impl Config {
|
||||
Self::try_read_non_empty_file(model_instructions_path, "model instructions file")?;
|
||||
let base_instructions = base_instructions.or(file_base_instructions);
|
||||
let developer_instructions = developer_instructions.or(cfg.developer_instructions);
|
||||
let include_permissions_instructions = config_profile
|
||||
.include_permissions_instructions
|
||||
.or(cfg.include_permissions_instructions)
|
||||
.unwrap_or(true);
|
||||
let include_apps_instructions = config_profile
|
||||
.include_apps_instructions
|
||||
.or(cfg.include_apps_instructions)
|
||||
.unwrap_or(true);
|
||||
let include_environment_context = config_profile
|
||||
.include_environment_context
|
||||
.or(cfg.include_environment_context)
|
||||
.unwrap_or(true);
|
||||
let guardian_developer_instructions = guardian_developer_instructions_from_requirements(
|
||||
config_layer_stack.requirements_toml(),
|
||||
);
|
||||
@@ -2648,9 +2613,6 @@ impl Config {
|
||||
developer_instructions,
|
||||
compact_prompt,
|
||||
commit_attribution,
|
||||
include_permissions_instructions,
|
||||
include_apps_instructions,
|
||||
include_environment_context,
|
||||
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
||||
// is important in code to differentiate the mode from the store implementation.
|
||||
cli_auth_credentials_store_mode: cfg.cli_auth_credentials_store.unwrap_or_default(),
|
||||
|
||||
@@ -49,9 +49,6 @@ pub struct ConfigProfile {
|
||||
pub experimental_instructions_file: Option<AbsolutePathBuf>,
|
||||
pub experimental_compact_prompt_file: Option<AbsolutePathBuf>,
|
||||
pub include_apply_patch_tool: Option<bool>,
|
||||
pub include_permissions_instructions: Option<bool>,
|
||||
pub include_apps_instructions: Option<bool>,
|
||||
pub include_environment_context: Option<bool>,
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub tools_view_image: Option<bool>,
|
||||
|
||||
@@ -559,7 +559,7 @@ impl ProjectTrustDecision {
|
||||
|
||||
impl ProjectTrustContext {
|
||||
fn decision_for_dir(&self, dir: &AbsolutePathBuf) -> ProjectTrustDecision {
|
||||
let dir_key = project_trust_key(dir.as_path());
|
||||
let dir_key = dir.as_path().to_string_lossy().to_string();
|
||||
if let Some(trust_level) = self.projects_trust.get(&dir_key).copied() {
|
||||
return ProjectTrustDecision {
|
||||
trust_level: Some(trust_level),
|
||||
@@ -647,17 +647,15 @@ async fn project_trust_context(
|
||||
let project_root = find_project_root(cwd, project_root_markers).await?;
|
||||
let projects = project_trust_config.projects.unwrap_or_default();
|
||||
|
||||
let project_root_key = project_trust_key(project_root.as_path());
|
||||
let project_root_key = project_root.as_path().to_string_lossy().to_string();
|
||||
let repo_root = resolve_root_git_project_for_trust(cwd.as_path());
|
||||
let repo_root_key = repo_root.as_ref().map(|root| project_trust_key(root));
|
||||
let repo_root_key = repo_root
|
||||
.as_ref()
|
||||
.map(|root| root.to_string_lossy().to_string());
|
||||
|
||||
let projects_trust = projects
|
||||
.into_iter()
|
||||
.filter_map(|(key, project)| {
|
||||
project
|
||||
.trust_level
|
||||
.map(|trust_level| (project_trust_key(Path::new(&key)), trust_level))
|
||||
})
|
||||
.filter_map(|(key, project)| project.trust_level.map(|trust_level| (key, trust_level)))
|
||||
.collect();
|
||||
|
||||
Ok(ProjectTrustContext {
|
||||
@@ -669,16 +667,6 @@ async fn project_trust_context(
|
||||
})
|
||||
}
|
||||
|
||||
/// Canonicalize the path and convert it to a string to be used as a key in the
|
||||
/// projects trust map. On Windows, strips UNC, when possible, to try to ensure
|
||||
/// that different paths that point to the same location have the same key.
|
||||
pub fn project_trust_key(project_path: &Path) -> String {
|
||||
normalize_path(project_path)
|
||||
.unwrap_or_else(|_| project_path.to_path_buf())
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Takes a `toml::Value` parsed from a config.toml file and walks through it,
|
||||
/// resolving any `AbsolutePathBuf` fields against `base_dir`, returning a new
|
||||
/// `toml::Value` with the same shape but with paths resolved.
|
||||
|
||||
@@ -16,10 +16,6 @@ fn build_environment_update_item(
|
||||
next: &TurnContext,
|
||||
shell: &Shell,
|
||||
) -> Option<ResponseItem> {
|
||||
if !next.config.include_environment_context {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prev = previous?;
|
||||
let prev_context = EnvironmentContext::from_turn_context_item(prev, shell);
|
||||
let next_context = EnvironmentContext::from_turn_context(next, shell);
|
||||
@@ -37,10 +33,6 @@ fn build_permissions_update_item(
|
||||
next: &TurnContext,
|
||||
exec_policy: &Policy,
|
||||
) -> Option<DeveloperInstructions> {
|
||||
if !next.config.include_permissions_instructions {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prev = previous?;
|
||||
if prev.sandbox_policy == *next.sandbox_policy.get()
|
||||
&& prev.approval_policy == next.approval_policy.value()
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
use crate::ThreadId;
|
||||
use crate::auth::KnownPlan;
|
||||
use crate::auth::PlanType;
|
||||
pub use crate::auth::RefreshTokenFailedError;
|
||||
pub use crate::auth::RefreshTokenFailedReason;
|
||||
use crate::exec_output::ExecToolCallOutput;
|
||||
use crate::network_policy::NetworkPolicyDecisionPayload;
|
||||
use crate::protocol::CodexErrorInfo;
|
||||
use crate::protocol::ErrorEvent;
|
||||
use crate::protocol::RateLimitSnapshot;
|
||||
use crate::protocol::TruncationPolicy;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::network_policy_decision::NetworkPolicyDecisionPayload;
|
||||
use chrono::DateTime;
|
||||
use chrono::Datelike;
|
||||
use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use codex_async_utils::CancelErr;
|
||||
use codex_utils_string::truncate_middle_chars;
|
||||
use codex_utils_string::truncate_middle_with_token_budget;
|
||||
pub use codex_login::auth::RefreshTokenFailedError;
|
||||
pub use codex_login::auth::RefreshTokenFailedReason;
|
||||
use codex_login::token_data::KnownPlan;
|
||||
use codex_login::token_data::PlanType;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::CodexErrorInfo;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_output_truncation::truncate_text;
|
||||
use reqwest::StatusCode;
|
||||
use serde_json;
|
||||
use std::io;
|
||||
@@ -26,7 +25,7 @@ use tokio::task::JoinError;
|
||||
pub type Result<T> = std::result::Result<T, CodexErr>;
|
||||
|
||||
/// Limit UI error messages to a reasonable size while keeping useful context.
|
||||
const ERROR_MESSAGE_UI_MAX_BYTES: usize = 2 * 1024;
|
||||
const ERROR_MESSAGE_UI_MAX_BYTES: usize = 2 * 1024; // 2 KiB
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SandboxErr {
|
||||
@@ -76,84 +75,114 @@ pub enum CodexErr {
|
||||
/// Optionally includes the requested delay before retrying the turn.
|
||||
#[error("stream disconnected before completion: {0}")]
|
||||
Stream(String, Option<Duration>),
|
||||
|
||||
#[error(
|
||||
"Codex ran out of room in the model's context window. Start a new thread or clear earlier history before retrying."
|
||||
)]
|
||||
ContextWindowExceeded,
|
||||
|
||||
#[error("no thread with id: {0}")]
|
||||
ThreadNotFound(ThreadId),
|
||||
|
||||
#[error("agent thread limit reached (max {max_threads})")]
|
||||
AgentLimitReached { max_threads: usize },
|
||||
|
||||
#[error("session configured event was not the first event in the stream")]
|
||||
SessionConfiguredNotFirstEvent,
|
||||
|
||||
/// Returned by run_command_stream when the spawned child process timed out (10s).
|
||||
#[error("timeout waiting for child process to exit")]
|
||||
Timeout,
|
||||
|
||||
/// Returned by run_command_stream when the child could not be spawned (its stdout/stderr pipes
|
||||
/// could not be captured). Analogous to the previous `CodexError::Spawn` variant.
|
||||
#[error("spawn failed: child stdout/stderr not captured")]
|
||||
Spawn,
|
||||
/// Returned by run_command_stream when the user pressed Ctrl-C (SIGINT). Session uses this to
|
||||
|
||||
/// Returned by run_command_stream when the user pressed Ctrl‑C (SIGINT). Session uses this to
|
||||
/// surface a polite FunctionCallOutput back to the model instead of crashing the CLI.
|
||||
#[error("interrupted (Ctrl-C). Something went wrong? Hit `/feedback` to report the issue.")]
|
||||
Interrupted,
|
||||
|
||||
/// Unexpected HTTP status code.
|
||||
#[error("{0}")]
|
||||
UnexpectedStatus(UnexpectedResponseError),
|
||||
|
||||
/// Invalid request.
|
||||
#[error("{0}")]
|
||||
InvalidRequest(String),
|
||||
|
||||
/// Invalid image.
|
||||
#[error("Image poisoning")]
|
||||
InvalidImageRequest(),
|
||||
|
||||
#[error("{0}")]
|
||||
UsageLimitReached(UsageLimitReachedError),
|
||||
|
||||
#[error("Selected model is at capacity. Please try a different model.")]
|
||||
ServerOverloaded,
|
||||
|
||||
#[error("{0}")]
|
||||
ResponseStreamFailed(ResponseStreamFailed),
|
||||
|
||||
#[error("{0}")]
|
||||
ConnectionFailed(ConnectionFailedError),
|
||||
|
||||
#[error("Quota exceeded. Check your plan and billing details.")]
|
||||
QuotaExceeded,
|
||||
|
||||
#[error(
|
||||
"To use Codex with your ChatGPT plan, upgrade to Plus: https://chatgpt.com/explore/plus."
|
||||
)]
|
||||
UsageNotIncluded,
|
||||
|
||||
#[error("We're currently experiencing high demand, which may cause temporary errors.")]
|
||||
InternalServerError,
|
||||
|
||||
/// Retry limit exceeded.
|
||||
#[error("{0}")]
|
||||
RetryLimit(RetryLimitReachedError),
|
||||
|
||||
/// Agent loop died unexpectedly
|
||||
#[error("internal error; agent loop died unexpectedly")]
|
||||
InternalAgentDied,
|
||||
|
||||
/// Sandbox error
|
||||
#[error("sandbox error: {0}")]
|
||||
Sandbox(#[from] SandboxErr),
|
||||
|
||||
#[error("codex-linux-sandbox was required but not provided")]
|
||||
LandlockSandboxExecutableNotProvided,
|
||||
|
||||
#[error("unsupported operation: {0}")]
|
||||
UnsupportedOperation(String),
|
||||
|
||||
#[error("{0}")]
|
||||
RefreshTokenFailed(RefreshTokenFailedError),
|
||||
|
||||
#[error("Fatal error: {0}")]
|
||||
Fatal(String),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Automatic conversions for common external error types
|
||||
// -----------------------------------------------------------------
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error(transparent)]
|
||||
LandlockRuleset(#[from] landlock::RulesetError),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[error(transparent)]
|
||||
LandlockPathFd(#[from] landlock::PathFdError),
|
||||
|
||||
#[error(transparent)]
|
||||
TokioJoin(#[from] JoinError),
|
||||
|
||||
#[error("{0}")]
|
||||
EnvVar(EnvVarError),
|
||||
}
|
||||
@@ -201,65 +230,6 @@ impl CodexErr {
|
||||
CodexErr::LandlockRuleset(_) | CodexErr::LandlockPathFd(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal shim so that existing `e.downcast_ref::<CodexErr>()` checks continue to compile
|
||||
/// after replacing `anyhow::Error` in the return signature. This mirrors the behavior of
|
||||
/// `anyhow::Error::downcast_ref` but works directly on our concrete enum.
|
||||
pub fn downcast_ref<T: std::any::Any>(&self) -> Option<&T> {
|
||||
(self as &dyn std::any::Any).downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// Translate core error to client-facing protocol error.
|
||||
pub fn to_codex_protocol_error(&self) -> CodexErrorInfo {
|
||||
match self {
|
||||
CodexErr::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
|
||||
CodexErr::UsageLimitReached(_)
|
||||
| CodexErr::QuotaExceeded
|
||||
| CodexErr::UsageNotIncluded => CodexErrorInfo::UsageLimitExceeded,
|
||||
CodexErr::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
|
||||
CodexErr::RetryLimit(_) => CodexErrorInfo::ResponseTooManyFailedAttempts {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::ConnectionFailed(_) => CodexErrorInfo::HttpConnectionFailed {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::ResponseStreamFailed(_) => CodexErrorInfo::ResponseStreamConnectionFailed {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::RefreshTokenFailed(_) => CodexErrorInfo::Unauthorized,
|
||||
CodexErr::SessionConfiguredNotFirstEvent
|
||||
| CodexErr::InternalServerError
|
||||
| CodexErr::InternalAgentDied => CodexErrorInfo::InternalServerError,
|
||||
CodexErr::UnsupportedOperation(_)
|
||||
| CodexErr::ThreadNotFound(_)
|
||||
| CodexErr::AgentLimitReached { .. } => CodexErrorInfo::BadRequest,
|
||||
CodexErr::Sandbox(_) => CodexErrorInfo::SandboxError,
|
||||
_ => CodexErrorInfo::Other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_error_event(&self, message_prefix: Option<String>) -> ErrorEvent {
|
||||
let error_message = self.to_string();
|
||||
let message: String = match message_prefix {
|
||||
Some(prefix) => format!("{prefix}: {error_message}"),
|
||||
None => error_message,
|
||||
};
|
||||
ErrorEvent {
|
||||
message,
|
||||
codex_error_info: Some(self.to_codex_protocol_error()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_status_code_value(&self) -> Option<u16> {
|
||||
let http_status_code = match self {
|
||||
CodexErr::RetryLimit(err) => Some(err.status),
|
||||
CodexErr::UnexpectedStatus(err) => Some(err.status),
|
||||
CodexErr::ConnectionFailed(err) => err.source.status(),
|
||||
CodexErr::ResponseStreamFailed(err) => err.source.status(),
|
||||
_ => None,
|
||||
};
|
||||
http_status_code.as_ref().map(StatusCode::as_u16)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -411,13 +381,6 @@ fn truncate_with_ellipsis(text: &str, max_bytes: usize) -> String {
|
||||
truncated
|
||||
}
|
||||
|
||||
fn truncate_text(content: &str, policy: TruncationPolicy) -> String {
|
||||
match policy {
|
||||
TruncationPolicy::Bytes(bytes) => truncate_middle_chars(content, bytes),
|
||||
TruncationPolicy::Tokens(tokens) => truncate_middle_with_token_budget(content, tokens).0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RetryLimitReachedError {
|
||||
pub status: StatusCode,
|
||||
@@ -440,10 +403,10 @@ impl std::fmt::Display for RetryLimitReachedError {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UsageLimitReachedError {
|
||||
pub plan_type: Option<PlanType>,
|
||||
pub resets_at: Option<DateTime<Utc>>,
|
||||
pub rate_limits: Option<Box<RateLimitSnapshot>>,
|
||||
pub promo_message: Option<String>,
|
||||
pub(crate) plan_type: Option<PlanType>,
|
||||
pub(crate) resets_at: Option<DateTime<Utc>>,
|
||||
pub(crate) rate_limits: Option<Box<RateLimitSnapshot>>,
|
||||
pub(crate) promo_message: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UsageLimitReachedError {
|
||||
@@ -575,6 +538,7 @@ fn now_for_retry() -> DateTime<Utc> {
|
||||
pub struct EnvVarError {
|
||||
/// Name of the environment variable that is missing.
|
||||
pub var: String,
|
||||
|
||||
/// Optional instructions to help the user get a valid value for the
|
||||
/// variable and set it.
|
||||
pub instructions: Option<String>,
|
||||
@@ -590,6 +554,67 @@ impl std::fmt::Display for EnvVarError {
|
||||
}
|
||||
}
|
||||
|
||||
impl CodexErr {
|
||||
/// Minimal shim so that existing `e.downcast_ref::<CodexErr>()` checks continue to compile
|
||||
/// after replacing `anyhow::Error` in the return signature. This mirrors the behavior of
|
||||
/// `anyhow::Error::downcast_ref` but works directly on our concrete enum.
|
||||
pub fn downcast_ref<T: std::any::Any>(&self) -> Option<&T> {
|
||||
(self as &dyn std::any::Any).downcast_ref::<T>()
|
||||
}
|
||||
|
||||
/// Translate core error to client-facing protocol error.
|
||||
pub fn to_codex_protocol_error(&self) -> CodexErrorInfo {
|
||||
match self {
|
||||
CodexErr::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
|
||||
CodexErr::UsageLimitReached(_)
|
||||
| CodexErr::QuotaExceeded
|
||||
| CodexErr::UsageNotIncluded => CodexErrorInfo::UsageLimitExceeded,
|
||||
CodexErr::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
|
||||
CodexErr::RetryLimit(_) => CodexErrorInfo::ResponseTooManyFailedAttempts {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::ConnectionFailed(_) => CodexErrorInfo::HttpConnectionFailed {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::ResponseStreamFailed(_) => CodexErrorInfo::ResponseStreamConnectionFailed {
|
||||
http_status_code: self.http_status_code_value(),
|
||||
},
|
||||
CodexErr::RefreshTokenFailed(_) => CodexErrorInfo::Unauthorized,
|
||||
CodexErr::SessionConfiguredNotFirstEvent
|
||||
| CodexErr::InternalServerError
|
||||
| CodexErr::InternalAgentDied => CodexErrorInfo::InternalServerError,
|
||||
CodexErr::UnsupportedOperation(_)
|
||||
| CodexErr::ThreadNotFound(_)
|
||||
| CodexErr::AgentLimitReached { .. } => CodexErrorInfo::BadRequest,
|
||||
CodexErr::Sandbox(_) => CodexErrorInfo::SandboxError,
|
||||
_ => CodexErrorInfo::Other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_error_event(&self, message_prefix: Option<String>) -> ErrorEvent {
|
||||
let error_message = self.to_string();
|
||||
let message: String = match message_prefix {
|
||||
Some(prefix) => format!("{prefix}: {error_message}"),
|
||||
None => error_message,
|
||||
};
|
||||
ErrorEvent {
|
||||
message,
|
||||
codex_error_info: Some(self.to_codex_protocol_error()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_status_code_value(&self) -> Option<u16> {
|
||||
let http_status_code = match self {
|
||||
CodexErr::RetryLimit(err) => Some(err.status),
|
||||
CodexErr::UnexpectedStatus(err) => Some(err.status),
|
||||
CodexErr::ConnectionFailed(err) => err.source.status(),
|
||||
CodexErr::ResponseStreamFailed(err) => err.source.status(),
|
||||
_ => None,
|
||||
};
|
||||
http_status_code.as_ref().map(StatusCode::as_u16)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_error_message_ui(e: &CodexErr) -> String {
|
||||
let message = match e {
|
||||
CodexErr::Sandbox(SandboxErr::Denied { output, .. }) => {
|
||||
@@ -610,7 +635,7 @@ pub fn get_error_message_ui(e: &CodexErr) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Timeouts are not sandbox errors from a UX perspective; present them plainly.
|
||||
// Timeouts are not sandbox errors from a UX perspective; present them plainly
|
||||
CodexErr::Sandbox(SandboxErr::Timeout { output }) => {
|
||||
format!(
|
||||
"error: command timed out after {} ms",
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::*;
|
||||
use crate::exec_output::StreamOutput;
|
||||
use crate::protocol::RateLimitWindow;
|
||||
use crate::exec::StreamOutput;
|
||||
use chrono::DateTime;
|
||||
use chrono::Duration as ChronoDuration;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
use http::Response as HttpResponse;
|
||||
use codex_protocol::protocol::RateLimitWindow;
|
||||
use pretty_assertions::assert_eq;
|
||||
use reqwest::Response;
|
||||
use reqwest::ResponseBuilderExt;
|
||||
@@ -124,7 +123,7 @@ fn sandbox_denied_reports_stdout_when_no_stderr() {
|
||||
|
||||
#[test]
|
||||
fn to_error_event_handles_response_stream_failed() {
|
||||
let response = HttpResponse::builder()
|
||||
let response = http::Response::builder()
|
||||
.status(StatusCode::TOO_MANY_REQUESTS)
|
||||
.url(Url::parse("http://example.com").unwrap())
|
||||
.body("")
|
||||
@@ -17,19 +17,18 @@ use tokio::io::BufReader;
|
||||
use tokio::process::Child;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use crate::error::SandboxErr;
|
||||
use crate::sandboxing::ExecOptions;
|
||||
use crate::sandboxing::ExecRequest;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::spawn::SpawnChildRequest;
|
||||
use crate::spawn::StdioPolicy;
|
||||
use crate::spawn::spawn_child_async;
|
||||
use crate::text_encoding::bytes_to_string_smart;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -633,6 +632,25 @@ fn finalize_exec_result(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod errors {
|
||||
use super::CodexErr;
|
||||
use codex_sandboxing::SandboxTransformError;
|
||||
|
||||
impl From<SandboxTransformError> for CodexErr {
|
||||
fn from(err: SandboxTransformError) -> Self {
|
||||
match err {
|
||||
SandboxTransformError::MissingLinuxSandboxExecutable => {
|
||||
CodexErr::LandlockSandboxExecutableNotProvided
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
SandboxTransformError::SeatbeltUnavailable => CodexErr::UnsupportedOperation(
|
||||
"seatbelt sandbox is only available on macOS".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We don't have a fully deterministic way to tell if our command failed
|
||||
/// because of the sandbox - a command in the user's zshrc file might hit an
|
||||
/// error, but the command itself might fail or succeed for other reasons.
|
||||
@@ -695,6 +713,12 @@ pub(crate) fn is_likely_sandbox_denied(
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamOutput<T: Clone> {
|
||||
pub text: T,
|
||||
pub truncated_after_lines: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RawExecToolCallOutput {
|
||||
pub exit_status: ExitStatus,
|
||||
@@ -704,6 +728,24 @@ struct RawExecToolCallOutput {
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
impl StreamOutput<String> {
|
||||
pub fn new(text: String) -> Self {
|
||||
Self {
|
||||
text,
|
||||
truncated_after_lines: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamOutput<Vec<u8>> {
|
||||
pub fn from_utf8_lossy(&self) -> StreamOutput<String> {
|
||||
StreamOutput {
|
||||
text: bytes_to_string_smart(&self.text),
|
||||
truncated_after_lines: self.truncated_after_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn append_capped(dst: &mut Vec<u8>, src: &[u8], max_bytes: usize) {
|
||||
if dst.len() >= max_bytes {
|
||||
@@ -758,6 +800,29 @@ fn aggregate_output(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecToolCallOutput {
|
||||
pub exit_code: i32,
|
||||
pub stdout: StreamOutput<String>,
|
||||
pub stderr: StreamOutput<String>,
|
||||
pub aggregated_output: StreamOutput<String>,
|
||||
pub duration: Duration,
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
impl Default for ExecToolCallOutput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exit_code: 0,
|
||||
stdout: StreamOutput::new(String::new()),
|
||||
stderr: StreamOutput::new(String::new()),
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn exec(
|
||||
params: ExecParams,
|
||||
|
||||
@@ -21,46 +21,9 @@ pub fn create_env(
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
thread_id: Option<ThreadId>,
|
||||
) -> HashMap<String, String> {
|
||||
create_env_from_vars(std::env::vars(), policy, thread_id)
|
||||
populate_env(std::env::vars(), policy, thread_id)
|
||||
}
|
||||
|
||||
fn create_env_from_vars<I>(
|
||||
vars: I,
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
thread_id: Option<ThreadId>,
|
||||
) -> HashMap<String, String>
|
||||
where
|
||||
I: IntoIterator<Item = (String, String)>,
|
||||
{
|
||||
let mut env_map = populate_env(vars, policy, thread_id);
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
// This is a workaround to address the failures we are seeing in the
|
||||
// following tests when run via Bazel on Windows:
|
||||
//
|
||||
// ```
|
||||
// suite::shell_command::unicode_output::with_login
|
||||
// suite::shell_command::unicode_output::without_login
|
||||
// ```
|
||||
//
|
||||
// Currently, we can only reproduce these failures in CI, which makes
|
||||
// iteration times long, so we include this quick fix for now to unblock
|
||||
// getting the Windows Bazel build running.
|
||||
if !env_map.keys().any(|k| k.eq_ignore_ascii_case("PATHEXT")) {
|
||||
env_map.insert("PATHEXT".to_string(), ".COM;.EXE;.BAT;.CMD".to_string());
|
||||
}
|
||||
}
|
||||
env_map
|
||||
}
|
||||
|
||||
const COMMON_CORE_VARS: &[&str] = &["PATH", "SHELL", "TMPDIR", "TEMP", "TMP"];
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const PLATFORM_CORE_VARS: &[&str] = &["PATHEXT", "USERNAME", "USERPROFILE"];
|
||||
|
||||
#[cfg(unix)]
|
||||
const PLATFORM_CORE_VARS: &[&str] = &["HOME", "LANG", "LC_ALL", "LC_CTYPE", "LOGNAME", "USER"];
|
||||
|
||||
fn populate_env<I>(
|
||||
vars: I,
|
||||
policy: &ShellEnvironmentPolicy,
|
||||
@@ -75,18 +38,17 @@ where
|
||||
ShellEnvironmentPolicyInherit::All => vars.into_iter().collect(),
|
||||
ShellEnvironmentPolicyInherit::None => HashMap::new(),
|
||||
ShellEnvironmentPolicyInherit::Core => {
|
||||
let core_vars: HashSet<&str> = COMMON_CORE_VARS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(PLATFORM_CORE_VARS.iter().copied())
|
||||
.collect();
|
||||
const CORE_VARS: &[&str] = &[
|
||||
"HOME", "LOGNAME", "PATH", "SHELL", "USER", "USERNAME", "TMPDIR", "TEMP", "TMP",
|
||||
];
|
||||
let allow: HashSet<&str> = CORE_VARS.iter().copied().collect();
|
||||
let is_core_var = |name: &str| {
|
||||
if cfg!(target_os = "windows") {
|
||||
core_vars
|
||||
CORE_VARS
|
||||
.iter()
|
||||
.any(|allowed| allowed.eq_ignore_ascii_case(name))
|
||||
} else {
|
||||
core_vars.contains(name)
|
||||
allow.contains(name)
|
||||
}
|
||||
};
|
||||
vars.into_iter().filter(|(k, _)| is_core_var(k)).collect()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use super::*;
|
||||
use codex_config::types::ShellEnvironmentPolicyInherit;
|
||||
use maplit::hashmap;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn make_vars(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
|
||||
pairs
|
||||
@@ -172,7 +171,6 @@ fn test_inherit_all_with_default_excludes() {
|
||||
fn test_core_inherit_respects_case_insensitive_names_on_windows() {
|
||||
let vars = make_vars(&[
|
||||
("Path", "C:\\Windows\\System32"),
|
||||
("PathExt", ".COM;.EXE;.BAT;.CMD"),
|
||||
("TEMP", "C:\\Temp"),
|
||||
("FOO", "bar"),
|
||||
]);
|
||||
@@ -187,7 +185,6 @@ fn test_core_inherit_respects_case_insensitive_names_on_windows() {
|
||||
let result = populate_env(vars, &policy, Some(thread_id));
|
||||
let mut expected: HashMap<String, String> = hashmap! {
|
||||
"Path".to_string() => "C:\\Windows\\System32".to_string(),
|
||||
"PathExt".to_string() => ".COM;.EXE;.BAT;.CMD".to_string(),
|
||||
"TEMP".to_string() => "C:\\Temp".to_string(),
|
||||
};
|
||||
expected.insert(CODEX_THREAD_ID_ENV_VAR.to_string(), thread_id.to_string());
|
||||
@@ -195,47 +192,6 @@ fn test_core_inherit_respects_case_insensitive_names_on_windows() {
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn create_env_inserts_pathext_on_windows_when_missing() {
|
||||
let vars = make_vars(&[]);
|
||||
|
||||
let policy = ShellEnvironmentPolicy {
|
||||
inherit: ShellEnvironmentPolicyInherit::None,
|
||||
ignore_default_excludes: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = create_env_from_vars(vars, &policy, /*thread_id*/ None);
|
||||
|
||||
let expected: HashMap<String, String> = hashmap! {
|
||||
"PATHEXT".to_string() => ".COM;.EXE;.BAT;.CMD".to_string(),
|
||||
};
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn create_env_preserves_existing_pathext_case_insensitively_on_windows() {
|
||||
let vars = make_vars(&[("PathExt", ".COM;.EXE;.BAT;.CMD;.PS1")]);
|
||||
|
||||
let policy = ShellEnvironmentPolicy {
|
||||
inherit: ShellEnvironmentPolicyInherit::Core,
|
||||
ignore_default_excludes: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = create_env_from_vars(vars, &policy, /*thread_id*/ None);
|
||||
|
||||
let pathext_vars = result
|
||||
.iter()
|
||||
.filter(|(key, _)| key.eq_ignore_ascii_case("PATHEXT"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(pathext_vars.len(), 1);
|
||||
assert_eq!(pathext_vars[0].1, ".COM;.EXE;.BAT;.CMD;.PS1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inherit_none() {
|
||||
let vars = make_vars(&[("PATH", "/usr/bin"), ("HOME", "/home")]);
|
||||
|
||||
@@ -274,7 +274,7 @@ pub(super) async fn run_guardian_review_session(
|
||||
let available_models = session
|
||||
.services
|
||||
.models_manager
|
||||
.list_models(codex_models_manager::manager::RefreshStrategy::Offline)
|
||||
.list_models(crate::models_manager::manager::RefreshStrategy::Offline)
|
||||
.await;
|
||||
let preferred_reasoning_effort = |supports_low: bool, fallback| {
|
||||
if supports_low {
|
||||
|
||||
@@ -34,10 +34,10 @@ use crate::config::Constrained;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::Permissions;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::rollout::recorder::RolloutRecorder;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_features::Feature;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
|
||||
use super::GUARDIAN_REVIEW_TIMEOUT;
|
||||
use super::GUARDIAN_REVIEWER_NAME;
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
// the TUI or the tracing stack).
|
||||
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
pub mod api_bridge;
|
||||
mod apply_patch;
|
||||
mod apps;
|
||||
mod arc_monitor;
|
||||
mod auth_env_telemetry;
|
||||
mod client;
|
||||
mod client_common;
|
||||
pub mod codex;
|
||||
@@ -28,6 +30,7 @@ pub mod connectors;
|
||||
mod context_manager;
|
||||
mod contextual_user_message;
|
||||
mod environment_context;
|
||||
pub mod error;
|
||||
pub mod exec;
|
||||
pub mod exec_env;
|
||||
mod exec_policy;
|
||||
@@ -43,23 +46,21 @@ pub mod landlock;
|
||||
pub mod mcp;
|
||||
mod mcp_skill_dependencies;
|
||||
mod mcp_tool_approval_templates;
|
||||
pub mod models_manager;
|
||||
mod network_policy_decision;
|
||||
pub mod network_proxy_loader;
|
||||
mod original_image_detail;
|
||||
pub use codex_mcp::mcp_connection_manager;
|
||||
pub use codex_mcp::mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY;
|
||||
pub use codex_mcp::mcp_connection_manager::MCP_SANDBOX_STATE_METHOD;
|
||||
pub use codex_mcp::mcp_connection_manager::SandboxState;
|
||||
pub use text_encoding::bytes_to_string_smart;
|
||||
mod mcp_tool_call;
|
||||
mod memories;
|
||||
pub mod mention_syntax;
|
||||
pub mod message_history;
|
||||
mod model_provider_info;
|
||||
pub mod utils;
|
||||
pub use utils::path_utils;
|
||||
pub mod personality_migration;
|
||||
pub mod plugins;
|
||||
#[doc(hidden)]
|
||||
pub mod prompt_debug;
|
||||
mod provider_auth;
|
||||
pub(crate) mod mentions {
|
||||
pub(crate) use crate::plugins::build_connector_slug_counts;
|
||||
pub(crate) use crate::plugins::build_skill_name_counts;
|
||||
@@ -94,11 +95,21 @@ pub(crate) use skills::skills_load_input_from_config;
|
||||
mod skills_watcher;
|
||||
mod stream_events_utils;
|
||||
pub mod test_support;
|
||||
mod text_encoding;
|
||||
mod unified_exec;
|
||||
pub mod windows_sandbox;
|
||||
pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
|
||||
pub use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
pub use model_provider_info::DEFAULT_LMSTUDIO_PORT;
|
||||
pub use model_provider_info::DEFAULT_OLLAMA_PORT;
|
||||
pub use model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::ModelProviderInfo;
|
||||
pub use model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
pub use model_provider_info::OPENAI_PROVIDER_ID;
|
||||
pub use model_provider_info::WireApi;
|
||||
pub use model_provider_info::built_in_model_providers;
|
||||
pub use model_provider_info::create_oss_provider_with_base_url;
|
||||
mod event_mapping;
|
||||
mod response_debug_context;
|
||||
pub mod review_format;
|
||||
pub mod review_prompts;
|
||||
mod thread_manager;
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::contextual_user_message::is_memory_excluded_contextual_user_fragment;
|
||||
use crate::error::CodexErr;
|
||||
use crate::memories::metrics;
|
||||
use crate::memories::phase_one;
|
||||
use crate::memories::phase_one::PRUNE_BATCH_SIZE;
|
||||
@@ -15,7 +16,6 @@ use codex_config::types::MemoriesConfig;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -228,7 +228,7 @@ async fn build_request_context(session: &Arc<Session>, config: &Config) -> Reque
|
||||
let model = session
|
||||
.services
|
||||
.models_manager
|
||||
.get_model_info(&model_name, &config.to_models_manager_config())
|
||||
.get_model_info(&model_name, config)
|
||||
.await;
|
||||
let turn_context = session.new_default_turn().await;
|
||||
RequestContext::from_turn_context(
|
||||
@@ -466,7 +466,7 @@ mod job {
|
||||
/// Serializes filtered stage-1 memory items for prompt inclusion.
|
||||
pub(super) fn serialize_filtered_rollout_response_items(
|
||||
items: &[RolloutItem],
|
||||
) -> codex_protocol::error::Result<String> {
|
||||
) -> crate::error::Result<String> {
|
||||
let filtered = items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use codex_models_manager::model_info::model_info_from_slug;
|
||||
use crate::models_manager::model_info::model_info_from_slug;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
use tokio::fs as tokio_fs;
|
||||
|
||||
@@ -2,11 +2,11 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::ModelClient;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use codex_api::RawMemory as ApiRawMemory;
|
||||
use codex_api::RawMemoryMetadata as ApiRawMemoryMetadata;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use serde_json::Map;
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
//! 2. User-defined entries inside `~/.codex/config.toml` under the `model_providers`
|
||||
//! key. These override or extend the defaults at runtime.
|
||||
|
||||
use crate::error::EnvVarError;
|
||||
use codex_api::Provider as ApiProvider;
|
||||
use codex_api::provider::RetryConfig as ApiRetryConfig;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_login::AuthMode;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::EnvVarError;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use http::HeaderMap;
|
||||
use http::header::HeaderName;
|
||||
use http::header::HeaderValue;
|
||||
@@ -25,7 +23,7 @@ use std::time::Duration;
|
||||
const DEFAULT_STREAM_IDLE_TIMEOUT_MS: u64 = 300_000;
|
||||
const DEFAULT_STREAM_MAX_RETRIES: u64 = 5;
|
||||
const DEFAULT_REQUEST_MAX_RETRIES: u64 = 4;
|
||||
pub const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 15_000;
|
||||
pub(crate) const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS: u64 = 15_000;
|
||||
/// Hard cap for user-configured `stream_max_retries`.
|
||||
const MAX_STREAM_MAX_RETRIES: u64 = 100;
|
||||
/// Hard cap for user-configured `request_max_retries`.
|
||||
@@ -34,8 +32,8 @@ const MAX_REQUEST_MAX_RETRIES: u64 = 100;
|
||||
const OPENAI_PROVIDER_NAME: &str = "OpenAI";
|
||||
pub const OPENAI_PROVIDER_ID: &str = "openai";
|
||||
const CHAT_WIRE_API_REMOVED_ERROR: &str = "`wire_api = \"chat\"` is no longer supported.\nHow to fix: set `wire_api = \"responses\"` in your provider config.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
pub const LEGACY_OLLAMA_CHAT_PROVIDER_ID: &str = "ollama-chat";
|
||||
pub const OLLAMA_CHAT_PROVIDER_REMOVED_ERROR: &str = "`ollama-chat` is no longer supported.\nHow to fix: replace `ollama-chat` with `ollama` in `model_provider`, `oss_provider`, or `--local-provider`.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
pub(crate) const LEGACY_OLLAMA_CHAT_PROVIDER_ID: &str = "ollama-chat";
|
||||
pub(crate) const OLLAMA_CHAT_PROVIDER_REMOVED_ERROR: &str = "`ollama-chat` is no longer supported.\nHow to fix: replace `ollama-chat` with `ollama` in `model_provider`, `oss_provider`, or `--local-provider`.\nMore info: https://github.com/openai/codex/discussions/7782";
|
||||
|
||||
/// Wire protocol that the provider speaks.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
@@ -83,48 +81,60 @@ pub struct ModelProviderInfo {
|
||||
/// Optional instructions to help the user get a valid value for the
|
||||
/// variable and set it.
|
||||
pub env_key_instructions: Option<String>,
|
||||
|
||||
/// Value to use with `Authorization: Bearer <token>` header. Use of this
|
||||
/// config is discouraged in favor of `env_key` for security reasons, but
|
||||
/// this may be necessary when using this programmatically.
|
||||
pub experimental_bearer_token: Option<String>,
|
||||
|
||||
/// Command-backed bearer-token configuration for this provider.
|
||||
pub auth: Option<ModelProviderAuthInfo>,
|
||||
|
||||
/// Which wire protocol this provider expects.
|
||||
#[serde(default)]
|
||||
pub wire_api: WireApi,
|
||||
|
||||
/// Optional query parameters to append to the base URL.
|
||||
pub query_params: Option<HashMap<String, String>>,
|
||||
|
||||
/// Additional HTTP headers to include in requests to this provider where
|
||||
/// the (key, value) pairs are the header name and value.
|
||||
pub http_headers: Option<HashMap<String, String>>,
|
||||
|
||||
/// Optional HTTP headers to include in requests to this provider where the
|
||||
/// (key, value) pairs are the header name and _environment variable_ whose
|
||||
/// value should be used. If the environment variable is not set, or the
|
||||
/// value is empty, the header will not be included in the request.
|
||||
pub env_http_headers: Option<HashMap<String, String>>,
|
||||
|
||||
/// Maximum number of times to retry a failed HTTP request to this provider.
|
||||
pub request_max_retries: Option<u64>,
|
||||
|
||||
/// Number of times to retry reconnecting a dropped streaming response before failing.
|
||||
pub stream_max_retries: Option<u64>,
|
||||
|
||||
/// Idle timeout (in milliseconds) to wait for activity on a streaming response before treating
|
||||
/// the connection as lost.
|
||||
pub stream_idle_timeout_ms: Option<u64>,
|
||||
|
||||
/// Maximum time (in milliseconds) to wait for a websocket connection attempt before treating
|
||||
/// it as failed.
|
||||
pub websocket_connect_timeout_ms: Option<u64>,
|
||||
|
||||
/// Does this provider require an OpenAI API Key or ChatGPT login token? If true,
|
||||
/// user is presented with login screen on first run, and login preference and token/key
|
||||
/// are stored in auth.json. If false (which is the default), login screen is skipped,
|
||||
/// and API key (if needed) comes from the "env_key" environment variable.
|
||||
#[serde(default)]
|
||||
pub requires_openai_auth: bool,
|
||||
|
||||
/// Whether this provider supports the Responses API WebSocket transport.
|
||||
#[serde(default)]
|
||||
pub supports_websockets: bool,
|
||||
}
|
||||
|
||||
impl ModelProviderInfo {
|
||||
pub fn validate(&self) -> std::result::Result<(), String> {
|
||||
pub(crate) fn validate(&self) -> std::result::Result<(), String> {
|
||||
let Some(auth) = self.auth.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -154,7 +164,7 @@ impl ModelProviderInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_header_map(&self) -> CodexResult<HeaderMap> {
|
||||
fn build_header_map(&self) -> crate::error::Result<HeaderMap> {
|
||||
let capacity = self.http_headers.as_ref().map_or(0, HashMap::len)
|
||||
+ self.env_http_headers.as_ref().map_or(0, HashMap::len);
|
||||
let mut headers = HeaderMap::with_capacity(capacity);
|
||||
@@ -181,7 +191,10 @@ impl ModelProviderInfo {
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
pub fn to_api_provider(&self, auth_mode: Option<AuthMode>) -> CodexResult<ApiProvider> {
|
||||
pub(crate) fn to_api_provider(
|
||||
&self,
|
||||
auth_mode: Option<AuthMode>,
|
||||
) -> crate::error::Result<ApiProvider> {
|
||||
let default_base_url = if matches!(auth_mode, Some(AuthMode::Chatgpt)) {
|
||||
"https://chatgpt.com/backend-api/codex"
|
||||
} else {
|
||||
@@ -214,14 +227,14 @@ impl ModelProviderInfo {
|
||||
/// If `env_key` is Some, returns the API key for this provider if present
|
||||
/// (and non-empty) in the environment. If `env_key` is required but
|
||||
/// cannot be found, returns an error.
|
||||
pub fn api_key(&self) -> CodexResult<Option<String>> {
|
||||
pub fn api_key(&self) -> crate::error::Result<Option<String>> {
|
||||
match &self.env_key {
|
||||
Some(env_key) => {
|
||||
let api_key = std::env::var(env_key)
|
||||
.ok()
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.ok_or_else(|| {
|
||||
CodexErr::EnvVar(EnvVarError {
|
||||
crate::error::CodexErr::EnvVar(EnvVarError {
|
||||
var: env_key.clone(),
|
||||
instructions: self.env_key_instructions.clone(),
|
||||
})
|
||||
@@ -300,7 +313,7 @@ impl ModelProviderInfo {
|
||||
self.name == OPENAI_PROVIDER_NAME
|
||||
}
|
||||
|
||||
pub fn has_command_auth(&self) -> bool {
|
||||
pub(crate) fn has_command_auth(&self) -> bool {
|
||||
self.auth.is_some()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
use codex_collaboration_mode_templates::DEFAULT as COLLABORATION_MODE_DEFAULT;
|
||||
use codex_collaboration_mode_templates::PLAN as COLLABORATION_MODE_PLAN;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
|
||||
@@ -7,6 +5,9 @@ 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_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";
|
||||
@@ -1,33 +1,33 @@
|
||||
use super::cache::ModelsCacheManager;
|
||||
use crate::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use crate::config::ModelsManagerConfig;
|
||||
use crate::model_info;
|
||||
use crate::api_bridge::auth_provider_from_auth;
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::auth_env_telemetry::AuthEnvTelemetry;
|
||||
use crate::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use crate::config::Config;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CoreResult;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets;
|
||||
use crate::models_manager::model_info;
|
||||
use crate::provider_auth::required_auth_manager_for_provider;
|
||||
use crate::response_debug_context::extract_response_debug_context;
|
||||
use crate::response_debug_context::telemetry_transport_error_message;
|
||||
use crate::util::FeedbackRequestTags;
|
||||
use crate::util::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_api::ModelsClient;
|
||||
use codex_api::RequestTelemetry;
|
||||
use codex_api::ReqwestTransport;
|
||||
use codex_api::TransportError;
|
||||
use codex_api::api_bridge::map_api_error;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_feedback::FeedbackRequestTags;
|
||||
use codex_feedback::emit_feedback_request_tags_with_auth_env;
|
||||
use codex_login::AuthEnvTelemetry;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::AuthMode;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::auth_provider_from_auth;
|
||||
use codex_login::collect_auth_env_telemetry;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_login::required_auth_manager_for_provider;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CoreResult;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_response_debug_context::extract_response_debug_context;
|
||||
use codex_response_debug_context::telemetry_transport_error_message;
|
||||
use http::HeaderMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
@@ -223,7 +223,10 @@ impl ModelsManager {
|
||||
};
|
||||
let remote_models = model_catalog
|
||||
.map(|catalog| catalog.models)
|
||||
.unwrap_or_else(|| Self::load_remote_models_from_file().unwrap_or_default());
|
||||
.unwrap_or_else(|| {
|
||||
Self::load_remote_models_from_file()
|
||||
.unwrap_or_else(|err| panic!("failed to load bundled models.json: {err}"))
|
||||
});
|
||||
Self {
|
||||
remote_models: RwLock::new(remote_models),
|
||||
catalog_mode,
|
||||
@@ -310,7 +313,7 @@ impl ModelsManager {
|
||||
// todo(aibrahim): look if we can tighten it to pub(crate)
|
||||
/// Look up model metadata, applying remote overrides and config adjustments.
|
||||
#[instrument(level = "info", skip(self, config), fields(model = model))]
|
||||
pub async fn get_model_info(&self, model: &str, config: &ModelsManagerConfig) -> ModelInfo {
|
||||
pub async fn get_model_info(&self, model: &str, config: &Config) -> ModelInfo {
|
||||
let remote_models = self.get_remote_models().await;
|
||||
Self::construct_model_info_from_candidates(model, &remote_models, config)
|
||||
}
|
||||
@@ -354,7 +357,7 @@ impl ModelsManager {
|
||||
fn construct_model_info_from_candidates(
|
||||
model: &str,
|
||||
candidates: &[ModelInfo],
|
||||
config: &ModelsManagerConfig,
|
||||
config: &Config,
|
||||
) -> ModelInfo {
|
||||
// First use the normal longest-prefix match. If that misses, allow a narrowly scoped
|
||||
// retry for namespaced slugs like `custom/gpt-5.3-codex`.
|
||||
@@ -375,7 +378,7 @@ impl ModelsManager {
|
||||
/// Refresh models if the provided ETag differs from the cached ETag.
|
||||
///
|
||||
/// Uses `Online` strategy to fetch latest models when ETags differ.
|
||||
pub async fn refresh_if_new_etag(&self, etag: String) {
|
||||
pub(crate) async fn refresh_if_new_etag(&self, etag: String) {
|
||||
let current_etag = self.get_etag().await;
|
||||
if current_etag.clone().is_some() && current_etag.as_deref() == Some(etag.as_str()) {
|
||||
if let Err(err) = self.cache_manager.renew_cache_ttl().await {
|
||||
@@ -450,7 +453,7 @@ impl ModelsManager {
|
||||
let client = ModelsClient::new(transport, api_provider, api_auth)
|
||||
.with_telemetry(Some(request_telemetry));
|
||||
|
||||
let client_version = crate::client_version_to_whole();
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
let (models, etag) = timeout(
|
||||
MODELS_REFRESH_TIMEOUT,
|
||||
client.list_models(&client_version, HeaderMap::new()),
|
||||
@@ -488,14 +491,16 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
fn load_remote_models_from_file() -> Result<Vec<ModelInfo>, std::io::Error> {
|
||||
Ok(crate::bundled_models_response()?.models)
|
||||
let file_contents = include_str!("../../models.json");
|
||||
let response: ModelsResponse = serde_json::from_str(file_contents)?;
|
||||
Ok(response.models)
|
||||
}
|
||||
|
||||
/// Attempt to satisfy the refresh from the cache when it matches the provider and TTL.
|
||||
async fn try_load_cache(&self) -> bool {
|
||||
let _timer =
|
||||
codex_otel::start_global_timer("codex.remote_models.load_cache.duration_ms", &[]);
|
||||
let client_version = crate::client_version_to_whole();
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
info!(client_version, "models cache: evaluating cache eligibility");
|
||||
let cache = match self.cache_manager.load_fresh(&client_version).await {
|
||||
Some(cache) => cache,
|
||||
@@ -537,7 +542,7 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Construct a manager with a specific provider for testing.
|
||||
pub fn with_provider_for_tests(
|
||||
pub(crate) fn with_provider_for_tests(
|
||||
codex_home: PathBuf,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
provider: ModelProviderInfo,
|
||||
@@ -552,7 +557,7 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Get model identifier without consulting remote state or cache.
|
||||
pub fn get_model_offline_for_tests(model: Option<&str>) -> String {
|
||||
pub(crate) fn get_model_offline_for_tests(model: Option<&str>) -> String {
|
||||
if let Some(model) = model {
|
||||
return model.to_string();
|
||||
}
|
||||
@@ -568,9 +573,9 @@ impl ModelsManager {
|
||||
}
|
||||
|
||||
/// Build `ModelInfo` without consulting remote state or cache.
|
||||
pub fn construct_model_info_offline_for_tests(
|
||||
pub(crate) fn construct_model_info_offline_for_tests(
|
||||
model: &str,
|
||||
config: &ModelsManagerConfig,
|
||||
config: &Config,
|
||||
) -> ModelInfo {
|
||||
let candidates: &[ModelInfo] = if let Some(model_catalog) = config.model_catalog.as_ref() {
|
||||
&model_catalog.models
|
||||
@@ -1,15 +1,14 @@
|
||||
use super::*;
|
||||
use crate::ModelsManagerConfig;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::model_provider_info::WireApi;
|
||||
use base64::Engine as _;
|
||||
use chrono::Utc;
|
||||
use codex_api::TransportError;
|
||||
use codex_login::AuthCredentialsStoreMode;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::responses::mount_models_once;
|
||||
use http::HeaderMap;
|
||||
use http::StatusCode;
|
||||
@@ -36,9 +35,6 @@ use wiremock::matchers::header_regex;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
|
||||
#[path = "model_info_overrides_tests.rs"]
|
||||
mod model_info_overrides_tests;
|
||||
|
||||
fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo {
|
||||
remote_model_with_visibility(slug, display, priority, "list")
|
||||
}
|
||||
@@ -193,7 +189,7 @@ move /y tokens.next tokens.txt >nul
|
||||
args: self.args.clone(),
|
||||
timeout_ms: NonZeroU64::new(timeout_ms).unwrap(),
|
||||
refresh_interval_ms: 60_000,
|
||||
cwd: match AbsolutePathBuf::try_from(self.tempdir.path()) {
|
||||
cwd: match codex_utils_absolute_path::AbsolutePathBuf::try_from(self.tempdir.path()) {
|
||||
Ok(cwd) => cwd,
|
||||
Err(err) => panic!("tempdir should be absolute: {err}"),
|
||||
},
|
||||
@@ -245,7 +241,11 @@ where
|
||||
#[tokio::test]
|
||||
async fn get_model_info_tracks_fallback_usage() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ModelsManagerConfig::default();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -275,7 +275,11 @@ async fn get_model_info_tracks_fallback_usage() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_uses_custom_catalog() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ModelsManagerConfig::default();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let mut overlay = remote_model("gpt-overlay", "Overlay", /*priority*/ 0);
|
||||
overlay.supports_image_detail_original = true;
|
||||
|
||||
@@ -304,7 +308,11 @@ async fn get_model_info_uses_custom_catalog() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_matches_namespaced_suffix() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ModelsManagerConfig::default();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let mut remote = remote_model("gpt-image", "Image", /*priority*/ 0);
|
||||
remote.supports_image_detail_original = true;
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
@@ -328,7 +336,11 @@ async fn get_model_info_matches_namespaced_suffix() {
|
||||
#[tokio::test]
|
||||
async fn get_model_info_rejects_multi_segment_namespace_suffix_matching() {
|
||||
let codex_home = tempdir().expect("temp dir");
|
||||
let config = ModelsManagerConfig::default();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.build()
|
||||
.await
|
||||
.expect("load default test config");
|
||||
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key"));
|
||||
let manager = ModelsManager::new(
|
||||
codex_home.path().to_path_buf(),
|
||||
@@ -581,7 +593,7 @@ async fn refresh_available_models_refetches_when_version_mismatch() {
|
||||
manager
|
||||
.cache_manager
|
||||
.mutate_cache_for_test(|cache| {
|
||||
let client_version = crate::client_version_to_whole();
|
||||
let client_version = crate::models_manager::client_version_to_whole();
|
||||
cache.client_version = Some(format!("{client_version}-mismatch"));
|
||||
})
|
||||
.await
|
||||
@@ -742,7 +754,7 @@ fn models_request_telemetry_emits_auth_env_feedback_tags_on_failure() {
|
||||
auth_mode: Some(TelemetryAuthMode::Chatgpt.to_string()),
|
||||
auth_header_attached: true,
|
||||
auth_header_name: Some("authorization"),
|
||||
auth_env: codex_login::AuthEnvTelemetry {
|
||||
auth_env: crate::auth_env_telemetry::AuthEnvTelemetry {
|
||||
openai_api_key_env_present: false,
|
||||
codex_api_key_env_present: false,
|
||||
codex_api_key_env_enabled: false,
|
||||
@@ -856,8 +868,9 @@ fn build_available_models_picks_default_after_hiding_hidden_models() {
|
||||
|
||||
#[test]
|
||||
fn bundled_models_json_roundtrips() {
|
||||
let response = crate::bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
let file_contents = include_str!("../../models.json");
|
||||
let response: ModelsResponse =
|
||||
serde_json::from_str(file_contents).expect("bundled models.json should deserialize");
|
||||
|
||||
let serialized =
|
||||
serde_json::to_string(&response).expect("bundled models.json should serialize");
|
||||
15
codex-rs/core/src/models_manager/mod.rs
Normal file
15
codex-rs/core/src/models_manager/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub mod cache;
|
||||
pub mod collaboration_mode_presets;
|
||||
pub mod manager;
|
||||
pub mod model_info;
|
||||
pub mod model_presets;
|
||||
|
||||
/// Convert the client version string to a whole version string (e.g. "1.2.3-alpha.4" -> "1.2.3").
|
||||
pub fn client_version_to_whole() -> String {
|
||||
format!(
|
||||
"{}.{}.{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH")
|
||||
)
|
||||
}
|
||||
@@ -9,18 +9,19 @@ use codex_protocol::openai_models::TruncationPolicyConfig;
|
||||
use codex_protocol::openai_models::WebSearchToolType;
|
||||
use codex_protocol::openai_models::default_input_modalities;
|
||||
|
||||
use crate::config::ModelsManagerConfig;
|
||||
use crate::config::Config;
|
||||
use codex_features::Feature;
|
||||
use codex_utils_output_truncation::approx_bytes_for_tokens;
|
||||
use tracing::warn;
|
||||
|
||||
pub const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md");
|
||||
pub const BASE_INSTRUCTIONS: &str = include_str!("../../prompt.md");
|
||||
const DEFAULT_PERSONALITY_HEADER: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
|
||||
const LOCAL_FRIENDLY_TEMPLATE: &str =
|
||||
"You optimize for team morale and being a supportive teammate as much as code quality.";
|
||||
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
|
||||
pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig) -> ModelInfo {
|
||||
pub(crate) fn with_config_overrides(mut model: ModelInfo, config: &Config) -> ModelInfo {
|
||||
if let Some(supports_reasoning_summaries) = config.model_supports_reasoning_summaries
|
||||
&& supports_reasoning_summaries
|
||||
{
|
||||
@@ -49,7 +50,7 @@ pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig)
|
||||
if let Some(base_instructions) = &config.base_instructions {
|
||||
model.base_instructions = base_instructions.clone();
|
||||
model.model_messages = None;
|
||||
} else if !config.personality_enabled {
|
||||
} else if !config.features.enabled(Feature::Personality) {
|
||||
model.model_messages = None;
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig)
|
||||
}
|
||||
|
||||
/// Build a minimal fallback model descriptor for missing/unknown slugs.
|
||||
pub fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
pub(crate) fn model_info_from_slug(slug: &str) -> ModelInfo {
|
||||
warn!("Unknown model {slug} is used. This will use fallback model metadata.");
|
||||
ModelInfo {
|
||||
slug: slug.to_string(),
|
||||
@@ -1,14 +1,12 @@
|
||||
use super::*;
|
||||
use crate::ModelsManagerConfig;
|
||||
use crate::config::test_config;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn reasoning_summaries_override_true_enables_support() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let config = ModelsManagerConfig {
|
||||
model_supports_reasoning_summaries: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(true);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
let mut expected = model;
|
||||
@@ -21,10 +19,8 @@ fn reasoning_summaries_override_true_enables_support() {
|
||||
fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
let mut model = model_info_from_slug("unknown-model");
|
||||
model.supports_reasoning_summaries = true;
|
||||
let config = ModelsManagerConfig {
|
||||
model_supports_reasoning_summaries: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
|
||||
@@ -34,10 +30,8 @@ fn reasoning_summaries_override_false_does_not_disable_support() {
|
||||
#[test]
|
||||
fn reasoning_summaries_override_false_is_noop_when_model_is_false() {
|
||||
let model = model_info_from_slug("unknown-model");
|
||||
let config = ModelsManagerConfig {
|
||||
model_supports_reasoning_summaries: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
let mut config = test_config();
|
||||
config.model_supports_reasoning_summaries = Some(false);
|
||||
|
||||
let updated = with_config_overrides(model.clone(), &config);
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
use codex_execpolicy::Decision as ExecPolicyDecision;
|
||||
use codex_execpolicy::NetworkRuleProtocol as ExecPolicyNetworkRuleProtocol;
|
||||
use codex_network_proxy::BlockedRequest;
|
||||
use codex_network_proxy::NetworkDecisionSource;
|
||||
use codex_network_proxy::NetworkPolicyDecision;
|
||||
use codex_protocol::approvals::NetworkApprovalContext;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
use codex_protocol::approvals::NetworkPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
||||
use codex_protocol::network_policy::NetworkPolicyDecisionPayload;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NetworkPolicyDecisionPayload {
|
||||
pub decision: NetworkPolicyDecision,
|
||||
pub source: NetworkDecisionSource,
|
||||
#[serde(default)]
|
||||
pub protocol: Option<NetworkApprovalProtocol>,
|
||||
pub host: Option<String>,
|
||||
pub reason: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ExecPolicyNetworkRuleAmendment {
|
||||
@@ -15,6 +28,12 @@ pub(crate) struct ExecPolicyNetworkRuleAmendment {
|
||||
pub justification: String,
|
||||
}
|
||||
|
||||
impl NetworkPolicyDecisionPayload {
|
||||
pub(crate) fn is_ask_from_decider(&self) -> bool {
|
||||
self.decision == NetworkPolicyDecision::Ask && self.source == NetworkDecisionSource::Decider
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_network_policy_decision(value: &str) -> Option<NetworkPolicyDecision> {
|
||||
match value {
|
||||
"deny" => Some(NetworkPolicyDecision::Deny),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
use codex_network_proxy::BlockedRequest;
|
||||
use codex_network_proxy::NetworkDecisionSource;
|
||||
use codex_protocol::approvals::NetworkPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkPolicyRuleAction;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::codex::Session;
|
||||
use crate::codex::build_prompt;
|
||||
use crate::codex::built_tools;
|
||||
use crate::config::Config;
|
||||
use crate::thread_manager::ThreadManager;
|
||||
|
||||
/// Build the model-visible `input` list for a single debug turn.
|
||||
#[doc(hidden)]
|
||||
pub async fn build_prompt_input(
|
||||
mut config: Config,
|
||||
input: Vec<UserInput>,
|
||||
) -> CodexResult<Vec<ResponseItem>> {
|
||||
config.ephemeral = true;
|
||||
|
||||
let auth_manager = AuthManager::shared(
|
||||
config.codex_home.clone(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
auth_manager.set_forced_chatgpt_workspace_id(config.forced_chatgpt_workspace_id.clone());
|
||||
|
||||
let thread_manager = ThreadManager::new(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
SessionSource::Exec,
|
||||
CollaborationModesConfig {
|
||||
default_mode_request_user_input: config
|
||||
.features
|
||||
.enabled(Feature::DefaultModeRequestUserInput),
|
||||
},
|
||||
Arc::new(EnvironmentManager::from_env()),
|
||||
);
|
||||
let thread = thread_manager.start_thread(config).await?;
|
||||
|
||||
let output = build_prompt_input_from_session(thread.thread.codex.session.as_ref(), input).await;
|
||||
let shutdown = thread.thread.shutdown_and_wait().await;
|
||||
let _removed = thread_manager.remove_thread(&thread.thread_id).await;
|
||||
|
||||
shutdown?;
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) async fn build_prompt_input_from_session(
|
||||
sess: &Session,
|
||||
input: Vec<UserInput>,
|
||||
) -> CodexResult<Vec<ResponseItem>> {
|
||||
let turn_context = sess.new_default_turn().await;
|
||||
sess.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
|
||||
.await;
|
||||
|
||||
if !input.is_empty() {
|
||||
let input_item = ResponseInputItem::from(input);
|
||||
let response_item = ResponseItem::from(input_item);
|
||||
sess.record_conversation_items(turn_context.as_ref(), std::slice::from_ref(&response_item))
|
||||
.await;
|
||||
}
|
||||
|
||||
let prompt_input = sess
|
||||
.clone_history()
|
||||
.await
|
||||
.for_prompt(&turn_context.model_info.input_modalities);
|
||||
let router = built_tools(
|
||||
sess,
|
||||
turn_context.as_ref(),
|
||||
&prompt_input,
|
||||
&HashSet::new(),
|
||||
Some(turn_context.turn_skills.outcome.as_ref()),
|
||||
&CancellationToken::new(),
|
||||
)
|
||||
.await?;
|
||||
let base_instructions = sess.get_base_instructions().await;
|
||||
let prompt = build_prompt(
|
||||
prompt_input,
|
||||
router.as_ref(),
|
||||
turn_context.as_ref(),
|
||||
base_instructions,
|
||||
);
|
||||
|
||||
Ok(prompt.get_formatted_input())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::config::test_config;
|
||||
|
||||
use super::build_prompt_input;
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_prompt_input_includes_context_and_user_message() {
|
||||
let codex_home = tempfile::tempdir().expect("create codex home");
|
||||
let cwd = tempfile::tempdir().expect("create cwd");
|
||||
let mut config = test_config();
|
||||
config.codex_home = codex_home.path().to_path_buf();
|
||||
config.cwd = AbsolutePathBuf::try_from(cwd.path().to_path_buf()).expect("absolute cwd");
|
||||
config.user_instructions = Some("Project-specific test instructions".to_string());
|
||||
|
||||
let input = build_prompt_input(
|
||||
config,
|
||||
vec![UserInput::Text {
|
||||
text: "hello from debug prompt".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.expect("build prompt input");
|
||||
|
||||
let expected_user_message = ResponseItem::Message {
|
||||
id: None,
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "hello from debug prompt".to_string(),
|
||||
}],
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
};
|
||||
assert_eq!(input.last(), Some(&expected_user_message));
|
||||
assert!(input.iter().any(|item| {
|
||||
let ResponseItem::Message { content, .. } = item else {
|
||||
return false;
|
||||
};
|
||||
|
||||
content.iter().any(|content_item| {
|
||||
let (ContentItem::InputText { text } | ContentItem::OutputText { text }) =
|
||||
content_item
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
text.contains("Project-specific test instructions")
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
|
||||
use crate::AuthManager;
|
||||
use crate::model_provider_info::ModelProviderInfo;
|
||||
use codex_login::AuthManager;
|
||||
|
||||
/// Returns the provider-scoped auth manager when this provider uses command-backed auth.
|
||||
///
|
||||
/// Providers without custom auth continue using the caller-supplied base manager.
|
||||
pub fn auth_manager_for_provider(
|
||||
pub(crate) fn auth_manager_for_provider(
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> Option<Arc<AuthManager>> {
|
||||
@@ -21,7 +20,7 @@ pub fn auth_manager_for_provider(
|
||||
///
|
||||
/// Providers with command-backed auth get a bearer-only manager; otherwise the caller's manager
|
||||
/// is reused unchanged.
|
||||
pub fn required_auth_manager_for_provider(
|
||||
pub(crate) fn required_auth_manager_for_provider(
|
||||
auth_manager: Arc<AuthManager>,
|
||||
provider: &ModelProviderInfo,
|
||||
) -> Arc<AuthManager> {
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::api_bridge::map_api_error;
|
||||
use crate::codex::Session;
|
||||
use crate::config::RealtimeWsMode;
|
||||
use crate::config::RealtimeWsVersion;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::realtime_context::build_realtime_startup_context;
|
||||
use async_channel::Receiver;
|
||||
use async_channel::Sender;
|
||||
@@ -14,16 +17,12 @@ use codex_api::RealtimeEventParser;
|
||||
use codex_api::RealtimeSessionConfig;
|
||||
use codex_api::RealtimeSessionMode;
|
||||
use codex_api::RealtimeWebsocketClient;
|
||||
use codex_api::api_bridge::map_api_error;
|
||||
use codex_api::endpoint::realtime_websocket::RealtimeWebsocketEvents;
|
||||
use codex_api::endpoint::realtime_websocket::RealtimeWebsocketWriter;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_login::AuthMode;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::default_headers;
|
||||
use codex_login::read_openai_api_key_from_env;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::protocol::CodexErrorInfo;
|
||||
use codex_protocol::protocol::ConversationAudioParams;
|
||||
use codex_protocol::protocol::ConversationStartParams;
|
||||
@@ -632,7 +631,10 @@ fn realtime_text_from_handoff_request(handoff: &RealtimeHandoffRequested) -> Opt
|
||||
.or((!handoff.input_transcript.is_empty()).then_some(handoff.input_transcript.clone()))
|
||||
}
|
||||
|
||||
fn realtime_api_key(auth: Option<&CodexAuth>, provider: &ModelProviderInfo) -> CodexResult<String> {
|
||||
fn realtime_api_key(
|
||||
auth: Option<&CodexAuth>,
|
||||
provider: &crate::ModelProviderInfo,
|
||||
) -> CodexResult<String> {
|
||||
if let Some(api_key) = provider.api_key()? {
|
||||
return Ok(api_key);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ const AUTH_ERROR_HEADER: &str = "x-openai-authorization-error";
|
||||
const X_ERROR_JSON_HEADER: &str = "x-error-json";
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct ResponseDebugContext {
|
||||
pub request_id: Option<String>,
|
||||
pub cf_ray: Option<String>,
|
||||
pub auth_error: Option<String>,
|
||||
pub auth_error_code: Option<String>,
|
||||
pub(crate) struct ResponseDebugContext {
|
||||
pub(crate) request_id: Option<String>,
|
||||
pub(crate) cf_ray: Option<String>,
|
||||
pub(crate) auth_error: Option<String>,
|
||||
pub(crate) auth_error_code: Option<String>,
|
||||
}
|
||||
|
||||
pub fn extract_response_debug_context(transport: &TransportError) -> ResponseDebugContext {
|
||||
pub(crate) fn extract_response_debug_context(transport: &TransportError) -> ResponseDebugContext {
|
||||
let mut context = ResponseDebugContext::default();
|
||||
|
||||
let TransportError::Http {
|
||||
@@ -53,14 +53,16 @@ pub fn extract_response_debug_context(transport: &TransportError) -> ResponseDeb
|
||||
context
|
||||
}
|
||||
|
||||
pub fn extract_response_debug_context_from_api_error(error: &ApiError) -> ResponseDebugContext {
|
||||
pub(crate) fn extract_response_debug_context_from_api_error(
|
||||
error: &ApiError,
|
||||
) -> ResponseDebugContext {
|
||||
match error {
|
||||
ApiError::Transport(transport) => extract_response_debug_context(transport),
|
||||
_ => ResponseDebugContext::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn telemetry_transport_error_message(error: &TransportError) -> String {
|
||||
pub(crate) fn telemetry_transport_error_message(error: &TransportError) -> String {
|
||||
match error {
|
||||
TransportError::Http { status, .. } => format!("http {}", status.as_u16()),
|
||||
TransportError::RetryLimit => "retry limit reached".to_string(),
|
||||
@@ -70,7 +72,7 @@ pub fn telemetry_transport_error_message(error: &TransportError) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn telemetry_api_error_message(error: &ApiError) -> String {
|
||||
pub(crate) fn telemetry_api_error_message(error: &ApiError) -> String {
|
||||
match error {
|
||||
ApiError::Transport(transport) => telemetry_transport_error_message(transport),
|
||||
ApiError::Api { status, .. } => format!("api error {}", status.as_u16()),
|
||||
@@ -9,6 +9,7 @@ ExecRequest for execution.
|
||||
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecExpiration;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::exec::StdoutStream;
|
||||
use crate::exec::WindowsRestrictedTokenFilesystemOverlay;
|
||||
use crate::exec::execute_exec_request;
|
||||
@@ -17,7 +18,6 @@ use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
pub use codex_protocol::models::SandboxPermissions;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -140,7 +140,7 @@ impl ExecRequest {
|
||||
pub async fn execute_env(
|
||||
exec_request: ExecRequest,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> codex_protocol::error::Result<ExecToolCallOutput> {
|
||||
) -> crate::error::Result<ExecToolCallOutput> {
|
||||
execute_exec_request(exec_request, stdout_stream, /*after_spawn*/ None).await
|
||||
}
|
||||
|
||||
@@ -148,6 +148,6 @@ pub async fn execute_exec_request_with_after_spawn(
|
||||
exec_request: ExecRequest,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> codex_protocol::error::Result<ExecToolCallOutput> {
|
||||
) -> crate::error::Result<ExecToolCallOutput> {
|
||||
execute_exec_request(exec_request, stdout_stream, after_spawn).await
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::error::CodexErr;
|
||||
use crate::rollout::SESSIONS_SUBDIR;
|
||||
use codex_protocol::error::CodexErr;
|
||||
|
||||
pub(crate) fn map_session_init_error(err: &anyhow::Error, codex_home: &Path) -> CodexErr {
|
||||
if let Some(mapped) = err
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::codex::INITIAL_SUBMIT_ID;
|
||||
use crate::codex::Session;
|
||||
use crate::codex::build_prompt;
|
||||
use crate::codex::built_tools;
|
||||
use crate::error::Result as CodexResult;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::metrics::names::STARTUP_PREWARM_AGE_AT_FIRST_TURN_METRIC;
|
||||
use codex_otel::metrics::names::STARTUP_PREWARM_DURATION_METRIC;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
|
||||
pub(crate) struct SessionStartupPrewarmHandle {
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::client::ModelClient;
|
||||
use crate::config::StartedNetworkProxy;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::tools::code_mode::CodeModeService;
|
||||
@@ -18,7 +19,6 @@ use codex_exec_server::Environment;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_login::AuthManager;
|
||||
use codex_mcp::mcp_connection_manager::McpConnectionManager;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -12,14 +12,14 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::memories::citations::get_thread_id_from_citations;
|
||||
use crate::memories::citations::parse_memory_citation;
|
||||
use crate::parse_turn_item;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::image_generation_artifact_path;
|
||||
use super::last_assistant_message_from_item;
|
||||
use super::save_image_generation_result;
|
||||
use crate::codex::make_session_and_context;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use crate::error::CodexErr;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
|
||||
@@ -27,11 +27,11 @@ use crate::hook_runtime::PendingInputHookDisposition;
|
||||
use crate::hook_runtime::inspect_pending_input;
|
||||
use crate::hook_runtime::record_additional_contexts;
|
||||
use crate::hook_runtime::record_pending_input;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::state::ActiveTurn;
|
||||
use crate::state::RunningTask;
|
||||
use crate::state::TaskKind;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::metrics::names::TURN_E2E_DURATION_METRIC;
|
||||
use codex_otel::metrics::names::TURN_NETWORK_PROXY_METRIC;
|
||||
|
||||
@@ -10,7 +10,9 @@ use uuid::Uuid;
|
||||
|
||||
use crate::codex::TurnContext;
|
||||
use crate::exec::ExecCapturePolicy;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::exec::StdoutStream;
|
||||
use crate::exec::StreamOutput;
|
||||
use crate::exec::execute_exec_request;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::sandboxing::ExecRequest;
|
||||
@@ -18,8 +20,6 @@ use crate::state::TaskKind;
|
||||
use crate::tools::format_exec_output_str;
|
||||
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
|
||||
use crate::user_shell_command::user_shell_command_record_item;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::exec_output::StreamOutput;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||
use codex_protocol::protocol::ExecCommandEndEvent;
|
||||
|
||||
@@ -8,24 +8,25 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_models_manager::collaboration_mode_presets;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::ThreadManager;
|
||||
use crate::config::Config;
|
||||
use crate::models_manager::collaboration_mode_presets;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::thread_manager;
|
||||
use crate::unified_exec;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
|
||||
static TEST_MODEL_PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
|
||||
let mut response = bundled_models_response()
|
||||
let file_contents = include_str!("../models.json");
|
||||
let mut response: ModelsResponse = serde_json::from_str(file_contents)
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
response.models.sort_by(|a, b| a.priority.cmp(&b.priority));
|
||||
let mut presets: Vec<ModelPreset> = response.models.into_iter().map(Into::into).collect();
|
||||
@@ -74,7 +75,7 @@ pub async fn start_thread_with_user_shell_override(
|
||||
thread_manager: &ThreadManager,
|
||||
config: Config,
|
||||
user_shell_override: crate::shell::Shell,
|
||||
) -> codex_protocol::error::Result<crate::NewThread> {
|
||||
) -> crate::error::Result<crate::NewThread> {
|
||||
thread_manager
|
||||
.start_thread_with_user_shell_override_for_tests(config, user_shell_override)
|
||||
.await
|
||||
@@ -86,7 +87,7 @@ pub async fn resume_thread_from_rollout_with_user_shell_override(
|
||||
rollout_path: PathBuf,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
user_shell_override: crate::shell::Shell,
|
||||
) -> codex_protocol::error::Result<crate::NewThread> {
|
||||
) -> crate::error::Result<crate::NewThread> {
|
||||
thread_manager
|
||||
.resume_thread_from_rollout_with_user_shell_override_for_tests(
|
||||
config,
|
||||
@@ -110,7 +111,7 @@ pub fn get_model_offline(model: Option<&str>) -> String {
|
||||
}
|
||||
|
||||
pub fn construct_model_info_offline(model: &str, config: &Config) -> ModelInfo {
|
||||
ModelsManager::construct_model_info_offline_for_tests(model, &config.to_models_manager_config())
|
||||
ModelsManager::construct_model_info_offline_for_tests(model, config)
|
||||
}
|
||||
|
||||
pub fn all_model_presets() -> &'static Vec<ModelPreset> {
|
||||
|
||||
@@ -10,54 +10,6 @@ use chardetng::EncodingDetector;
|
||||
use encoding_rs::Encoding;
|
||||
use encoding_rs::IBM866;
|
||||
use encoding_rs::WINDOWS_1252;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamOutput<T: Clone> {
|
||||
pub text: T,
|
||||
pub truncated_after_lines: Option<u32>,
|
||||
}
|
||||
|
||||
impl StreamOutput<String> {
|
||||
pub fn new(text: String) -> Self {
|
||||
Self {
|
||||
text,
|
||||
truncated_after_lines: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamOutput<Vec<u8>> {
|
||||
pub fn from_utf8_lossy(&self) -> StreamOutput<String> {
|
||||
StreamOutput {
|
||||
text: bytes_to_string_smart(&self.text),
|
||||
truncated_after_lines: self.truncated_after_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecToolCallOutput {
|
||||
pub exit_code: i32,
|
||||
pub stdout: StreamOutput<String>,
|
||||
pub stderr: StreamOutput<String>,
|
||||
pub aggregated_output: StreamOutput<String>,
|
||||
pub duration: Duration,
|
||||
pub timed_out: bool,
|
||||
}
|
||||
|
||||
impl Default for ExecToolCallOutput {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exit_code: 0,
|
||||
stdout: StreamOutput::new(String::new()),
|
||||
stderr: StreamOutput::new(String::new()),
|
||||
aggregated_output: StreamOutput::new(String::new()),
|
||||
duration: Duration::ZERO,
|
||||
timed_out: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to convert arbitrary bytes to UTF-8 with best-effort encoding detection.
|
||||
pub fn bytes_to_string_smart(bytes: &[u8]) -> String {
|
||||
@@ -134,7 +86,7 @@ fn decode_bytes(bytes: &[u8], encoding: &'static Encoding) -> String {
|
||||
/// `“test”` into unreadable Cyrillic. To avoid that, we treat inputs comprising a handful of bytes
|
||||
/// from the problematic range plus ASCII letters as CP1252 punctuation. We deliberately do *not*
|
||||
/// cap how many of those punctuation bytes we accept: VS Code frequently prints several quoted
|
||||
/// phrases (e.g., `"foo" - "bar"`), and truncating the count would once again mis-decode those as
|
||||
/// phrases (e.g., `"foo" – "bar"`), and truncating the count would once again mis-decode those as
|
||||
/// Cyrillic. If we discover additional encodings with overlapping byte ranges, prefer adding
|
||||
/// encoding-specific byte allowlists like `WINDOWS_1252_PUNCT` and tests that exercise real-world
|
||||
/// shell snippets.
|
||||
@@ -165,5 +117,5 @@ fn is_windows_1252_punct(byte: u8) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "exec_output_tests.rs"]
|
||||
#[path = "text_encoding_tests.rs"]
|
||||
mod tests;
|
||||
340
codex-rs/core/src/text_encoding_tests.rs
Normal file
340
codex-rs/core/src/text_encoding_tests.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
use super::*;
|
||||
use encoding_rs::BIG5;
|
||||
use encoding_rs::EUC_KR;
|
||||
use encoding_rs::GBK;
|
||||
use encoding_rs::ISO_8859_2;
|
||||
use encoding_rs::ISO_8859_3;
|
||||
use encoding_rs::ISO_8859_4;
|
||||
use encoding_rs::ISO_8859_5;
|
||||
use encoding_rs::ISO_8859_6;
|
||||
use encoding_rs::ISO_8859_7;
|
||||
use encoding_rs::ISO_8859_8;
|
||||
use encoding_rs::ISO_8859_10;
|
||||
use encoding_rs::ISO_8859_13;
|
||||
use encoding_rs::SHIFT_JIS;
|
||||
use encoding_rs::WINDOWS_874;
|
||||
use encoding_rs::WINDOWS_1250;
|
||||
use encoding_rs::WINDOWS_1251;
|
||||
use encoding_rs::WINDOWS_1253;
|
||||
use encoding_rs::WINDOWS_1254;
|
||||
use encoding_rs::WINDOWS_1255;
|
||||
use encoding_rs::WINDOWS_1256;
|
||||
use encoding_rs::WINDOWS_1257;
|
||||
use encoding_rs::WINDOWS_1258;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_utf8_passthrough() {
|
||||
// Fast path: when UTF-8 is valid we should avoid copies and return as-is.
|
||||
let utf8_text = "Hello, мир! 世界";
|
||||
let bytes = utf8_text.as_bytes();
|
||||
assert_eq!(bytes_to_string_smart(bytes), utf8_text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp1251_russian_text() {
|
||||
// Cyrillic text emitted by PowerShell/WSL in CP1251 should decode cleanly.
|
||||
let bytes = b"\xEF\xF0\xE8\xEC\xE5\xF0"; // "пример" encoded with Windows-1251
|
||||
assert_eq!(bytes_to_string_smart(bytes), "пример");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp1251_privet_word() {
|
||||
// Regression: CP1251 words like "Привет" must not be mis-identified as Windows-1252.
|
||||
let bytes = b"\xCF\xF0\xE8\xE2\xE5\xF2"; // "Привет" encoded with Windows-1251
|
||||
assert_eq!(bytes_to_string_smart(bytes), "Привет");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_koi8_r_privet_word() {
|
||||
// KOI8-R output should decode to the original Cyrillic as well.
|
||||
let bytes = b"\xF0\xD2\xC9\xD7\xC5\xD4"; // "Привет" encoded with KOI8-R
|
||||
assert_eq!(bytes_to_string_smart(bytes), "Привет");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp866_russian_text() {
|
||||
// Legacy consoles (cmd.exe) commonly emit CP866 bytes for Cyrillic content.
|
||||
let bytes = b"\xAF\xE0\xA8\xAC\xA5\xE0"; // "пример" encoded with CP866
|
||||
assert_eq!(bytes_to_string_smart(bytes), "пример");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp866_uppercase_text() {
|
||||
// Ensure the IBM866 heuristic still returns IBM866 for uppercase-only words.
|
||||
let bytes = b"\x8F\x90\x88"; // "ПРИ" encoded with CP866 uppercase letters
|
||||
assert_eq!(bytes_to_string_smart(bytes), "ПРИ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp866_uppercase_followed_by_ascii() {
|
||||
// Regression test: uppercase CP866 tokens next to ASCII text should not be treated as
|
||||
// CP1252.
|
||||
let bytes = b"\x8F\x90\x88 test"; // "ПРИ test" encoded with CP866 uppercase letters followed by ASCII
|
||||
assert_eq!(bytes_to_string_smart(bytes), "ПРИ test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1252_quotes() {
|
||||
// Smart detection should map Windows-1252 punctuation into proper Unicode.
|
||||
let bytes = b"\x93\x94test";
|
||||
assert_eq!(bytes_to_string_smart(bytes), "\u{201C}\u{201D}test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1252_multiple_quotes() {
|
||||
// Longer snippets of punctuation (e.g., “foo” – “bar”) should still flip to CP1252.
|
||||
let bytes = b"\x93foo\x94 \x96 \x93bar\x94";
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(bytes),
|
||||
"\u{201C}foo\u{201D} \u{2013} \u{201C}bar\u{201D}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1252_privet_gibberish_is_preserved() {
|
||||
// Windows-1252 cannot encode Cyrillic; if the input literally contains "ПÑ..." we should not "fix" it.
|
||||
let bytes = "Привет".as_bytes();
|
||||
assert_eq!(bytes_to_string_smart(bytes), "Привет");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_1_latin_text() {
|
||||
// ISO-8859-1 (code page 28591) is the Latin segment used by LatArCyrHeb.
|
||||
// encoding_rs unifies ISO-8859-1 with Windows-1252, so reuse that constant here.
|
||||
let (encoded, _, had_errors) = WINDOWS_1252.encode("Hello");
|
||||
assert!(!had_errors, "failed to encode Latin sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_2_central_european_text() {
|
||||
// ISO-8859-2 (code page 28592) covers additional Central European glyphs.
|
||||
let (encoded, _, had_errors) = ISO_8859_2.encode("Příliš žluťoučký kůň");
|
||||
assert!(!had_errors, "failed to encode ISO-8859-2 sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"Příliš žluťoučký kůň"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_3_south_europe_text() {
|
||||
// ISO-8859-3 (code page 28593) adds support for Maltese/Esperanto letters.
|
||||
// chardetng rarely distinguishes ISO-8859-3 from neighboring Latin code pages, so we rely on
|
||||
// an ASCII-only sample to ensure round-tripping still succeeds.
|
||||
let (encoded, _, had_errors) = ISO_8859_3.encode("Esperanto and Maltese");
|
||||
assert!(!had_errors, "failed to encode ISO-8859-3 sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"Esperanto and Maltese"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_4_baltic_text() {
|
||||
// ISO-8859-4 (code page 28594) targets the Baltic/Nordic repertoire.
|
||||
let sample = "Šis ir rakstzīmju kodēšanas tests. Dažās valodās, kurās tiek \
|
||||
izmantotas latīņu valodas burti, lēmuma pieņemšanai mums ir nepieciešams \
|
||||
vairāk ieguldījuma.";
|
||||
let (encoded, _, had_errors) = ISO_8859_4.encode(sample);
|
||||
assert!(!had_errors, "failed to encode ISO-8859-4 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), sample);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_5_cyrillic_text() {
|
||||
// ISO-8859-5 (code page 28595) covers the Cyrillic portion.
|
||||
let (encoded, _, had_errors) = ISO_8859_5.encode("Привет");
|
||||
assert!(!had_errors, "failed to encode Cyrillic sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Привет");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_6_arabic_text() {
|
||||
// ISO-8859-6 (code page 28596) covers the Arabic glyphs.
|
||||
let (encoded, _, had_errors) = ISO_8859_6.encode("مرحبا");
|
||||
assert!(!had_errors, "failed to encode Arabic sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "مرحبا");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_7_greek_text() {
|
||||
// ISO-8859-7 (code page 28597) is used for Greek locales.
|
||||
let (encoded, _, had_errors) = ISO_8859_7.encode("Καλημέρα");
|
||||
assert!(!had_errors, "failed to encode ISO-8859-7 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Καλημέρα");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_8_hebrew_text() {
|
||||
// ISO-8859-8 (code page 28598) covers the Hebrew glyphs.
|
||||
let (encoded, _, had_errors) = ISO_8859_8.encode("שלום");
|
||||
assert!(!had_errors, "failed to encode Hebrew sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "שלום");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_9_turkish_text() {
|
||||
// ISO-8859-9 (code page 28599) mirrors Latin-1 but inserts Turkish letters.
|
||||
// encoding_rs exposes the equivalent Windows-1254 mapping.
|
||||
let (encoded, _, had_errors) = WINDOWS_1254.encode("İstanbul");
|
||||
assert!(!had_errors, "failed to encode ISO-8859-9 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "İstanbul");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_10_nordic_text() {
|
||||
// ISO-8859-10 (code page 28600) adds additional Nordic letters.
|
||||
let sample = "Þetta er prófun fyrir Ægir og Øystein.";
|
||||
let (encoded, _, had_errors) = ISO_8859_10.encode(sample);
|
||||
assert!(!had_errors, "failed to encode ISO-8859-10 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), sample);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_11_thai_text() {
|
||||
// ISO-8859-11 (code page 28601) mirrors TIS-620 / Windows-874 for Thai.
|
||||
let sample = "ภาษาไทยสำหรับการทดสอบ ISO-8859-11";
|
||||
// encoding_rs exposes the equivalent Windows-874 encoding, so use that constant.
|
||||
let (encoded, _, had_errors) = WINDOWS_874.encode(sample);
|
||||
assert!(!had_errors, "failed to encode ISO-8859-11 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), sample);
|
||||
}
|
||||
|
||||
// ISO-8859-12 was never standardized, and encodings 14–16 cannot be distinguished reliably
|
||||
// without the heuristics we removed (chardetng generally reports neighboring Latin pages), so
|
||||
// we intentionally omit coverage for those slots until the detector can identify them.
|
||||
|
||||
#[test]
|
||||
fn test_iso8859_13_baltic_text() {
|
||||
// ISO-8859-13 (code page 28603) is common across Baltic languages.
|
||||
let (encoded, _, had_errors) = ISO_8859_13.encode("Sveiki");
|
||||
assert!(!had_errors, "failed to encode ISO-8859-13 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Sveiki");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1250_central_european_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1250.encode("Příliš žluťoučký kůň");
|
||||
assert!(!had_errors, "failed to encode Central European sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"Příliš žluťoučký kůň"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1251_encoded_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1251.encode("Привет из Windows-1251");
|
||||
assert!(!had_errors, "failed to encode Windows-1251 sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"Привет из Windows-1251"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1253_greek_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1253.encode("Γειά σου");
|
||||
assert!(!had_errors, "failed to encode Greek sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Γειά σου");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1254_turkish_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1254.encode("İstanbul");
|
||||
assert!(!had_errors, "failed to encode Turkish sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "İstanbul");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1255_hebrew_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1255.encode("שלום");
|
||||
assert!(!had_errors, "failed to encode Windows-1255 Hebrew sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "שלום");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1256_arabic_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1256.encode("مرحبا");
|
||||
assert!(!had_errors, "failed to encode Windows-1256 Arabic sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "مرحبا");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1257_baltic_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1257.encode("Pērkons");
|
||||
assert!(!had_errors, "failed to encode Baltic sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Pērkons");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_1258_vietnamese_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_1258.encode("Xin chào");
|
||||
assert!(!had_errors, "failed to encode Vietnamese sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "Xin chào");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_874_thai_text() {
|
||||
let (encoded, _, had_errors) = WINDOWS_874.encode("สวัสดีครับ นี่คือการทดสอบภาษาไทย");
|
||||
assert!(!had_errors, "failed to encode Thai sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"สวัสดีครับ นี่คือการทดสอบภาษาไทย"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_932_shift_jis_text() {
|
||||
let (encoded, _, had_errors) = SHIFT_JIS.encode("こんにちは");
|
||||
assert!(!had_errors, "failed to encode Shift-JIS sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "こんにちは");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_936_gbk_text() {
|
||||
let (encoded, _, had_errors) = GBK.encode("你好,世界,这是一个测试");
|
||||
assert!(!had_errors, "failed to encode GBK sample");
|
||||
assert_eq!(
|
||||
bytes_to_string_smart(encoded.as_ref()),
|
||||
"你好,世界,这是一个测试"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_949_korean_text() {
|
||||
let (encoded, _, had_errors) = EUC_KR.encode("안녕하세요");
|
||||
assert!(!had_errors, "failed to encode Korean sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "안녕하세요");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_windows_950_big5_text() {
|
||||
let (encoded, _, had_errors) = BIG5.encode("繁體");
|
||||
assert!(!had_errors, "failed to encode Big5 sample");
|
||||
assert_eq!(bytes_to_string_smart(encoded.as_ref()), "繁體");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_latin1_cafe() {
|
||||
// Latin-1 bytes remain common in Western-European locales; decode them directly.
|
||||
let bytes = b"caf\xE9"; // codespell:ignore caf
|
||||
assert_eq!(bytes_to_string_smart(bytes), "café");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preserves_ansi_sequences() {
|
||||
// ANSI escape sequences should survive regardless of the detected encoding.
|
||||
let bytes = b"\x1b[31mred\x1b[0m";
|
||||
assert_eq!(bytes_to_string_smart(bytes), "\x1b[31mred\x1b[0m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fallback_to_lossy() {
|
||||
// Completely invalid sequences fall back to the old lossy behavior.
|
||||
let invalid_bytes = [0xFF, 0xFE, 0xFD];
|
||||
let result = bytes_to_string_smart(&invalid_bytes);
|
||||
assert_eq!(result, String::from_utf8_lossy(&invalid_bytes));
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::ModelProviderInfo;
|
||||
use crate::OPENAI_PROVIDER_ID;
|
||||
use crate::SkillsManager;
|
||||
use crate::agent::AgentControl;
|
||||
use crate::codex::Codex;
|
||||
@@ -6,8 +8,12 @@ use crate::codex::CodexSpawnOk;
|
||||
use crate::codex::INITIAL_SUBMIT_ID;
|
||||
use crate::codex_thread::CodexThread;
|
||||
use crate::config::Config;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::rollout::truncation;
|
||||
@@ -20,15 +26,8 @@ use codex_app_server_protocol::TurnStatus;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::OPENAI_PROVIDER_ID;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_models_manager::manager::ModelsManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -357,7 +356,10 @@ impl ThreadManager {
|
||||
self.state.models_manager.clone()
|
||||
}
|
||||
|
||||
pub async fn list_models(&self, refresh_strategy: RefreshStrategy) -> Vec<ModelPreset> {
|
||||
pub async fn list_models(
|
||||
&self,
|
||||
refresh_strategy: crate::models_manager::manager::RefreshStrategy,
|
||||
) -> Vec<ModelPreset> {
|
||||
self.state
|
||||
.models_manager
|
||||
.list_models(refresh_strategy)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::*;
|
||||
use crate::codex::make_session_and_context;
|
||||
use crate::config::test_config;
|
||||
use crate::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::rollout::RolloutRecorder;
|
||||
use crate::tasks::interrupted_turn_history_marker;
|
||||
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ReasoningItemReasoningSummary;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::SandboxErr;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user