Compare commits

..

18 Commits

Author SHA1 Message Date
Eric Traut
e89966159b Merge commit 'dedd1c386a9bb2b4031d26b0494217a20868fb7a' into etraut/loop
# Conflicts:
#	codex-rs/tui/src/status/card.rs
2026-03-31 18:55:49 -06:00
Eric Traut
eaaedfcdc1 Merge commit 'ae057e0bb9f3cde31c54fcca59a78456f7805282' into etraut/loop 2026-03-31 17:20:49 -06:00
Eric Traut
c8a5fb70b7 codex: address PR review feedback (#16282) 2026-03-31 17:04:45 -06:00
Eric Traut
24b097484d Merge branch 'main' into etraut/loop 2026-03-31 16:32:35 -06:00
Eric Traut
7bca5b7a06 codex: address PR review feedback (#16282) 2026-03-31 16:20:39 -06:00
Eric Traut
a8fa8ef41b codex: address PR review feedback (#16282) 2026-03-31 14:57:25 -06:00
Eric Traut
66aef68ceb Restore unrelated snapshot files 2026-03-31 14:52:23 -06:00
Eric Traut
320fd6275c codex: address PR review feedback (#16282) 2026-03-31 13:01:59 -06:00
Eric Traut
d6e6a6f1be Merge main into etraut/loop 2026-03-31 12:12:51 -06:00
Eric Traut
d5e526be1d codex: address PR review feedback (#16282) 2026-03-31 11:45:52 -06:00
Eric Traut
c022ab7ac8 codex: address PR review feedback (#16282) 2026-03-31 00:45:00 -06:00
Eric Traut
c149b699e3 codex: address PR review feedback (#16282) 2026-03-31 00:13:06 -06:00
Eric Traut
c709d0d5e6 codex: address PR review feedback (#16282) 2026-03-30 21:43:23 -06:00
Eric Traut
50ffa4e0c5 codex: fix argument comment lint on PR #16282 2026-03-30 17:38:55 -06:00
Eric Traut
757cfdb485 codex: fix blob-size-policy on PR #16282 2026-03-30 17:31:06 -06:00
Eric Traut
4721da7f80 Merge origin/main into etraut/loop 2026-03-30 17:24:10 -06:00
Eric Traut
1f771bea15 Fix thread job prompt handling and popup UI 2026-03-30 16:47:01 -06:00
Eric Traut
a02c2cd50c Add experimental thread job scheduler for /loop 2026-03-30 15:30:34 -06:00
882 changed files with 20244 additions and 41331 deletions

View File

@@ -44,7 +44,6 @@ common --remote_timeout=3600
common --noexperimental_throttle_remote_action_building
common --experimental_remote_execution_keepalive
common --grpc_keepalive_time=30s
common --experimental_remote_downloader=grpcs://remote.buildbuddy.io
# This limits both in-flight executions and concurrent downloads. Even with high number
# of jobs execution will still be limited by CPU cores, so this just pays a bit of
@@ -125,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.

View File

@@ -1,48 +0,0 @@
---
name: codex-bug
description: Diagnose GitHub bug reports in openai/codex. Use when given a GitHub issue URL from openai/codex and asked to decide next steps such as verifying against the repo, requesting more info, or explaining why it is not a bug; follow any additional user-provided instructions.
---
# Codex Bug
## Overview
Diagnose a Codex GitHub bug report and decide the next action: verify against sources, request more info, or explain why it is not a bug.
## Workflow
1. Confirm the input
- Require a GitHub issue URL that points to `github.com/openai/codex/issues/…`.
- If the URL is missing or not in the right repo, ask the user for the correct link.
2. Network access
- Always access the issue over the network immediately, even if you think access is blocked or unavailable.
- Prefer the GitHub API over HTML pages because the HTML is noisy:
- Issue: `https://api.github.com/repos/openai/codex/issues/<number>`
- Comments: `https://api.github.com/repos/openai/codex/issues/<number>/comments`
- If the environment requires explicit approval, request it on demand via the tool and continue without additional user prompting.
- Only if the network attempt fails after requesting approval, explain what you can do offline (e.g., draft a response template) and ask how to proceed.
3. Read the issue
- Use the GitHub API responses (issue + comments) as the source of truth rather than scraping the HTML issue page.
- Extract: title, body, repro steps, expected vs actual, environment, logs, and any attachments.
- Note whether the report already includes logs or session details.
- If the report includes a thread ID, mention it in the summary and use it to look up the logs and session details if you have access to them.
4. Summarize the bug before investigating
- Before inspecting code, docs, or logs in depth, write a short summary of the report in your own words.
- Include the reported behavior, expected behavior, repro steps, environment, and what evidence is already attached or missing.
5. Decide the course of action
- **Verify with sources** when the report is specific and likely reproducible. Inspect relevant Codex files (or mention the files to inspect if access is unavailable).
- **Request more information** when the report is vague, missing repro steps, or lacks logs/environment.
- **Explain not a bug** when the report contradicts current behavior or documented constraints (cite the evidence from the issue and any local sources you checked).
6. Respond
- Provide a concise report of your findings and next steps.

View File

@@ -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'

View File

@@ -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.
@@ -172,12 +146,6 @@ if [[ -n "${BAZEL_REPOSITORY_CACHE:-}" ]]; then
post_config_bazel_args+=("--repository_cache=${BAZEL_REPOSITORY_CACHE}")
fi
if [[ -n "${CODEX_BAZEL_EXECUTION_LOG_COMPACT_DIR:-}" ]]; then
post_config_bazel_args+=(
"--execution_log_compact_file=${CODEX_BAZEL_EXECUTION_LOG_COMPACT_DIR}/execution-log-${bazel_args[0]}-${GITHUB_JOB:-local}-$$.zst"
)
fi
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
windows_action_env_vars=(
INCLUDE

View File

@@ -1,12 +1,11 @@
#!/usr/bin/env python3
"""Verify that codex-rs Cargo manifests follow workspace manifest policy.
"""Verify that codex-rs crates inherit workspace metadata, lints, and names.
Checks:
- Crates inherit `[workspace.package]` metadata.
- Crates opt into `[lints] workspace = true`.
- Crate names follow the codex-rs directory naming conventions.
- Workspace manifests do not introduce workspace crate feature toggles.
This keeps `cargo clippy` aligned with the workspace lint policy by ensuring
each crate opts into `[lints] workspace = true`, and it also checks the crate
name conventions for top-level `codex-rs/*` crates and `codex-rs/utils/*`
crates.
"""
from __future__ import annotations
@@ -25,48 +24,20 @@ TOP_LEVEL_NAME_EXCEPTIONS = {
UTILITY_NAME_EXCEPTIONS = {
"path-utils": "codex-utils-path",
}
MANIFEST_FEATURE_EXCEPTIONS = {}
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}
def main() -> int:
internal_package_names = workspace_package_names()
used_manifest_feature_exceptions: set[str] = set()
used_optional_dependency_exceptions: set[tuple[str, str, str]] = set()
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]] = set()
failures_by_path: dict[str, list[str]] = {}
for path in manifests_to_verify():
if errors := manifest_errors(
path,
internal_package_names,
used_manifest_feature_exceptions,
used_optional_dependency_exceptions,
used_internal_dependency_feature_exceptions,
):
failures_by_path[manifest_key(path)] = errors
add_unused_exception_errors(
failures_by_path,
used_manifest_feature_exceptions,
used_optional_dependency_exceptions,
used_internal_dependency_feature_exceptions,
)
if not failures_by_path:
failures = [
(path.relative_to(ROOT), errors)
for path in cargo_manifests()
if (errors := manifest_errors(path))
]
if not failures:
return 0
print(
"Cargo manifests under codex-rs must inherit workspace package metadata, "
"opt into workspace lints, and avoid introducing new workspace crate "
"features."
)
print(
"Workspace crate features are disallowed because our Bazel build setup "
"does not honor them today, which can let issues hidden behind feature "
"gates go unnoticed, and because they add extra crate build "
"permutations we want to avoid."
"Cargo manifests under codex-rs must inherit workspace package metadata and "
"opt into workspace lints."
)
print(
"Cargo only applies `codex-rs/Cargo.toml` `[workspace.lints.clippy]` "
@@ -85,13 +56,8 @@ def main() -> int:
"Package-name checks apply to `codex-rs/<crate>/Cargo.toml` and "
"`codex-rs/utils/<crate>/Cargo.toml`."
)
print(
"Workspace crate features are forbidden; add a targeted exception here "
"only if there is a deliberate temporary migration in flight."
)
print()
for path in sorted(failures_by_path):
errors = failures_by_path[path]
for path, errors in failures:
print(f"{path}:")
for error in errors:
print(f" - {error}")
@@ -99,106 +65,28 @@ def main() -> int:
return 1
def manifest_errors(
path: Path,
internal_package_names: set[str],
used_manifest_feature_exceptions: set[str],
used_optional_dependency_exceptions: set[tuple[str, str, str]],
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]],
) -> list[str]:
def manifest_errors(path: Path) -> list[str]:
manifest = load_manifest(path)
package = manifest.get("package")
if not isinstance(package, dict) and path != CARGO_RS_ROOT / "Cargo.toml":
if not isinstance(package, dict):
return []
errors = []
if isinstance(package, dict):
for field in WORKSPACE_PACKAGE_FIELDS:
if not is_workspace_reference(package.get(field)):
errors.append(f"set `{field}.workspace = true` in `[package]`")
for field in WORKSPACE_PACKAGE_FIELDS:
if not is_workspace_reference(package.get(field)):
errors.append(f"set `{field}.workspace = true` in `[package]`")
lints = manifest.get("lints")
if not (isinstance(lints, dict) and lints.get("workspace") is True):
errors.append("add `[lints]` with `workspace = true`")
lints = manifest.get("lints")
if not (isinstance(lints, dict) and lints.get("workspace") is True):
errors.append("add `[lints]` with `workspace = true`")
expected_name = expected_package_name(path)
if expected_name is not None:
actual_name = package.get("name")
if actual_name != expected_name:
errors.append(
f"set `[package].name` to `{expected_name}` (found `{actual_name}`)"
)
path_key = manifest_key(path)
features = manifest.get("features")
if features is not None:
normalized_features = normalize_feature_mapping(features)
expected_features = MANIFEST_FEATURE_EXCEPTIONS.get(path_key)
if expected_features is None:
expected_name = expected_package_name(path)
if expected_name is not None:
actual_name = package.get("name")
if actual_name != expected_name:
errors.append(
"remove `[features]`; new workspace crate features are not allowed"
f"set `[package].name` to `{expected_name}` (found `{actual_name}`)"
)
else:
used_manifest_feature_exceptions.add(path_key)
if normalized_features != expected_features:
errors.append(
"limit `[features]` to the existing exception list while "
"workspace crate features are being removed "
f"(expected {render_feature_mapping(expected_features)})"
)
for section_name, dependencies in dependency_sections(manifest):
for dependency_name, dependency in dependencies.items():
if not isinstance(dependency, dict):
continue
if dependency.get("optional") is True:
exception_key = (path_key, section_name, dependency_name)
if exception_key in OPTIONAL_DEPENDENCY_EXCEPTIONS:
used_optional_dependency_exceptions.add(exception_key)
else:
errors.append(
"remove `optional = true` from "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new optional dependencies are not allowed because they "
"create crate features"
)
if not is_internal_dependency(path, dependency_name, dependency, internal_package_names):
continue
dependency_features = dependency.get("features")
if dependency_features is not None:
normalized_dependency_features = normalize_string_list(
dependency_features
)
exception_key = (path_key, section_name, dependency_name)
expected_dependency_features = (
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS.get(exception_key)
)
if expected_dependency_features is None:
errors.append(
"remove `features = [...]` from workspace dependency "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new workspace crate feature activations are not allowed"
)
else:
used_internal_dependency_feature_exceptions.add(exception_key)
if normalized_dependency_features != expected_dependency_features:
errors.append(
"limit workspace dependency features on "
f"`{dependency_entry_label(section_name, dependency_name)}` "
"to the existing exception list while workspace crate "
"features are being removed "
f"(expected {render_string_list(expected_dependency_features)})"
)
if dependency.get("default-features") is False:
errors.append(
"remove `default-features = false` from workspace dependency "
f"`{dependency_entry_label(section_name, dependency_name)}`; "
"new workspace crate feature toggles are not allowed"
)
return errors
@@ -221,153 +109,6 @@ def is_workspace_reference(value: object) -> bool:
return isinstance(value, dict) and value.get("workspace") is True
def manifest_key(path: Path) -> str:
return str(path.relative_to(ROOT))
def normalize_feature_mapping(value: object) -> dict[str, tuple[str, ...]] | None:
if not isinstance(value, dict):
return None
normalized = {}
for key, features in value.items():
if not isinstance(key, str):
return None
normalized_features = normalize_string_list(features)
if normalized_features is None:
return None
normalized[key] = normalized_features
return normalized
def normalize_string_list(value: object) -> tuple[str, ...] | None:
if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
return None
return tuple(value)
def render_feature_mapping(features: dict[str, tuple[str, ...]]) -> str:
entries = [
f"{name} = {render_string_list(items)}" for name, items in features.items()
]
return ", ".join(entries)
def render_string_list(items: tuple[str, ...]) -> str:
return "[" + ", ".join(f'"{item}"' for item in items) + "]"
def dependency_sections(manifest: dict) -> list[tuple[str, dict]]:
sections = []
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = manifest.get(section_name)
if isinstance(dependencies, dict):
sections.append((section_name, dependencies))
workspace = manifest.get("workspace")
if isinstance(workspace, dict):
workspace_dependencies = workspace.get("dependencies")
if isinstance(workspace_dependencies, dict):
sections.append(("workspace.dependencies", workspace_dependencies))
target = manifest.get("target")
if not isinstance(target, dict):
return sections
for target_name, tables in target.items():
if not isinstance(tables, dict):
continue
for section_name in ("dependencies", "dev-dependencies", "build-dependencies"):
dependencies = tables.get(section_name)
if isinstance(dependencies, dict):
sections.append((f"target.{target_name}.{section_name}", dependencies))
return sections
def dependency_entry_label(section_name: str, dependency_name: str) -> str:
return f"[{section_name}].{dependency_name}"
def is_internal_dependency(
manifest_path: Path,
dependency_name: str,
dependency: dict,
internal_package_names: set[str],
) -> bool:
package_name = dependency.get("package", dependency_name)
if isinstance(package_name, str) and package_name in internal_package_names:
return True
dependency_path = dependency.get("path")
if not isinstance(dependency_path, str):
return False
resolved_dependency_path = (manifest_path.parent / dependency_path).resolve()
try:
resolved_dependency_path.relative_to(CARGO_RS_ROOT)
except ValueError:
return False
return True
def add_unused_exception_errors(
failures_by_path: dict[str, list[str]],
used_manifest_feature_exceptions: set[str],
used_optional_dependency_exceptions: set[tuple[str, str, str]],
used_internal_dependency_feature_exceptions: set[tuple[str, str, str]],
) -> None:
for path_key in sorted(
set(MANIFEST_FEATURE_EXCEPTIONS) - used_manifest_feature_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale `[features]` exception from "
"`MANIFEST_FEATURE_EXCEPTIONS`",
)
for path_key, section_name, dependency_name in sorted(
OPTIONAL_DEPENDENCY_EXCEPTIONS - used_optional_dependency_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale optional-dependency exception for "
f"`{dependency_entry_label(section_name, dependency_name)}` from "
"`OPTIONAL_DEPENDENCY_EXCEPTIONS`",
)
for path_key, section_name, dependency_name in sorted(
set(INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS)
- used_internal_dependency_feature_exceptions
):
add_failure(
failures_by_path,
path_key,
"remove the stale internal dependency feature exception for "
f"`{dependency_entry_label(section_name, dependency_name)}` from "
"`INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS`",
)
def add_failure(failures_by_path: dict[str, list[str]], path_key: str, error: str) -> None:
failures_by_path.setdefault(path_key, []).append(error)
def workspace_package_names() -> set[str]:
package_names = set()
for path in cargo_manifests():
manifest = load_manifest(path)
package = manifest.get("package")
if not isinstance(package, dict):
continue
package_name = package.get("name")
if isinstance(package_name, str):
package_names.add(package_name)
return package_names
def load_manifest(path: Path) -> dict:
return tomllib.loads(path.read_text())
@@ -380,9 +121,5 @@ def cargo_manifests() -> list[Path]:
)
def manifests_to_verify() -> list[Path]:
return [CARGO_RS_ROOT / "Cargo.toml", *cargo_manifests()]
if __name__ == "__main__":
sys.exit(main())

View File

@@ -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`.

View File

@@ -58,31 +58,11 @@ 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
run: ./scripts/check-module-bazel-lock.sh
- 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 test //...
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
@@ -96,16 +76,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 \
@@ -114,23 +87,15 @@ jobs:
-- \
"${bazel_targets[@]}"
- name: Upload Bazel execution logs
if: always() && !cancelled()
continue-on-error: true
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: bazel-execution-logs-test-${{ matrix.target }}
path: ${{ runner.temp }}/bazel-execution-logs
if-no-files-found: ignore
# 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,71 +126,30 @@ 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[@]}"
- name: Upload Bazel execution logs
if: always() && !cancelled()
continue-on-error: true
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: bazel-execution-logs-clippy-${{ matrix.target }}
path: ${{ runner.temp }}/bazel-execution-logs
if-no-files-found: ignore
//codex-rs/... \
-//codex-rs/v8-poc:all
# Save bazel repository cache explicitly; make non-fatal so cache uploading
# never fails the overall job. Only save when key wasn't hit.
- name: Save bazel repository cache
if: always() && !cancelled()
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') }}

View File

@@ -63,5 +63,10 @@ jobs:
- name: Check root README ToC
run: python3 scripts/readme_toc.py README.md
- name: Ensure codex-cli/README.md contains only ASCII and certain Unicode code points
run: ./scripts/asciicheck.py codex-cli/README.md
- name: Check codex-cli/README ToC
run: python3 scripts/readme_toc.py codex-cli/README.md
- name: Prettier (run `pnpm run format:fix` to fix)
run: pnpm run format

View File

@@ -52,15 +52,13 @@ jobs:
6. code-review — Issues related to the code review feature or functionality.
7. safety-check - Issues related to cyber risk detection or trusted access verification.
8. auth - Problems related to authentication, login, or access tokens.
9. exec - Problems related to the "codex exec" command or functionality.
10. hooks - Problems related to event hooks
11. context - Problems related to compaction, context windows, or available context reporting.
12. skills - Problems related to skills or plugins
13. custom-model - Problems that involve using custom model providers, local models, or OSS models.
14. rate-limits - Problems related to token limits, rate limits, or token usage reporting.
15. sandbox - Issues related to local sandbox environments or tool call approvals to override sandbox restrictions.
16. tool-calls - Problems related to specific tool call invocations including unexpected errors, failures, or hangs.
17. TUI - Problems with the terminal user interface (TUI) including keyboard shortcuts, copy & pasting, menus, or screen update issues.
9. codex-exec - Problems related to the "codex exec" command or functionality.
10. context-management - Problems related to compaction, context windows, or available context reporting.
11. custom-model - Problems that involve using custom model providers, local models, or OSS models.
12. rate-limits - Problems related to token limits, rate limits, or token usage reporting.
13. sandbox - Issues related to local sandbox environments or tool call approvals to override sandbox restrictions.
14. tool-calls - Problems related to specific tool call invocations including unexpected errors, failures, or hangs.
15. TUI - Problems with the terminal user interface (TUI) including keyboard shortcuts, copy & pasting, menus, or screen update issues.
Issue number: ${{ github.event.issue.number }}

View File

@@ -3,7 +3,6 @@ on:
push:
branches:
- main
- "**full-ci**"
workflow_dispatch:
# CI builds in debug (dev) for faster signal.
@@ -446,10 +445,10 @@ jobs:
set -euo pipefail
RECIPE="${RUNNER_TEMP}/chef-recipe.json"
cargo chef prepare --recipe-path "$RECIPE"
cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release
cargo chef cook --recipe-path "$RECIPE" --target ${{ matrix.target }} --release --all-features
- name: cargo clippy
run: cargo clippy --target ${{ matrix.target }} --tests --profile ${{ matrix.profile }} --timings -- -D warnings
run: cargo clippy --target ${{ matrix.target }} --all-features --tests --profile ${{ matrix.profile }} --timings -- -D warnings
- name: Upload Cargo timings (clippy)
if: always()
@@ -673,7 +672,7 @@ jobs:
- name: tests
id: test
run: cargo nextest run --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings
run: cargo nextest run --all-features --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings
env:
RUST_BACKTRACE: 1
NEXTEST_STATUS_LEVEL: leak

View File

@@ -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 }}

View File

@@ -15,13 +15,11 @@ In the codex-rs folder where the rust code lives:
- When you cannot make that API change and still need a small positional-literal callsite in Rust, follow the `argument_comment_lint` convention:
- Use an exact `/*param_name*/` comment before opaque literal arguments such as `None`, booleans, and numeric literals when passing them by position.
- Do not add these comments for string or char literals unless the comment adds real clarity; those literals are intentionally exempt from the lint.
- The parameter name in the comment must exactly match the callee signature.
- You can run `just argument-comment-lint` to run the lint check locally. This is powered by Bazel, so running it the first time can be slow if Bazel is not warmed up, though incremental invocations should take <15s. Most of the time, it is best to update the PR and let CI take responsibility for checking this (or run it asynchronously in the background after submitting the PR). Note CI checks all three platforms, which the local run does not.
- If you add one of these comments, the parameter name must exactly match the callee signature.
- When possible, make `match` statements exhaustive and avoid wildcard arms.
- Newly added traits should include doc comments that explain their role and how implementations are expected to use them.
- When writing tests, prefer comparing the equality of entire objects over fields one by one.
- When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable.
- Prefer private modules and explicitly exported public crate API.
- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`.
- If you change Rust dependencies (`Cargo.toml` or `Cargo.lock`), run `just bazel-lock-update` from the
repo root to refresh `MODULE.bazel.lock`, and include that lockfile update in the same change.
@@ -52,6 +50,8 @@ Run `just fmt` (in `codex-rs` directory) automatically after you have finished m
Before finalizing a large change to `codex-rs`, run `just fix -p <project>` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspacewide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Do not re-run tests after running `fix` or `fmt`.
Also run `just argument-comment-lint` to ensure the codebase is clean of comment lint errors.
## The `codex-core` crate
Over time, the `codex-core` crate (defined in `codex-rs/core/`) has become bloated because it is the largest crate, so it is often easier to add something new to `codex-core` rather than refactor out the library code you need so your new code neither takes a dependency on, nor contributes to the size of, `codex-core`.

View File

@@ -71,8 +71,6 @@ single_version_override(
patch_strip = 1,
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 +84,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 +188,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 = {
@@ -228,13 +214,6 @@ inject_repo(crate, "zstd")
use_repo(crate, "argument_comment_lint_crates")
bazel_dep(name = "bzip2", version = "1.0.8.bcr.3")
single_version_override(
module_name = "bzip2",
patch_strip = 1,
patches = [
"//patches:bzip2_windows_stack_args.patch",
],
)
crate.annotation(
crate = "bzip2-sys",
@@ -248,30 +227,20 @@ 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")
bazel_dep(name = "xz", version = "5.4.5.bcr.8")
single_version_override(
module_name = "xz",
patch_strip = 1,
patches = [
"//patches:xz_windows_stack_args.patch",
],
)
# TODO(zbarsky): Enable annotation after fixing windows arm64 builds.
crate.annotation(
crate = "lzma-sys",
gen_build_script = "off",
deps = ["@xz//:lzma"],
gen_build_script = "on",
)
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
View File

@@ -228,8 +228,6 @@
"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",
@@ -925,7 +923,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\"]}}",
@@ -1369,7 +1367,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\":[]}}",

View File

@@ -46,7 +46,7 @@ Each archive contains a single entry with the platform baked into the name (e.g.
### Using Codex with your ChatGPT plan
Run `codex` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codex as part of your Plus, Pro, Business, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
Run `codex` and select **Sign in with ChatGPT**. We recommend signing into your ChatGPT account to use Codex as part of your Plus, Pro, Team, Edu, or Enterprise plan. [Learn more about what's included in your ChatGPT plan](https://help.openai.com/en/articles/11369540-codex-in-chatgpt).
You can also use Codex with an API key, but this requires [additional setup](https://developers.openai.com/codex/auth#sign-in-with-an-api-key).

736
codex-cli/README.md Normal file
View File

@@ -0,0 +1,736 @@
<h1 align="center">OpenAI Codex CLI</h1>
<p align="center">Lightweight coding agent that runs in your terminal</p>
<p align="center"><code>npm i -g @openai/codex</code></p>
> [!IMPORTANT]
> This is the documentation for the _legacy_ TypeScript implementation of the Codex CLI. It has been superseded by the _Rust_ implementation. See the [README in the root of the Codex repository](https://github.com/openai/codex/blob/main/README.md) for details.
![Codex demo GIF using: codex "explain this codebase to me"](../.github/demo.gif)
---
<details>
<summary><strong>Table of contents</strong></summary>
<!-- Begin ToC -->
- [Experimental technology disclaimer](#experimental-technology-disclaimer)
- [Quickstart](#quickstart)
- [Why Codex?](#why-codex)
- [Security model & permissions](#security-model--permissions)
- [Platform sandboxing details](#platform-sandboxing-details)
- [System requirements](#system-requirements)
- [CLI reference](#cli-reference)
- [Memory & project docs](#memory--project-docs)
- [Non-interactive / CI mode](#non-interactive--ci-mode)
- [Tracing / verbose logging](#tracing--verbose-logging)
- [Recipes](#recipes)
- [Installation](#installation)
- [Configuration guide](#configuration-guide)
- [Basic configuration parameters](#basic-configuration-parameters)
- [Custom AI provider configuration](#custom-ai-provider-configuration)
- [History configuration](#history-configuration)
- [Configuration examples](#configuration-examples)
- [Full configuration example](#full-configuration-example)
- [Custom instructions](#custom-instructions)
- [Environment variables setup](#environment-variables-setup)
- [FAQ](#faq)
- [Zero data retention (ZDR) usage](#zero-data-retention-zdr-usage)
- [Codex open source fund](#codex-open-source-fund)
- [Contributing](#contributing)
- [Development workflow](#development-workflow)
- [Git hooks with Husky](#git-hooks-with-husky)
- [Debugging](#debugging)
- [Writing high-impact code changes](#writing-high-impact-code-changes)
- [Opening a pull request](#opening-a-pull-request)
- [Review process](#review-process)
- [Community values](#community-values)
- [Getting help](#getting-help)
- [Contributor license agreement (CLA)](#contributor-license-agreement-cla)
- [Quick fixes](#quick-fixes)
- [Releasing `codex`](#releasing-codex)
- [Alternative build options](#alternative-build-options)
- [Nix flake development](#nix-flake-development)
- [Security & responsible AI](#security--responsible-ai)
- [License](#license)
<!-- End ToC -->
</details>
---
## Experimental technology disclaimer
Codex CLI is an experimental project under active development. It is not yet stable, may contain bugs, incomplete features, or undergo breaking changes. We're building it in the open with the community and welcome:
- Bug reports
- Feature requests
- Pull requests
- Good vibes
Help us improve by filing issues or submitting PRs (see the section below for how to contribute)!
## Quickstart
Install globally:
```shell
npm install -g @openai/codex
```
Next, set your OpenAI API key as an environment variable:
```shell
export OPENAI_API_KEY="your-api-key-here"
```
> **Note:** This command sets the key only for your current terminal session. You can add the `export` line to your shell's configuration file (e.g., `~/.zshrc`) but we recommend setting for the session. **Tip:** You can also place your API key into a `.env` file at the root of your project:
>
> ```env
> OPENAI_API_KEY=your-api-key-here
> ```
>
> The CLI will automatically load variables from `.env` (via `dotenv/config`).
<details>
<summary><strong>Use <code>--provider</code> to use other models</strong></summary>
> Codex also allows you to use other providers that support the OpenAI Chat Completions API. You can set the provider in the config file or use the `--provider` flag. The possible options for `--provider` are:
>
> - openai (default)
> - openrouter
> - azure
> - gemini
> - ollama
> - mistral
> - deepseek
> - xai
> - groq
> - arceeai
> - any other provider that is compatible with the OpenAI API
>
> If you use a provider other than OpenAI, you will need to set the API key for the provider in the config file or in the environment variable as:
>
> ```shell
> export <provider>_API_KEY="your-api-key-here"
> ```
>
> If you use a provider not listed above, you must also set the base URL for the provider:
>
> ```shell
> export <provider>_BASE_URL="https://your-provider-api-base-url"
> ```
</details>
<br />
Run interactively:
```shell
codex
```
Or, run with a prompt as input (and optionally in `Full Auto` mode):
```shell
codex "explain this codebase to me"
```
```shell
codex --approval-mode full-auto "create the fanciest todo-list app"
```
That's it - Codex will scaffold a file, run it inside a sandbox, install any
missing dependencies, and show you the live result. Approve the changes and
they'll be committed to your working directory.
---
## Why Codex?
Codex CLI is built for developers who already **live in the terminal** and want
ChatGPT-level reasoning **plus** the power to actually run code, manipulate
files, and iterate - all under version control. In short, it's _chat-driven
development_ that understands and executes your repo.
- **Zero setup** - bring your OpenAI API key and it just works!
- **Full auto-approval, while safe + secure** by running network-disabled and directory-sandboxed
- **Multimodal** - pass in screenshots or diagrams to implement features ✨
And it's **fully open-source** so you can see and contribute to how it develops!
---
## Security model & permissions
Codex lets you decide _how much autonomy_ the agent receives and auto-approval policy via the
`--approval-mode` flag (or the interactive onboarding prompt):
| Mode | What the agent may do without asking | Still requires approval |
| ------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **Suggest** <br>(default) | <li>Read any file in the repo | <li>**All** file writes/patches<li> **Any** arbitrary shell commands (aside from reading files) |
| **Auto Edit** | <li>Read **and** apply-patch writes to files | <li>**All** shell commands |
| **Full Auto** | <li>Read/write files <li> Execute shell commands (network disabled, writes limited to your workdir) | - |
In **Full Auto** every command is run **network-disabled** and confined to the
current working directory (plus temporary files) for defense-in-depth. Codex
will also show a warning/confirmation if you start in **auto-edit** or
**full-auto** while the directory is _not_ tracked by Git, so you always have a
safety net.
Coming soon: you'll be able to whitelist specific commands to auto-execute with
the network enabled, once we're confident in additional safeguards.
### Platform sandboxing details
The hardening mechanism Codex uses depends on your OS:
- **macOS 12+** - commands are wrapped with **Apple Seatbelt** (`sandbox-exec`).
- Everything is placed in a read-only jail except for a small set of
writable roots (`$PWD`, `$TMPDIR`, `~/.codex`, etc.).
- Outbound network is _fully blocked_ by default - even if a child process
tries to `curl` somewhere it will fail.
- **Linux** - there is no sandboxing by default.
We recommend using Docker for sandboxing, where Codex launches itself inside a **minimal
container image** and mounts your repo _read/write_ at the same path. A
custom `iptables`/`ipset` firewall script denies all egress except the
OpenAI API. This gives you deterministic, reproducible runs without needing
root on the host. You can use the [`run_in_container.sh`](../codex-cli/scripts/run_in_container.sh) script to set up the sandbox.
---
## System requirements
| Requirement | Details |
| --------------------------- | --------------------------------------------------------------- |
| Operating systems | macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 **via WSL2** |
| Node.js | **16 or newer** (Node 20 LTS recommended) |
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
| RAM | 4-GB minimum (8-GB recommended) |
> Never run `sudo npm install -g`; fix npm permissions instead.
---
## CLI reference
| Command | Purpose | Example |
| ------------------------------------ | ----------------------------------- | ------------------------------------ |
| `codex` | Interactive REPL | `codex` |
| `codex "..."` | Initial prompt for interactive REPL | `codex "fix lint errors"` |
| `codex -q "..."` | Non-interactive "quiet mode" | `codex -q --json "explain utils.ts"` |
| `codex completion <bash\|zsh\|fish>` | Print shell completion script | `codex completion bash` |
Key flags: `--model/-m`, `--approval-mode/-a`, `--quiet/-q`, and `--notify`.
---
## Memory & project docs
You can give Codex extra instructions and guidance using `AGENTS.md` files. Codex looks for `AGENTS.md` files in the following places, and merges them top-down:
1. `~/.codex/AGENTS.md` - personal global guidance
2. `AGENTS.md` at repo root - shared project notes
3. `AGENTS.md` in the current working directory - sub-folder/feature specifics
Disable loading of these files with `--no-project-doc` or the environment variable `CODEX_DISABLE_PROJECT_DOC=1`.
---
## Non-interactive / CI mode
Run Codex head-less in pipelines. Example GitHub Action step:
```yaml
- name: Update changelog via Codex
run: |
npm install -g @openai/codex
export OPENAI_API_KEY="${{ secrets.OPENAI_KEY }}"
codex -a auto-edit --quiet "update CHANGELOG for next release"
```
Set `CODEX_QUIET_MODE=1` to silence interactive UI noise.
## Tracing / verbose logging
Setting the environment variable `DEBUG=true` prints full API request and response details:
```shell
DEBUG=true codex
```
---
## Recipes
Below are a few bite-size examples you can copy-paste. Replace the text in quotes with your own task. See the [prompting guide](https://github.com/openai/codex/blob/main/codex-cli/examples/prompting_guide.md) for more tips and usage patterns.
| ✨ | What you type | What happens |
| --- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | `codex "Refactor the Dashboard component to React Hooks"` | Codex rewrites the class component, runs `npm test`, and shows the diff. |
| 2 | `codex "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
| 3 | `codex "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
| 4 | `codex "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
| 5 | `codex "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
| 6 | `codex "Carefully review this repo, and propose 3 high impact well-scoped PRs"` | Suggests impactful PRs in the current codebase. |
| 7 | `codex "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
---
## Installation
<details open>
<summary><strong>From npm (Recommended)</strong></summary>
```bash
npm install -g @openai/codex
# or
yarn global add @openai/codex
# or
bun install -g @openai/codex
# or
pnpm add -g @openai/codex
```
</details>
<details>
<summary><strong>Build from source</strong></summary>
```bash
# Clone the repository and navigate to the CLI package
git clone https://github.com/openai/codex.git
cd codex/codex-cli
# Enable corepack
corepack enable
# Install dependencies and build
pnpm install
pnpm build
# Linux-only: download prebuilt sandboxing binaries (requires gh and zstd).
./scripts/install_native_deps.sh
# Get the usage and the options
node ./dist/cli.js --help
# Run the locally-built CLI directly
node ./dist/cli.js
# Or link the command globally for convenience
pnpm link
```
</details>
---
## Configuration guide
Codex configuration files can be placed in the `~/.codex/` directory, supporting both YAML and JSON formats.
### Basic configuration parameters
| Parameter | Type | Default | Description | Available Options |
| ------------------- | ------- | ---------- | -------------------------------- | ---------------------------------------------------------------------------------------------- |
| `model` | string | `o4-mini` | AI model to use | Any model name supporting OpenAI API |
| `approvalMode` | string | `suggest` | AI assistant's permission mode | `suggest` (suggestions only)<br>`auto-edit` (automatic edits)<br>`full-auto` (fully automatic) |
| `fullAutoErrorMode` | string | `ask-user` | Error handling in full-auto mode | `ask-user` (prompt for user input)<br>`ignore-and-continue` (ignore and proceed) |
| `notify` | boolean | `true` | Enable desktop notifications | `true`/`false` |
### Custom AI provider configuration
In the `providers` object, you can configure multiple AI service providers. Each provider requires the following parameters:
| Parameter | Type | Description | Example |
| --------- | ------ | --------------------------------------- | ----------------------------- |
| `name` | string | Display name of the provider | `"OpenAI"` |
| `baseURL` | string | API service URL | `"https://api.openai.com/v1"` |
| `envKey` | string | Environment variable name (for API key) | `"OPENAI_API_KEY"` |
### History configuration
In the `history` object, you can configure conversation history settings:
| Parameter | Type | Description | Example Value |
| ------------------- | ------- | ------------------------------------------------------ | ------------- |
| `maxSize` | number | Maximum number of history entries to save | `1000` |
| `saveHistory` | boolean | Whether to save history | `true` |
| `sensitivePatterns` | array | Patterns of sensitive information to filter in history | `[]` |
### Configuration examples
1. YAML format (save as `~/.codex/config.yaml`):
```yaml
model: o4-mini
approvalMode: suggest
fullAutoErrorMode: ask-user
notify: true
```
2. JSON format (save as `~/.codex/config.json`):
```json
{
"model": "o4-mini",
"approvalMode": "suggest",
"fullAutoErrorMode": "ask-user",
"notify": true
}
```
### Full configuration example
Below is a comprehensive example of `config.json` with multiple custom providers:
```json
{
"model": "o4-mini",
"provider": "openai",
"providers": {
"openai": {
"name": "OpenAI",
"baseURL": "https://api.openai.com/v1",
"envKey": "OPENAI_API_KEY"
},
"azure": {
"name": "AzureOpenAI",
"baseURL": "https://YOUR_PROJECT_NAME.openai.azure.com/openai",
"envKey": "AZURE_OPENAI_API_KEY"
},
"openrouter": {
"name": "OpenRouter",
"baseURL": "https://openrouter.ai/api/v1",
"envKey": "OPENROUTER_API_KEY"
},
"gemini": {
"name": "Gemini",
"baseURL": "https://generativelanguage.googleapis.com/v1beta/openai",
"envKey": "GEMINI_API_KEY"
},
"ollama": {
"name": "Ollama",
"baseURL": "http://localhost:11434/v1",
"envKey": "OLLAMA_API_KEY"
},
"mistral": {
"name": "Mistral",
"baseURL": "https://api.mistral.ai/v1",
"envKey": "MISTRAL_API_KEY"
},
"deepseek": {
"name": "DeepSeek",
"baseURL": "https://api.deepseek.com",
"envKey": "DEEPSEEK_API_KEY"
},
"xai": {
"name": "xAI",
"baseURL": "https://api.x.ai/v1",
"envKey": "XAI_API_KEY"
},
"groq": {
"name": "Groq",
"baseURL": "https://api.groq.com/openai/v1",
"envKey": "GROQ_API_KEY"
},
"arceeai": {
"name": "ArceeAI",
"baseURL": "https://conductor.arcee.ai/v1",
"envKey": "ARCEEAI_API_KEY"
}
},
"history": {
"maxSize": 1000,
"saveHistory": true,
"sensitivePatterns": []
}
}
```
### Custom instructions
You can create a `~/.codex/AGENTS.md` file to define custom guidance for the agent:
```markdown
- Always respond with emojis
- Only use git commands when explicitly requested
```
### Environment variables setup
For each AI provider, you need to set the corresponding API key in your environment variables. For example:
```bash
# OpenAI
export OPENAI_API_KEY="your-api-key-here"
# Azure OpenAI
export AZURE_OPENAI_API_KEY="your-azure-api-key-here"
export AZURE_OPENAI_API_VERSION="2025-04-01-preview" (Optional)
# OpenRouter
export OPENROUTER_API_KEY="your-openrouter-key-here"
# Similarly for other providers
```
---
## FAQ
<details>
<summary>OpenAI released a model called Codex in 2021 - is this related?</summary>
In 2021, OpenAI released Codex, an AI system designed to generate code from natural language prompts. That original Codex model was deprecated as of March 2023 and is separate from the CLI tool.
</details>
<details>
<summary>Which models are supported?</summary>
Any model available with [Responses API](https://platform.openai.com/docs/api-reference/responses). The default is `o4-mini`, but pass `--model gpt-4.1` or set `model: gpt-4.1` in your config file to override.
</details>
<details>
<summary>Why does <code>o3</code> or <code>o4-mini</code> not work for me?</summary>
It's possible that your [API account needs to be verified](https://help.openai.com/en/articles/10910291-api-organization-verification) in order to start streaming responses and seeing chain of thought summaries from the API. If you're still running into issues, please let us know!
</details>
<details>
<summary>How do I stop Codex from editing my files?</summary>
Codex runs model-generated commands in a sandbox. If a proposed command or file change doesn't look right, you can simply type **n** to deny the command or give the model feedback.
</details>
<details>
<summary>Does it work on Windows?</summary>
Not directly. It requires [Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/wsl/install) - Codex is regularly tested on macOS and Linux with Node 20+, and also supports Node 16.
</details>
---
## Zero data retention (ZDR) usage
Codex CLI **does** support OpenAI organizations with [Zero Data Retention (ZDR)](https://platform.openai.com/docs/guides/your-data#zero-data-retention) enabled. If your OpenAI organization has Zero Data Retention enabled and you still encounter errors such as:
```
OpenAI rejected the request. Error details: Status: 400, Code: unsupported_parameter, Type: invalid_request_error, Message: 400 Previous response cannot be used for this organization due to Zero Data Retention.
```
You may need to upgrade to a more recent version with: `npm i -g @openai/codex@latest`
---
## Codex open source fund
We're excited to launch a **$1 million initiative** supporting open source projects that use Codex CLI and other OpenAI models.
- Grants are awarded up to **$25,000** API credits.
- Applications are reviewed **on a rolling basis**.
**Interested? [Apply here](https://openai.com/form/codex-open-source-fund/).**
---
## Contributing
This project is under active development and the code will likely change pretty significantly. We'll update this message once that's complete!
More broadly we welcome contributions - whether you are opening your very first pull request or you're a seasoned maintainer. At the same time we care about reliability and long-term maintainability, so the bar for merging code is intentionally **high**. The guidelines below spell out what "high-quality" means in practice and should make the whole process transparent and friendly.
### Development workflow
- Create a _topic branch_ from `main` - e.g. `feat/interactive-prompt`.
- Keep your changes focused. Multiple unrelated fixes should be opened as separate PRs.
- Use `pnpm test:watch` during development for super-fast feedback.
- We use **Vitest** for unit tests, **ESLint** + **Prettier** for style, and **TypeScript** for type-checking.
- Before pushing, run the full test/type/lint suite:
### Git hooks with Husky
This project uses [Husky](https://typicode.github.io/husky/) to enforce code quality checks:
- **Pre-commit hook**: Automatically runs lint-staged to format and lint files before committing
- **Pre-push hook**: Runs tests and type checking before pushing to the remote
These hooks help maintain code quality and prevent pushing code with failing tests. For more details, see [HUSKY.md](./HUSKY.md).
```bash
pnpm test && pnpm run lint && pnpm run typecheck
```
- If you have **not** yet signed the Contributor License Agreement (CLA), add a PR comment containing the exact text
```text
I have read the CLA Document and I hereby sign the CLA
```
The CLA-Assistant bot will turn the PR status green once all authors have signed.
```bash
# Watch mode (tests rerun on change)
pnpm test:watch
# Type-check without emitting files
pnpm typecheck
# Automatically fix lint + prettier issues
pnpm lint:fix
pnpm format:fix
```
### Debugging
To debug the CLI with a visual debugger, do the following in the `codex-cli` folder:
- Run `pnpm run build` to build the CLI, which will generate `cli.js.map` alongside `cli.js` in the `dist` folder.
- Run the CLI with `node --inspect-brk ./dist/cli.js` The program then waits until a debugger is attached before proceeding. Options:
- In VS Code, choose **Debug: Attach to Node Process** from the command palette and choose the option in the dropdown with debug port `9229` (likely the first option)
- Go to <chrome://inspect> in Chrome and find **localhost:9229** and click **trace**
### Writing high-impact code changes
1. **Start with an issue.** Open a new one or comment on an existing discussion so we can agree on the solution before code is written.
2. **Add or update tests.** Every new feature or bug-fix should come with test coverage that fails before your change and passes afterwards. 100% coverage is not required, but aim for meaningful assertions.
3. **Document behaviour.** If your change affects user-facing behaviour, update the README, inline help (`codex --help`), or relevant example projects.
4. **Keep commits atomic.** Each commit should compile and the tests should pass. This makes reviews and potential rollbacks easier.
### Opening a pull request
- Fill in the PR template (or include similar information) - **What? Why? How?**
- Run **all** checks locally (`npm test && npm run lint && npm run typecheck`). CI failures that could have been caught locally slow down the process.
- Make sure your branch is up-to-date with `main` and that you have resolved merge conflicts.
- Mark the PR as **Ready for review** only when you believe it is in a merge-able state.
### Review process
1. One maintainer will be assigned as a primary reviewer.
2. We may ask for changes - please do not take this personally. We value the work, we just also value consistency and long-term maintainability.
3. When there is consensus that the PR meets the bar, a maintainer will squash-and-merge.
### Community values
- **Be kind and inclusive.** Treat others with respect; we follow the [Contributor Covenant](https://www.contributor-covenant.org/).
- **Assume good intent.** Written communication is hard - err on the side of generosity.
- **Teach & learn.** If you spot something confusing, open an issue or PR with improvements.
### Getting help
If you run into problems setting up the project, would like feedback on an idea, or just want to say _hi_ - please open a Discussion or jump into the relevant issue. We are happy to help.
Together we can make Codex CLI an incredible tool. **Happy hacking!** :rocket:
### Contributor license agreement (CLA)
All contributors **must** accept the CLA. The process is lightweight:
1. Open your pull request.
2. Paste the following comment (or reply `recheck` if you've signed before):
```text
I have read the CLA Document and I hereby sign the CLA
```
3. The CLA-Assistant bot records your signature in the repo and marks the status check as passed.
No special Git commands, email attachments, or commit footers required.
#### Quick fixes
| Scenario | Command |
| ----------------- | ------------------------------------------------ |
| Amend last commit | `git commit --amend -s --no-edit && git push -f` |
The **DCO check** blocks merges until every commit in the PR carries the footer (with squash this is just the one).
### Releasing `codex`
To publish a new version of the CLI you first need to stage the npm package. A
helper script in `codex-cli/scripts/` does all the heavy lifting. Inside the
`codex-cli` folder run:
```bash
# Classic, JS implementation that includes small, native binaries for Linux sandboxing.
pnpm stage-release
# Optionally specify the temp directory to reuse between runs.
RELEASE_DIR=$(mktemp -d)
pnpm stage-release --tmp "$RELEASE_DIR"
# "Fat" package that additionally bundles the native Rust CLI binaries for
# Linux. End-users can then opt-in at runtime by setting CODEX_RUST=1.
pnpm stage-release --native
```
Go to the folder where the release is staged and verify that it works as intended. If so, run the following from the temp folder:
```
cd "$RELEASE_DIR"
npm publish
```
### Alternative build options
#### Nix flake development
Prerequisite: Nix >= 2.4 with flakes enabled (`experimental-features = nix-command flakes` in `~/.config/nix/nix.conf`).
Enter a Nix development shell:
```bash
# Use either one of the commands according to which implementation you want to work with
nix develop .#codex-cli # For entering codex-cli specific shell
nix develop .#codex-rs # For entering codex-rs specific shell
```
This shell includes Node.js, installs dependencies, builds the CLI, and provides a `codex` command alias.
Build and run the CLI directly:
```bash
# Use either one of the commands according to which implementation you want to work with
nix build .#codex-cli # For building codex-cli
nix build .#codex-rs # For building codex-rs
./result/bin/codex --help
```
Run the CLI via the flake app:
```bash
# Use either one of the commands according to which implementation you want to work with
nix run .#codex-cli # For running codex-cli
nix run .#codex-rs # For running codex-rs
```
Use direnv with flakes
If you have direnv installed, you can use the following `.envrc` to automatically enter the Nix shell when you `cd` into the project directory:
```bash
cd codex-rs
echo "use flake ../flake.nix#codex-cli" >> .envrc && direnv allow
cd codex-cli
echo "use flake ../flake.nix#codex-rs" >> .envrc && direnv allow
```
---
## Security & responsible AI
Have you discovered a vulnerability or have concerns about model output? Please e-mail **security@openai.com** and we will respond promptly.
---
## License
This repository is licensed under the [Apache-2.0 License](LICENSE).

233
codex-rs/Cargo.lock generated
View File

@@ -408,11 +408,9 @@ dependencies = [
"base64 0.22.1",
"chrono",
"codex-app-server-protocol",
"codex-config",
"codex-core",
"codex-features",
"codex-login",
"codex-models-manager",
"codex-protocol",
"codex-utils-cargo-bin",
"core_test_support",
@@ -449,7 +447,7 @@ dependencies = [
"objc2-foundation",
"parking_lot",
"percent-encoding",
"windows-sys 0.52.0",
"windows-sys 0.60.2",
"wl-clipboard-rs",
"x11rb",
]
@@ -1369,9 +1367,7 @@ dependencies = [
"anyhow",
"assert_matches",
"async-trait",
"base64 0.22.1",
"bytes",
"chrono",
"codex-client",
"codex-protocol",
"codex-utils-rustls-provider",
@@ -1405,13 +1401,11 @@ dependencies = [
"base64 0.22.1",
"chrono",
"clap",
"codex-analytics",
"codex-app-server-protocol",
"codex-arg0",
"codex-backend-client",
"codex-chatgpt",
"codex-cloud-requirements",
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
@@ -1419,12 +1413,9 @@ dependencies = [
"codex-file-search",
"codex-git-utils",
"codex-login",
"codex-mcp",
"codex-models-manager",
"codex-otel",
"codex-protocol",
"codex-rmcp-client",
"codex-rollout",
"codex-sandboxing",
"codex-shell-command",
"codex-state",
@@ -1434,11 +1425,9 @@ dependencies = [
"codex-utils-cli",
"codex-utils-json-to-toml",
"codex-utils-pty",
"codex-utils-rustls-provider",
"constant_time_eq",
"core_test_support",
"futures",
"gethostname",
"hmac",
"jsonwebtoken",
"opentelemetry",
@@ -1461,7 +1450,6 @@ dependencies = [
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"url",
"uuid",
"wiremock",
]
@@ -1496,7 +1484,6 @@ dependencies = [
"codex-experimental-api-macros",
"codex-git-utils",
"codex-protocol",
"codex-shell-command",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"inventory",
@@ -1506,6 +1493,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"shlex",
"similar",
"strum_macros 0.28.0",
"tempfile",
@@ -1543,14 +1531,11 @@ dependencies = [
"anyhow",
"assert_cmd",
"assert_matches",
"codex-exec-server",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"pretty_assertions",
"similar",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tree-sitter",
"tree-sitter-bash",
]
@@ -1561,11 +1546,9 @@ version = "0.0.0"
dependencies = [
"anyhow",
"codex-apply-patch",
"codex-exec-server",
"codex-linux-sandbox",
"codex-sandboxing",
"codex-shell-escalation",
"codex-utils-absolute-path",
"codex-utils-home-dir",
"dotenvy",
"tempfile",
@@ -1589,7 +1572,7 @@ dependencies = [
"anyhow",
"codex-backend-openapi-models",
"codex-client",
"codex-login",
"codex-core",
"codex-protocol",
"pretty_assertions",
"reqwest",
@@ -1612,7 +1595,6 @@ version = "0.0.0"
dependencies = [
"anyhow",
"clap",
"codex-config",
"codex-connectors",
"codex-core",
"codex-git-utils",
@@ -1647,7 +1629,6 @@ dependencies = [
"codex-execpolicy",
"codex-features",
"codex-login",
"codex-mcp",
"codex-mcp-server",
"codex-protocol",
"codex-responses-api-proxy",
@@ -1659,7 +1640,6 @@ dependencies = [
"codex-tui",
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-path",
"codex-windows-sandbox",
"libc",
"owo-colors",
@@ -1715,9 +1695,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"codex-backend-client",
"codex-config",
"codex-core",
"codex-login",
"codex-otel",
"codex-protocol",
"hmac",
@@ -1743,7 +1721,6 @@ dependencies = [
"clap",
"codex-client",
"codex-cloud-tasks-client",
"codex-cloud-tasks-mock-client",
"codex-core",
"codex-git-utils",
"codex-login",
@@ -1773,21 +1750,12 @@ dependencies = [
"chrono",
"codex-backend-client",
"codex-git-utils",
"diffy",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]]
name = "codex-cloud-tasks-mock-client"
version = "0.0.0"
dependencies = [
"async-trait",
"chrono",
"codex-cloud-tasks-client",
"diffy",
]
[[package]]
name = "codex-code-mode"
version = "0.0.0"
@@ -1802,10 +1770,6 @@ dependencies = [
"v8",
]
[[package]]
name = "codex-collaboration-mode-templates"
version = "0.0.0"
[[package]]
name = "codex-config"
version = "0.0.0"
@@ -1813,13 +1777,8 @@ dependencies = [
"anyhow",
"codex-app-server-protocol",
"codex-execpolicy",
"codex-features",
"codex-git-utils",
"codex-model-provider-info",
"codex-network-proxy",
"codex-protocol",
"codex-utils-absolute-path",
"dunce",
"futures",
"multimap",
"pretty_assertions",
@@ -1828,13 +1787,11 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"sha2",
"tempfile",
"thiserror 2.0.18",
"tokio",
"toml 0.9.11+spec-1.1.0",
"toml_edit 0.24.0+spec-1.1.0",
"tracing",
"wildmatch",
]
[[package]]
@@ -1861,6 +1818,7 @@ dependencies = [
"async-trait",
"base64 0.22.1",
"bm25",
"chardetng",
"chrono",
"clap",
"codex-analytics",
@@ -1876,19 +1834,14 @@ 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",
@@ -1918,6 +1871,7 @@ dependencies = [
"ctor 0.6.3",
"dirs",
"dunce",
"encoding_rs",
"env-flags",
"eventsource-stream",
"futures",
@@ -1926,6 +1880,7 @@ dependencies = [
"image",
"indexmap 2.13.0",
"insta",
"landlock",
"libc",
"maplit",
"notify",
@@ -1939,6 +1894,8 @@ dependencies = [
"regex-lite",
"reqwest",
"rmcp",
"schemars 0.8.22",
"seccompiler",
"serde",
"serde_json",
"serial_test",
@@ -1962,6 +1919,7 @@ dependencies = [
"uuid",
"walkdir",
"which 8.0.0",
"wildmatch",
"windows-sys 0.52.0",
"wiremock",
"zip",
@@ -2024,8 +1982,6 @@ dependencies = [
"codex-core",
"codex-feedback",
"codex-git-utils",
"codex-login",
"codex-model-provider-info",
"codex-otel",
"codex-protocol",
"codex-utils-absolute-path",
@@ -2128,6 +2084,7 @@ dependencies = [
name = "codex-features"
version = "0.0.0"
dependencies = [
"codex-login",
"codex-otel",
"codex-protocol",
"pretty_assertions",
@@ -2142,7 +2099,6 @@ name = "codex-feedback"
version = "0.0.0"
dependencies = [
"anyhow",
"codex-login",
"codex-protocol",
"pretty_assertions",
"sentry",
@@ -2226,7 +2182,6 @@ version = "0.0.0"
dependencies = [
"cc",
"clap",
"codex-config",
"codex-core",
"codex-protocol",
"codex-sandboxing",
@@ -2248,7 +2203,6 @@ name = "codex-lmstudio"
version = "0.0.0"
dependencies = [
"codex-core",
"codex-model-provider-info",
"reqwest",
"serde_json",
"tokio",
@@ -2265,13 +2219,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",
@@ -2283,6 +2234,7 @@ dependencies = [
"rand 0.9.2",
"regex-lite",
"reqwest",
"schemars 0.8.22",
"serde",
"serde_json",
"serial_test",
@@ -2298,47 +2250,15 @@ dependencies = [
"wiremock",
]
[[package]]
name = "codex-mcp"
version = "0.0.0"
dependencies = [
"anyhow",
"async-channel",
"codex-async-utils",
"codex-config",
"codex-login",
"codex-otel",
"codex-plugin",
"codex-protocol",
"codex-rmcp-client",
"codex-utils-plugins",
"futures",
"pretty_assertions",
"regex-lite",
"rmcp",
"serde",
"serde_json",
"sha1",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
"url",
]
[[package]]
name = "codex-mcp-server"
version = "0.0.0"
dependencies = [
"anyhow",
"codex-arg0",
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
"codex-login",
"codex-models-manager",
"codex-protocol",
"codex-shell-command",
"codex-utils-cli",
@@ -2359,54 +2279,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-config",
"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"
@@ -2446,7 +2318,6 @@ dependencies = [
"async-stream",
"bytes",
"codex-core",
"codex-model-provider-info",
"futures",
"pretty_assertions",
"reqwest",
@@ -2511,27 +2382,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",
@@ -2539,24 +2401,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"
@@ -2566,7 +2415,6 @@ dependencies = [
"codex-process-hardening",
"ctor 0.6.3",
"libc",
"pretty_assertions",
"reqwest",
"serde",
"serde_json",
@@ -2581,7 +2429,6 @@ dependencies = [
"anyhow",
"axum",
"codex-client",
"codex-config",
"codex-keyring-store",
"codex-protocol",
"codex-utils-cargo-bin",
@@ -2593,6 +2440,7 @@ dependencies = [
"pretty_assertions",
"reqwest",
"rmcp",
"schemars 0.8.22",
"serde",
"serde_json",
"serial_test",
@@ -2769,15 +2617,11 @@ version = "0.0.0"
dependencies = [
"codex-app-server-protocol",
"codex-code-mode",
"codex-features",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-pty",
"pretty_assertions",
"rmcp",
"serde",
"serde_json",
"tracing",
]
[[package]]
@@ -2797,20 +2641,14 @@ dependencies = [
"codex-chatgpt",
"codex-cli",
"codex-cloud-requirements",
"codex-config",
"codex-core",
"codex-exec-server",
"codex-features",
"codex-feedback",
"codex-file-search",
"codex-git-utils",
"codex-login",
"codex-mcp",
"codex-model-provider-info",
"codex-models-manager",
"codex-otel",
"codex-protocol",
"codex-rollout",
"codex-shell-command",
"codex-state",
"codex-terminal-detection",
@@ -2869,7 +2707,6 @@ dependencies = [
"unicode-segmentation",
"unicode-width 0.2.1",
"url",
"urlencoding",
"uuid",
"vt100",
"webbrowser",
@@ -2883,6 +2720,7 @@ name = "codex-utils-absolute-path"
version = "0.0.0"
dependencies = [
"dirs",
"path-absolutize",
"pretty_assertions",
"schemars 0.8.22",
"serde",
@@ -2971,7 +2809,6 @@ version = "0.0.0"
dependencies = [
"codex-core",
"codex-lmstudio",
"codex-model-provider-info",
"codex-ollama",
]
@@ -2998,7 +2835,6 @@ dependencies = [
name = "codex-utils-plugins"
version = "0.0.0"
dependencies = [
"codex-login",
"serde",
"serde_json",
"tempfile",
@@ -3043,7 +2879,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",
@@ -3314,9 +3149,6 @@ dependencies = [
"codex-core",
"codex-exec-server",
"codex-features",
"codex-login",
"codex-model-provider-info",
"codex-models-manager",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
@@ -3971,7 +3803,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -4216,7 +4048,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]]
@@ -5122,13 +4954,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",
@@ -5137,7 +4970,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.5.10",
"socket2 0.6.2",
"system-configuration",
"tokio",
"tower-service",
@@ -5643,7 +5476,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6130,7 +5963,7 @@ name = "mcp_test_support"
version = "0.0.0"
dependencies = [
"anyhow",
"codex-login",
"codex-core",
"codex-mcp-server",
"codex-terminal-detection",
"codex-utils-cargo-bin",
@@ -6421,7 +6254,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]]
@@ -6597,7 +6430,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",
@@ -7065,7 +6898,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]]
@@ -7611,7 +7444,7 @@ dependencies = [
"quinn-udp",
"rustc-hash 2.1.1",
"rustls",
"socket2 0.5.10",
"socket2 0.6.2",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -7648,9 +7481,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]]
@@ -8486,7 +8319,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -9885,9 +9718,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",
@@ -9920,7 +9753,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.3",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -11366,7 +11199,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]]

View File

@@ -18,9 +18,7 @@ members = [
"cloud-requirements",
"cloud-tasks",
"cloud-tasks-client",
"cloud-tasks-mock-client",
"cli",
"collaboration-mode-templates",
"connectors",
"config",
"shell-command",
@@ -40,10 +38,7 @@ members = [
"linux-sandbox",
"lmstudio",
"login",
"codex-mcp",
"mcp-server",
"model-provider-info",
"models-manager",
"network-proxy",
"ollama",
"process-hardening",
@@ -51,7 +46,6 @@ members = [
"rollout",
"rmcp-client",
"responses-api-proxy",
"response-debug-context",
"sandboxing",
"stdio-to-uds",
"otel",
@@ -102,9 +96,10 @@ license = "Apache-2.0"
[workspace.dependencies]
# Internal
app_test_support = { path = "app-server/tests/common" }
codex-analytics = { path = "analytics" }
codex-ansi-escape = { path = "ansi-escape" }
codex-analytics = { path = "analytics" }
codex-api = { path = "codex-api" }
codex-code-mode = { path = "code-mode" }
codex-app-server = { path = "app-server" }
codex-app-server-client = { path = "app-server-client" }
codex-app-server-protocol = { path = "app-server-protocol" }
@@ -116,21 +111,17 @@ 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" }
codex-code-mode = { path = "code-mode" }
codex-config = { path = "config" }
codex-connectors = { path = "connectors" }
codex-config = { path = "config" }
codex-core = { path = "core" }
codex-core-skills = { path = "core-skills" }
codex-exec = { path = "exec" }
codex-exec-server = { path = "exec-server" }
codex-execpolicy = { path = "execpolicy" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-features = { path = "features" }
codex-feedback = { path = "feedback" }
codex-features = { path = "features" }
codex-file-search = { path = "file-search" }
codex-git-utils = { path = "git-utils" }
codex-hooks = { path = "hooks" }
@@ -139,20 +130,16 @@ codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
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" }
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-responses-api-proxy = { path = "responses-api-proxy" }
codex-rmcp-client = { path = "rmcp-client" }
codex-sandboxing = { path = "sandboxing" }
codex-secrets = { path = "secrets" }
codex-shell-command = { path = "shell-command" }
@@ -163,6 +150,7 @@ codex-stdio-to-uds = { path = "stdio-to-uds" }
codex-terminal-detection = { path = "terminal-detection" }
codex-tools = { path = "tools" }
codex-tui = { path = "tui" }
codex-v8-poc = { path = "v8-poc" }
codex-utils-absolute-path = { path = "utils/absolute-path" }
codex-utils-approval-presets = { path = "utils/approval-presets" }
codex-utils-cache = { path = "utils/cache" }
@@ -183,9 +171,8 @@ codex-utils-rustls-provider = { path = "utils/rustls-provider" }
codex-utils-sandbox-summary = { path = "utils/sandbox-summary" }
codex-utils-sleep-inhibitor = { path = "utils/sleep-inhibitor" }
codex-utils-stream-parser = { path = "utils/stream-parser" }
codex-utils-string = { path = "utils/string" }
codex-utils-template = { path = "utils/template" }
codex-v8-poc = { path = "v8-poc" }
codex-utils-string = { path = "utils/string" }
codex-windows-sandbox = { path = "windows-sandbox-rs" }
core_test_support = { path = "core/tests/common" }
mcp_test_support = { path = "mcp-server/tests/common" }
@@ -230,12 +217,12 @@ gethostname = "1.1.0"
globset = "0.4"
hmac = "0.12.1"
http = "1.3.1"
iana-time-zone = "0.1.64"
icu_decimal = "2.1"
icu_locale_core = "2.1"
icu_provider = { version = "2.1", features = ["sync"] }
ignore = "0.4.23"
image = { version = "^0.25.9", default-features = false }
iana-time-zone = "0.1.64"
include_dir = "0.7.4"
indexmap = "2.12.0"
insta = "1.46.3"
@@ -277,6 +264,7 @@ regex-lite = "0.1.8"
reqwest = "0.12"
rmcp = { version = "0.15.0", default-features = false }
runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" }
v8 = "=146.4.0"
rustls = { version = "0.23", default-features = false, features = [
"ring",
"std",
@@ -345,7 +333,6 @@ unicode-width = "0.2"
url = "2"
urlencoding = "2.1"
uuid = "1"
v8 = "=146.4.0"
vt100 = "0.16.2"
walkdir = "2.5.0"
webbrowser = "1.0"

View File

@@ -13,7 +13,6 @@ use crate::events::TrackEventRequest;
use crate::events::codex_app_metadata;
use crate::events::codex_plugin_metadata;
use crate::events::codex_plugin_used_metadata;
use crate::events::subagent_thread_started_event_request;
use crate::facts::AnalyticsFact;
use crate::facts::AppInvocation;
use crate::facts::AppMentionedInput;
@@ -25,7 +24,6 @@ use crate::facts::PluginStateChangedInput;
use crate::facts::PluginUsedInput;
use crate::facts::SkillInvocation;
use crate::facts::SkillInvokedInput;
use crate::facts::SubAgentThreadStartedInput;
use crate::facts::TrackEventsContext;
use crate::reducer::AnalyticsReducer;
use crate::reducer::normalize_path_for_skill_id;
@@ -49,7 +47,6 @@ use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginId;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::protocol::SubAgentSource;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::HashSet;
@@ -61,7 +58,6 @@ use tokio::sync::mpsc;
fn sample_thread(thread_id: &str, ephemeral: bool) -> Thread {
Thread {
id: thread_id.to_string(),
forked_from_id: None,
preview: "first prompt".to_string(),
ephemeral,
model_provider: "openai".to_string(),
@@ -449,155 +445,6 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
assert_eq!(payload[0]["event_params"]["parent_thread_id"], json!(null));
}
#[test]
fn subagent_thread_started_review_serializes_expected_shape() {
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
SubAgentThreadStartedInput {
thread_id: "thread-review".to_string(),
product_client_id: "codex-tui".to_string(),
client_name: "codex-tui".to_string(),
client_version: "1.0.0".to_string(),
model: "gpt-5".to_string(),
ephemeral: false,
subagent_source: SubAgentSource::Review,
created_at: 123,
},
));
let payload = serde_json::to_value(&event).expect("serialize review subagent event");
assert_eq!(payload["event_params"]["thread_source"], "subagent");
assert_eq!(
payload["event_params"]["app_server_client"]["product_client_id"],
"codex-tui"
);
assert_eq!(
payload["event_params"]["app_server_client"]["client_name"],
"codex-tui"
);
assert_eq!(
payload["event_params"]["app_server_client"]["client_version"],
"1.0.0"
);
assert_eq!(
payload["event_params"]["app_server_client"]["rpc_transport"],
"in_process"
);
assert_eq!(payload["event_params"]["created_at"], 123);
assert_eq!(payload["event_params"]["initialization_mode"], "new");
assert_eq!(payload["event_params"]["subagent_source"], "review");
assert_eq!(payload["event_params"]["parent_thread_id"], json!(null));
}
#[test]
fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() {
let parent_thread_id =
codex_protocol::ThreadId::from_string("11111111-1111-1111-1111-111111111111")
.expect("valid thread id");
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
SubAgentThreadStartedInput {
thread_id: "thread-spawn".to_string(),
product_client_id: "codex-tui".to_string(),
client_name: "codex-tui".to_string(),
client_version: "1.0.0".to_string(),
model: "gpt-5".to_string(),
ephemeral: true,
subagent_source: SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
agent_path: None,
agent_nickname: None,
agent_role: None,
},
created_at: 124,
},
));
let payload = serde_json::to_value(&event).expect("serialize thread spawn subagent event");
assert_eq!(payload["event_params"]["thread_source"], "subagent");
assert_eq!(payload["event_params"]["subagent_source"], "thread_spawn");
assert_eq!(
payload["event_params"]["parent_thread_id"],
"11111111-1111-1111-1111-111111111111"
);
}
#[test]
fn subagent_thread_started_memory_consolidation_serializes_expected_shape() {
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
SubAgentThreadStartedInput {
thread_id: "thread-memory".to_string(),
product_client_id: "codex-tui".to_string(),
client_name: "codex-tui".to_string(),
client_version: "1.0.0".to_string(),
model: "gpt-5".to_string(),
ephemeral: false,
subagent_source: SubAgentSource::MemoryConsolidation,
created_at: 125,
},
));
let payload =
serde_json::to_value(&event).expect("serialize memory consolidation subagent event");
assert_eq!(
payload["event_params"]["subagent_source"],
"memory_consolidation"
);
assert_eq!(payload["event_params"]["parent_thread_id"], json!(null));
}
#[test]
fn subagent_thread_started_other_serializes_expected_shape() {
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
SubAgentThreadStartedInput {
thread_id: "thread-guardian".to_string(),
product_client_id: "codex-tui".to_string(),
client_name: "codex-tui".to_string(),
client_version: "1.0.0".to_string(),
model: "gpt-5".to_string(),
ephemeral: false,
subagent_source: SubAgentSource::Other("guardian".to_string()),
created_at: 126,
},
));
let payload = serde_json::to_value(&event).expect("serialize other subagent event");
assert_eq!(payload["event_params"]["subagent_source"], "guardian");
}
#[tokio::test]
async fn subagent_thread_started_publishes_without_initialize() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted(
SubAgentThreadStartedInput {
thread_id: "thread-review".to_string(),
product_client_id: "codex-tui".to_string(),
client_name: "codex-tui".to_string(),
client_version: "1.0.0".to_string(),
model: "gpt-5".to_string(),
ephemeral: false,
subagent_source: SubAgentSource::Review,
created_at: 127,
},
)),
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(payload.as_array().expect("events array").len(), 1);
assert_eq!(payload[0]["event_type"], "codex_thread_initialized");
assert_eq!(
payload[0]["event_params"]["app_server_client"]["product_client_id"],
"codex-tui"
);
assert_eq!(payload[0]["event_params"]["thread_source"], "subagent");
assert_eq!(payload[0]["event_params"]["subagent_source"], "review");
}
#[test]
fn plugin_used_event_serializes_expected_shape() {
let tracking = TrackEventsContext {

View File

@@ -11,7 +11,6 @@ use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::SkillInvocation;
use crate::facts::SkillInvokedInput;
use crate::facts::SubAgentThreadStartedInput;
use crate::facts::TrackEventsContext;
use crate::reducer::AnalyticsReducer;
use codex_app_server_protocol::ClientResponse;
@@ -145,12 +144,6 @@ impl AnalyticsEventsClient {
});
}
pub fn track_subagent_thread_started(&self, input: SubAgentThreadStartedInput) {
self.record_fact(AnalyticsFact::Custom(
CustomAnalyticsFact::SubAgentThreadStarted(input),
));
}
pub fn track_app_mentioned(&self, tracking: TrackEventsContext, mentions: Vec<AppInvocation>) {
if mentions.is_empty() {
return;

View File

@@ -1,12 +1,10 @@
use crate::facts::AppInvocation;
use crate::facts::InvocationType;
use crate::facts::PluginState;
use crate::facts::SubAgentThreadStartedInput;
use crate::facts::TrackEventsContext;
use codex_login::default_client::originator;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use serde::Serialize;
#[derive(Clone, Copy, Debug, Serialize)]
@@ -230,49 +228,3 @@ pub(crate) fn current_runtime_metadata() -> CodexRuntimeMetadata {
runtime_arch: std::env::consts::ARCH.to_string(),
}
}
pub(crate) fn subagent_thread_started_event_request(
input: SubAgentThreadStartedInput,
) -> ThreadInitializedEvent {
let event_params = ThreadInitializedEventParams {
thread_id: input.thread_id,
app_server_client: CodexAppServerClientMetadata {
product_client_id: input.product_client_id,
client_name: Some(input.client_name),
client_version: Some(input.client_version),
rpc_transport: AppServerRpcTransport::InProcess,
experimental_api_enabled: None,
},
runtime: current_runtime_metadata(),
model: input.model,
ephemeral: input.ephemeral,
thread_source: Some("subagent"),
initialization_mode: ThreadInitializationMode::New,
subagent_source: Some(subagent_source_name(&input.subagent_source)),
parent_thread_id: subagent_parent_thread_id(&input.subagent_source),
created_at: input.created_at,
};
ThreadInitializedEvent {
event_type: "codex_thread_initialized",
event_params,
}
}
fn subagent_source_name(subagent_source: &SubAgentSource) -> String {
match subagent_source {
SubAgentSource::Review => "review".to_string(),
SubAgentSource::Compact => "compact".to_string(),
SubAgentSource::ThreadSpawn { .. } => "thread_spawn".to_string(),
SubAgentSource::MemoryConsolidation => "memory_consolidation".to_string(),
SubAgentSource::Other(other) => other.clone(),
}
}
fn subagent_parent_thread_id(subagent_source: &SubAgentSource) -> Option<String> {
match subagent_source {
SubAgentSource::ThreadSpawn {
parent_thread_id, ..
} => Some(parent_thread_id.to_string()),
_ => None,
}
}

View File

@@ -7,7 +7,6 @@ use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_plugin::PluginTelemetryMetadata;
use codex_protocol::protocol::SkillScope;
use codex_protocol::protocol::SubAgentSource;
use serde::Serialize;
use std::path::PathBuf;
@@ -51,18 +50,6 @@ pub struct AppInvocation {
pub invocation_type: Option<InvocationType>,
}
#[derive(Clone)]
pub struct SubAgentThreadStartedInput {
pub thread_id: String,
pub product_client_id: String,
pub client_name: String,
pub client_version: String,
pub model: String,
pub ephemeral: bool,
pub subagent_source: SubAgentSource,
pub created_at: u64,
}
#[allow(dead_code)]
pub(crate) enum AnalyticsFact {
Initialize {
@@ -88,7 +75,6 @@ pub(crate) enum AnalyticsFact {
}
pub(crate) enum CustomAnalyticsFact {
SubAgentThreadStarted(SubAgentThreadStartedInput),
SkillInvoked(SkillInvokedInput),
AppMentioned(AppMentionedInput),
AppUsed(AppUsedInput),

View File

@@ -8,7 +8,6 @@ pub use events::AppServerRpcTransport;
pub use facts::AppInvocation;
pub use facts::InvocationType;
pub use facts::SkillInvocation;
pub use facts::SubAgentThreadStartedInput;
pub use facts::TrackEventsContext;
pub use facts::build_track_events_context;

View File

@@ -15,7 +15,6 @@ use crate::events::codex_app_metadata;
use crate::events::codex_plugin_metadata;
use crate::events::codex_plugin_used_metadata;
use crate::events::plugin_state_event_type;
use crate::events::subagent_thread_started_event_request;
use crate::events::thread_source_name;
use crate::facts::AnalyticsFact;
use crate::facts::AppMentionedInput;
@@ -25,7 +24,6 @@ use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::PluginUsedInput;
use crate::facts::SkillInvokedInput;
use crate::facts::SubAgentThreadStartedInput;
use codex_app_server_protocol::ClientResponse;
use codex_app_server_protocol::InitializeParams;
use codex_git_utils::collect_git_info;
@@ -78,9 +76,6 @@ impl AnalyticsReducer {
}
AnalyticsFact::Notification(_notification) => {}
AnalyticsFact::Custom(input) => match input {
CustomAnalyticsFact::SubAgentThreadStarted(input) => {
self.ingest_subagent_thread_started(input, out);
}
CustomAnalyticsFact::SkillInvoked(input) => {
self.ingest_skill_invoked(input, out).await;
}
@@ -125,16 +120,6 @@ impl AnalyticsReducer {
);
}
fn ingest_subagent_thread_started(
&mut self,
input: SubAgentThreadStartedInput,
out: &mut Vec<TrackEventRequest>,
) {
out.push(TrackEventRequest::ThreadInitialized(
subagent_thread_started_event_request(input),
));
}
async fn ingest_skill_invoked(
&mut self,
input: SkillInvokedInput,

View File

@@ -1060,9 +1060,6 @@ mod tests {
items: Vec::new(),
status: codex_app_server_protocol::TurnStatus::Completed,
error: None,
started_at: None,
completed_at: Some(0),
duration_ms: Some(1),
},
})
}
@@ -1837,9 +1834,6 @@ mod tests {
items: Vec::new(),
status: codex_app_server_protocol::TurnStatus::Completed,
error: None,
started_at: None,
completed_at: Some(0),
duration_ms: None,
},
}
)

View File

@@ -17,12 +17,12 @@ clap = { workspace = true, features = ["derive"] }
codex-experimental-api-macros = { workspace = true }
codex-git-utils = { workspace = true }
codex-protocol = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-absolute-path = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_with = { workspace = true }
shlex = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
rmcp = { workspace = true, default-features = false, features = [

View File

@@ -800,7 +800,7 @@
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
"properties": {
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -819,15 +819,10 @@
}
],
"description": "Absolute file or directory path to watch."
},
"watchId": {
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
"type": "string"
}
},
"required": [
"path",
"watchId"
"path"
],
"type": "object"
},
@@ -1047,17 +1042,6 @@
"null"
]
},
"detail": {
"anyOf": [
{
"$ref": "#/definitions/McpServerStatusDetail"
},
{
"type": "null"
}
],
"description": "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted."
},
"limit": {
"description": "Optional page size; defaults to a server-defined value.",
"format": "uint32",
@@ -1224,25 +1208,6 @@
}
]
},
"McpResourceReadParams": {
"properties": {
"server": {
"type": "string"
},
"threadId": {
"type": "string"
},
"uri": {
"type": "string"
}
},
"required": [
"server",
"threadId",
"uri"
],
"type": "object"
},
"McpServerOauthLoginParams": {
"properties": {
"name": {
@@ -1270,13 +1235,6 @@
],
"type": "object"
},
"McpServerStatusDetail": {
"enum": [
"full",
"toolsAndAuthOnly"
],
"type": "string"
},
"MergeStrategy": {
"enum": [
"replace",
@@ -2857,48 +2815,6 @@
],
"type": "object"
},
"ThreadRealtimeStartTransport": {
"description": "EXPERIMENTAL - transport used by thread realtime.",
"oneOf": [
{
"properties": {
"type": {
"enum": [
"websocket"
],
"title": "WebsocketThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"type"
],
"title": "WebsocketThreadRealtimeStartTransport",
"type": "object"
},
{
"properties": {
"sdp": {
"description": "SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the realtime events data channel.",
"type": "string"
},
"type": {
"enum": [
"webrtc"
],
"title": "WebrtcThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"sdp",
"type"
],
"title": "WebrtcThreadRealtimeStartTransport",
"type": "object"
}
]
},
"ThreadResumeParams": {
"description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"properties": {
@@ -4504,30 +4420,6 @@
"title": "McpServerStatus/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"mcpServer/resource/read"
],
"title": "McpServer/resource/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/McpResourceReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "McpServer/resource/readRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -1013,7 +1013,7 @@
"type": "array"
},
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -1163,176 +1163,6 @@
],
"type": "object"
},
"GuardianApprovalReviewAction": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"cwd": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"command"
],
"title": "CommandGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"command",
"cwd",
"source",
"type"
],
"title": "CommandGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"argv": {
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"type": "string"
},
"program": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"execve"
],
"title": "ExecveGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"argv",
"cwd",
"program",
"source",
"type"
],
"title": "ExecveGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"cwd": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"applyPatch"
],
"title": "ApplyPatchGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"cwd",
"files",
"type"
],
"title": "ApplyPatchGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"host": {
"type": "string"
},
"port": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
},
"target": {
"type": "string"
},
"type": {
"enum": [
"networkAccess"
],
"title": "NetworkAccessGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"host",
"port",
"protocol",
"target",
"type"
],
"title": "NetworkAccessGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"connectorId": {
"type": [
"string",
"null"
]
},
"connectorName": {
"type": [
"string",
"null"
]
},
"server": {
"type": "string"
},
"toolName": {
"type": "string"
},
"toolTitle": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"mcpToolCall"
],
"title": "McpToolCallGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"server",
"toolName",
"type"
],
"title": "McpToolCallGuardianApprovalReviewAction",
"type": "object"
}
]
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
@@ -1343,13 +1173,6 @@
],
"type": "string"
},
"GuardianCommandSource": {
"enum": [
"shell",
"unifiedExec"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
@@ -1577,9 +1400,7 @@
"ItemGuardianApprovalReviewCompletedNotification": {
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -1594,7 +1415,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -1605,9 +1425,7 @@
"ItemGuardianApprovalReviewStartedNotification": {
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -1622,7 +1440,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -1736,7 +1553,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1856,15 +1672,6 @@
],
"type": "object"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
},
"NonSteerableTurnKind": {
"enum": [
"review",
@@ -2426,13 +2233,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -3185,6 +2985,81 @@
}
]
},
"ThreadJob": {
"properties": {
"createdAt": {
"format": "int64",
"type": "integer"
},
"cronExpression": {
"type": "string"
},
"id": {
"type": "string"
},
"lastRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"nextRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"prompt": {
"type": "string"
},
"runOnce": {
"type": "boolean"
}
},
"required": [
"createdAt",
"cronExpression",
"id",
"prompt",
"runOnce"
],
"type": "object"
},
"ThreadJobFiredNotification": {
"properties": {
"job": {
"$ref": "#/definitions/ThreadJob"
},
"threadId": {
"type": "string"
}
},
"required": [
"job",
"threadId"
],
"type": "object"
},
"ThreadJobUpdatedNotification": {
"properties": {
"jobs": {
"items": {
"$ref": "#/definitions/ThreadJob"
},
"type": "array"
},
"threadId": {
"type": "string"
}
},
"required": [
"jobs",
"threadId"
],
"type": "object"
},
"ThreadNameUpdatedNotification": {
"properties": {
"threadId": {
@@ -3304,22 +3179,6 @@
],
"type": "object"
},
"ThreadRealtimeSdpNotification": {
"description": "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.",
"properties": {
"sdp": {
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"sdp",
"threadId"
],
"type": "object"
},
"ThreadRealtimeStartedNotification": {
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
"properties": {
@@ -3549,22 +3408,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -3586,14 +3429,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}
@@ -4180,6 +4015,46 @@
"title": "Thread/name/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/updated"
],
"title": "Thread/job/updatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadJobUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/fired"
],
"title": "Thread/job/firedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadJobFiredNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/firedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -4943,26 +4818,6 @@
"title": "Thread/realtime/outputAudio/deltaNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/realtime/sdp"
],
"title": "Thread/realtime/sdpNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadRealtimeSdpNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/realtime/sdpNotification",
"type": "object"
},
{
"properties": {
"method": {

View File

@@ -1201,30 +1201,6 @@
"title": "McpServerStatus/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"mcpServer/resource/read"
],
"title": "McpServer/resource/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/McpResourceReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "McpServer/resource/readRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -3087,7 +3063,7 @@
"type": "string"
},
"protocol": {
"$ref": "#/definitions/v2/NetworkApprovalProtocol"
"$ref": "#/definitions/NetworkApprovalProtocol"
}
},
"required": [
@@ -3096,6 +3072,15 @@
],
"type": "object"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
},
"NetworkPolicyAmendment": {
"properties": {
"action": {
@@ -3572,6 +3557,46 @@
"title": "Thread/name/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/updated"
],
"title": "Thread/job/updatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadJobUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/fired"
],
"title": "Thread/job/firedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadJobFiredNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/firedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -4335,26 +4360,6 @@
"title": "Thread/realtime/outputAudio/deltaNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/realtime/sdp"
],
"title": "Thread/realtime/sdpNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadRealtimeSdpNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/realtime/sdpNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -7531,7 +7536,7 @@
"type": "array"
},
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -7801,7 +7806,7 @@
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
"properties": {
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -7828,6 +7833,25 @@
}
],
"description": "Absolute file or directory path to watch."
}
},
"required": [
"path"
],
"title": "FsWatchParams",
"type": "object"
},
"FsWatchResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Created watch handle returned by `fs/watch`.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Canonicalized path associated with the watch."
},
"watchId": {
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
@@ -7838,25 +7862,6 @@
"path",
"watchId"
],
"title": "FsWatchParams",
"type": "object"
},
"FsWatchResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/watch`.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
}
],
"description": "Canonicalized path associated with the watch."
}
},
"required": [
"path"
],
"title": "FsWatchResponse",
"type": "object"
},
@@ -8112,176 +8117,6 @@
],
"type": "object"
},
"GuardianApprovalReviewAction": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"cwd": {
"type": "string"
},
"source": {
"$ref": "#/definitions/v2/GuardianCommandSource"
},
"type": {
"enum": [
"command"
],
"title": "CommandGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"command",
"cwd",
"source",
"type"
],
"title": "CommandGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"argv": {
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"type": "string"
},
"program": {
"type": "string"
},
"source": {
"$ref": "#/definitions/v2/GuardianCommandSource"
},
"type": {
"enum": [
"execve"
],
"title": "ExecveGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"argv",
"cwd",
"program",
"source",
"type"
],
"title": "ExecveGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"cwd": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"applyPatch"
],
"title": "ApplyPatchGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"cwd",
"files",
"type"
],
"title": "ApplyPatchGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"host": {
"type": "string"
},
"port": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"protocol": {
"$ref": "#/definitions/v2/NetworkApprovalProtocol"
},
"target": {
"type": "string"
},
"type": {
"enum": [
"networkAccess"
],
"title": "NetworkAccessGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"host",
"port",
"protocol",
"target",
"type"
],
"title": "NetworkAccessGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"connectorId": {
"type": [
"string",
"null"
]
},
"connectorName": {
"type": [
"string",
"null"
]
},
"server": {
"type": "string"
},
"toolName": {
"type": "string"
},
"toolTitle": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"mcpToolCall"
],
"title": "McpToolCallGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"server",
"toolName",
"type"
],
"title": "McpToolCallGuardianApprovalReviewAction",
"type": "object"
}
]
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
@@ -8292,13 +8127,6 @@
],
"type": "string"
},
"GuardianCommandSource": {
"enum": [
"shell",
"unifiedExec"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
@@ -8561,9 +8389,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/v2/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/v2/GuardianApprovalReview"
},
@@ -8578,7 +8404,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -8591,9 +8416,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/v2/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/v2/GuardianApprovalReview"
},
@@ -8608,7 +8431,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -8648,17 +8470,6 @@
"null"
]
},
"detail": {
"anyOf": [
{
"$ref": "#/definitions/v2/McpServerStatusDetail"
},
{
"type": "null"
}
],
"description": "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted."
},
"limit": {
"description": "Optional page size; defaults to a server-defined value.",
"format": "uint32",
@@ -8984,43 +8795,6 @@
],
"type": "string"
},
"McpResourceReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"server": {
"type": "string"
},
"threadId": {
"type": "string"
},
"uri": {
"type": "string"
}
},
"required": [
"server",
"threadId",
"uri"
],
"title": "McpResourceReadParams",
"type": "object"
},
"McpResourceReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"contents": {
"items": {
"$ref": "#/definitions/v2/ResourceContent"
},
"type": "array"
}
},
"required": [
"contents"
],
"title": "McpResourceReadResponse",
"type": "object"
},
"McpServerOauthLoginCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -9136,13 +8910,6 @@
],
"type": "object"
},
"McpServerStatusDetail": {
"enum": [
"full",
"toolsAndAuthOnly"
],
"type": "string"
},
"McpServerStatusUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -9204,7 +8971,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -9308,13 +9074,6 @@
},
"Model": {
"properties": {
"additionalSpeedTiers": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"availabilityNux": {
"anyOf": [
{
@@ -9530,15 +9289,6 @@
],
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
},
"NetworkDomainPermission": {
"enum": [
"allow",
@@ -9580,12 +9330,6 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",
@@ -10756,57 +10500,6 @@
],
"type": "object"
},
"ResourceContent": {
"anyOf": [
{
"properties": {
"_meta": true,
"mimeType": {
"type": [
"string",
"null"
]
},
"text": {
"type": "string"
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"text",
"uri"
],
"type": "object"
},
{
"properties": {
"_meta": true,
"blob": {
"type": "string"
},
"mimeType": {
"type": [
"string",
"null"
]
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"blob",
"uri"
],
"type": "object"
}
],
"description": "Contents returned when reading a resource from an MCP server."
},
"ResourceTemplate": {
"description": "A template description for resources available on the server.",
"properties": {
@@ -12350,13 +12043,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -13314,6 +13000,85 @@
}
]
},
"ThreadJob": {
"properties": {
"createdAt": {
"format": "int64",
"type": "integer"
},
"cronExpression": {
"type": "string"
},
"id": {
"type": "string"
},
"lastRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"nextRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"prompt": {
"type": "string"
},
"runOnce": {
"type": "boolean"
}
},
"required": [
"createdAt",
"cronExpression",
"id",
"prompt",
"runOnce"
],
"type": "object"
},
"ThreadJobFiredNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"job": {
"$ref": "#/definitions/v2/ThreadJob"
},
"threadId": {
"type": "string"
}
},
"required": [
"job",
"threadId"
],
"title": "ThreadJobFiredNotification",
"type": "object"
},
"ThreadJobUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"jobs": {
"items": {
"$ref": "#/definitions/v2/ThreadJob"
},
"type": "array"
},
"threadId": {
"type": "string"
}
},
"required": [
"jobs",
"threadId"
],
"title": "ThreadJobUpdatedNotification",
"type": "object"
},
"ThreadListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -13682,66 +13447,6 @@
"title": "ThreadRealtimeOutputAudioDeltaNotification",
"type": "object"
},
"ThreadRealtimeSdpNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.",
"properties": {
"sdp": {
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"sdp",
"threadId"
],
"title": "ThreadRealtimeSdpNotification",
"type": "object"
},
"ThreadRealtimeStartTransport": {
"description": "EXPERIMENTAL - transport used by thread realtime.",
"oneOf": [
{
"properties": {
"type": {
"enum": [
"websocket"
],
"title": "WebsocketThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"type"
],
"title": "WebsocketThreadRealtimeStartTransport",
"type": "object"
},
{
"properties": {
"sdp": {
"description": "SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the realtime events data channel.",
"type": "string"
},
"type": {
"enum": [
"webrtc"
],
"title": "WebrtcThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"sdp",
"type"
],
"title": "WebrtcThreadRealtimeStartTransport",
"type": "object"
}
]
},
"ThreadRealtimeStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
@@ -14553,22 +14258,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -14590,14 +14279,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/v2/TurnStatus"
}

View File

@@ -1776,30 +1776,6 @@
"title": "McpServerStatus/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"mcpServer/resource/read"
],
"title": "McpServer/resource/readRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/McpResourceReadParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "McpServer/resource/readRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -4179,7 +4155,7 @@
"type": "array"
},
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -4449,7 +4425,7 @@
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
"properties": {
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},
@@ -4476,6 +4452,25 @@
}
],
"description": "Absolute file or directory path to watch."
}
},
"required": [
"path"
],
"title": "FsWatchParams",
"type": "object"
},
"FsWatchResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Created watch handle returned by `fs/watch`.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Canonicalized path associated with the watch."
},
"watchId": {
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
@@ -4486,25 +4481,6 @@
"path",
"watchId"
],
"title": "FsWatchParams",
"type": "object"
},
"FsWatchResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful response for `fs/watch`.",
"properties": {
"path": {
"allOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
}
],
"description": "Canonicalized path associated with the watch."
}
},
"required": [
"path"
],
"title": "FsWatchResponse",
"type": "object"
},
@@ -4871,176 +4847,6 @@
],
"type": "object"
},
"GuardianApprovalReviewAction": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"cwd": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"command"
],
"title": "CommandGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"command",
"cwd",
"source",
"type"
],
"title": "CommandGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"argv": {
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"type": "string"
},
"program": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"execve"
],
"title": "ExecveGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"argv",
"cwd",
"program",
"source",
"type"
],
"title": "ExecveGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"cwd": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"applyPatch"
],
"title": "ApplyPatchGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"cwd",
"files",
"type"
],
"title": "ApplyPatchGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"host": {
"type": "string"
},
"port": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
},
"target": {
"type": "string"
},
"type": {
"enum": [
"networkAccess"
],
"title": "NetworkAccessGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"host",
"port",
"protocol",
"target",
"type"
],
"title": "NetworkAccessGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"connectorId": {
"type": [
"string",
"null"
]
},
"connectorName": {
"type": [
"string",
"null"
]
},
"server": {
"type": "string"
},
"toolName": {
"type": "string"
},
"toolTitle": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"mcpToolCall"
],
"title": "McpToolCallGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"server",
"toolName",
"type"
],
"title": "McpToolCallGuardianApprovalReviewAction",
"type": "object"
}
]
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
@@ -5051,13 +4857,6 @@
],
"type": "string"
},
"GuardianCommandSource": {
"enum": [
"shell",
"unifiedExec"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
@@ -5364,9 +5163,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -5381,7 +5178,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -5394,9 +5190,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -5411,7 +5205,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",
@@ -5451,17 +5244,6 @@
"null"
]
},
"detail": {
"anyOf": [
{
"$ref": "#/definitions/McpServerStatusDetail"
},
{
"type": "null"
}
],
"description": "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted."
},
"limit": {
"description": "Optional page size; defaults to a server-defined value.",
"format": "uint32",
@@ -5787,43 +5569,6 @@
],
"type": "string"
},
"McpResourceReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"server": {
"type": "string"
},
"threadId": {
"type": "string"
},
"uri": {
"type": "string"
}
},
"required": [
"server",
"threadId",
"uri"
],
"title": "McpResourceReadParams",
"type": "object"
},
"McpResourceReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"contents": {
"items": {
"$ref": "#/definitions/ResourceContent"
},
"type": "array"
}
},
"required": [
"contents"
],
"title": "McpResourceReadResponse",
"type": "object"
},
"McpServerOauthLoginCompletedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -5939,13 +5684,6 @@
],
"type": "object"
},
"McpServerStatusDetail": {
"enum": [
"full",
"toolsAndAuthOnly"
],
"type": "string"
},
"McpServerStatusUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -6007,7 +5745,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -6111,13 +5848,6 @@
},
"Model": {
"properties": {
"additionalSpeedTiers": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"availabilityNux": {
"anyOf": [
{
@@ -6333,15 +6063,6 @@
],
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
},
"NetworkDomainPermission": {
"enum": [
"allow",
@@ -6383,12 +6104,6 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",
@@ -7559,57 +7274,6 @@
],
"type": "object"
},
"ResourceContent": {
"anyOf": [
{
"properties": {
"_meta": true,
"mimeType": {
"type": [
"string",
"null"
]
},
"text": {
"type": "string"
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"text",
"uri"
],
"type": "object"
},
{
"properties": {
"_meta": true,
"blob": {
"type": "string"
},
"mimeType": {
"type": [
"string",
"null"
]
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"blob",
"uri"
],
"type": "object"
}
],
"description": "Contents returned when reading a resource from an MCP server."
},
"ResourceTemplate": {
"description": "A template description for resources available on the server.",
"properties": {
@@ -8680,6 +8344,46 @@
"title": "Thread/name/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/updated"
],
"title": "Thread/job/updatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadJobUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/job/fired"
],
"title": "Thread/job/firedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadJobFiredNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/job/firedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -9443,26 +9147,6 @@
"title": "Thread/realtime/outputAudio/deltaNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/realtime/sdp"
],
"title": "Thread/realtime/sdpNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadRealtimeSdpNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/realtime/sdpNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -10205,13 +9889,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -11169,6 +10846,85 @@
}
]
},
"ThreadJob": {
"properties": {
"createdAt": {
"format": "int64",
"type": "integer"
},
"cronExpression": {
"type": "string"
},
"id": {
"type": "string"
},
"lastRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"nextRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"prompt": {
"type": "string"
},
"runOnce": {
"type": "boolean"
}
},
"required": [
"createdAt",
"cronExpression",
"id",
"prompt",
"runOnce"
],
"type": "object"
},
"ThreadJobFiredNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"job": {
"$ref": "#/definitions/ThreadJob"
},
"threadId": {
"type": "string"
}
},
"required": [
"job",
"threadId"
],
"title": "ThreadJobFiredNotification",
"type": "object"
},
"ThreadJobUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"jobs": {
"items": {
"$ref": "#/definitions/ThreadJob"
},
"type": "array"
},
"threadId": {
"type": "string"
}
},
"required": [
"jobs",
"threadId"
],
"title": "ThreadJobUpdatedNotification",
"type": "object"
},
"ThreadListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -11537,66 +11293,6 @@
"title": "ThreadRealtimeOutputAudioDeltaNotification",
"type": "object"
},
"ThreadRealtimeSdpNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.",
"properties": {
"sdp": {
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"sdp",
"threadId"
],
"title": "ThreadRealtimeSdpNotification",
"type": "object"
},
"ThreadRealtimeStartTransport": {
"description": "EXPERIMENTAL - transport used by thread realtime.",
"oneOf": [
{
"properties": {
"type": {
"enum": [
"websocket"
],
"title": "WebsocketThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"type"
],
"title": "WebsocketThreadRealtimeStartTransport",
"type": "object"
},
{
"properties": {
"sdp": {
"description": "SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the realtime events data channel.",
"type": "string"
},
"type": {
"enum": [
"webrtc"
],
"title": "WebrtcThreadRealtimeStartTransportType",
"type": "string"
}
},
"required": [
"sdp",
"type"
],
"title": "WebrtcThreadRealtimeStartTransport",
"type": "object"
}
]
},
"ThreadRealtimeStartedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.",
@@ -12408,22 +12104,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -12445,14 +12125,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -1,14 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
"user",
"guardian_subagent"
],
"type": "string"
},
"AskForApproval": {
"oneOf": [
{
@@ -151,12 +143,6 @@
"null"
]
},
"dangerFullAccessDenylistOnly": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowAllUnixSockets": {
"type": [
"boolean",

View File

@@ -16,7 +16,7 @@
"type": "array"
},
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},

View File

@@ -3,7 +3,7 @@
"description": "Stop filesystem watch notifications for a prior `fs/watch`.",
"properties": {
"watchId": {
"description": "Watch identifier previously provided to `fs/watch`.",
"description": "Watch identifier returned by `fs/watch`.",
"type": "string"
}
},

View File

@@ -15,15 +15,10 @@
}
],
"description": "Absolute file or directory path to watch."
},
"watchId": {
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
"type": "string"
}
},
"required": [
"path",
"watchId"
"path"
],
"title": "FsWatchParams",
"type": "object"

View File

@@ -6,7 +6,7 @@
"type": "string"
}
},
"description": "Successful response for `fs/watch`.",
"description": "Created watch handle returned by `fs/watch`.",
"properties": {
"path": {
"allOf": [
@@ -15,10 +15,15 @@
}
],
"description": "Canonicalized path associated with the watch."
},
"watchId": {
"description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.",
"type": "string"
}
},
"required": [
"path"
"path",
"watchId"
],
"title": "FsWatchResponse",
"type": "object"

View File

@@ -294,7 +294,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"

View File

@@ -37,176 +37,6 @@
],
"type": "object"
},
"GuardianApprovalReviewAction": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"cwd": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"command"
],
"title": "CommandGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"command",
"cwd",
"source",
"type"
],
"title": "CommandGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"argv": {
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"type": "string"
},
"program": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"execve"
],
"title": "ExecveGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"argv",
"cwd",
"program",
"source",
"type"
],
"title": "ExecveGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"cwd": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"applyPatch"
],
"title": "ApplyPatchGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"cwd",
"files",
"type"
],
"title": "ApplyPatchGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"host": {
"type": "string"
},
"port": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
},
"target": {
"type": "string"
},
"type": {
"enum": [
"networkAccess"
],
"title": "NetworkAccessGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"host",
"port",
"protocol",
"target",
"type"
],
"title": "NetworkAccessGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"connectorId": {
"type": [
"string",
"null"
]
},
"connectorName": {
"type": [
"string",
"null"
]
},
"server": {
"type": "string"
},
"toolName": {
"type": "string"
},
"toolTitle": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"mcpToolCall"
],
"title": "McpToolCallGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"server",
"toolName",
"type"
],
"title": "McpToolCallGuardianApprovalReviewAction",
"type": "object"
}
]
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
@@ -217,13 +47,6 @@
],
"type": "string"
},
"GuardianCommandSource": {
"enum": [
"shell",
"unifiedExec"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
@@ -232,22 +55,11 @@
"high"
],
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
}
},
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -262,7 +74,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",

View File

@@ -37,176 +37,6 @@
],
"type": "object"
},
"GuardianApprovalReviewAction": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"cwd": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"command"
],
"title": "CommandGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"command",
"cwd",
"source",
"type"
],
"title": "CommandGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"argv": {
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"type": "string"
},
"program": {
"type": "string"
},
"source": {
"$ref": "#/definitions/GuardianCommandSource"
},
"type": {
"enum": [
"execve"
],
"title": "ExecveGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"argv",
"cwd",
"program",
"source",
"type"
],
"title": "ExecveGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"cwd": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"applyPatch"
],
"title": "ApplyPatchGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"cwd",
"files",
"type"
],
"title": "ApplyPatchGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"host": {
"type": "string"
},
"port": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
},
"target": {
"type": "string"
},
"type": {
"enum": [
"networkAccess"
],
"title": "NetworkAccessGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"host",
"port",
"protocol",
"target",
"type"
],
"title": "NetworkAccessGuardianApprovalReviewAction",
"type": "object"
},
{
"properties": {
"connectorId": {
"type": [
"string",
"null"
]
},
"connectorName": {
"type": [
"string",
"null"
]
},
"server": {
"type": "string"
},
"toolName": {
"type": "string"
},
"toolTitle": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"mcpToolCall"
],
"title": "McpToolCallGuardianApprovalReviewActionType",
"type": "string"
}
},
"required": [
"server",
"toolName",
"type"
],
"title": "McpToolCallGuardianApprovalReviewAction",
"type": "object"
}
]
},
"GuardianApprovalReviewStatus": {
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
"enum": [
@@ -217,13 +47,6 @@
],
"type": "string"
},
"GuardianCommandSource": {
"enum": [
"shell",
"unifiedExec"
],
"type": "string"
},
"GuardianRiskLevel": {
"description": "[UNSTABLE] Risk level assigned by guardian approval review.",
"enum": [
@@ -232,22 +55,11 @@
"high"
],
"type": "string"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5Tcp",
"socks5Udp"
],
"type": "string"
}
},
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.\n\nTODO(ccunningham): Attach guardian review state to the reviewed tool item's lifecycle instead of sending separate standalone review notifications so the app-server API can persist and replay review state via `thread/read`.",
"properties": {
"action": {
"$ref": "#/definitions/GuardianApprovalReviewAction"
},
"action": true,
"review": {
"$ref": "#/definitions/GuardianApprovalReview"
},
@@ -262,7 +74,6 @@
}
},
"required": [
"action",
"review",
"targetItemId",
"threadId",

View File

@@ -294,7 +294,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"

View File

@@ -1,14 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"McpServerStatusDetail": {
"enum": [
"full",
"toolsAndAuthOnly"
],
"type": "string"
}
},
"properties": {
"cursor": {
"description": "Opaque pagination cursor returned by a previous call.",
@@ -17,17 +8,6 @@
"null"
]
},
"detail": {
"anyOf": [
{
"$ref": "#/definitions/McpServerStatusDetail"
},
{
"type": "null"
}
],
"description": "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted."
},
"limit": {
"description": "Optional page size; defaults to a server-defined value.",
"format": "uint32",

View File

@@ -1,21 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"server": {
"type": "string"
},
"threadId": {
"type": "string"
},
"uri": {
"type": "string"
}
},
"required": [
"server",
"threadId",
"uri"
],
"title": "McpResourceReadParams",
"type": "object"
}

View File

@@ -1,69 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ResourceContent": {
"anyOf": [
{
"properties": {
"_meta": true,
"mimeType": {
"type": [
"string",
"null"
]
},
"text": {
"type": "string"
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"text",
"uri"
],
"type": "object"
},
{
"properties": {
"_meta": true,
"blob": {
"type": "string"
},
"mimeType": {
"type": [
"string",
"null"
]
},
"uri": {
"description": "The URI of this resource.",
"type": "string"
}
},
"required": [
"blob",
"uri"
],
"type": "object"
}
],
"description": "Contents returned when reading a resource from an MCP server."
}
},
"properties": {
"contents": {
"items": {
"$ref": "#/definitions/ResourceContent"
},
"type": "array"
}
},
"required": [
"contents"
],
"title": "McpResourceReadResponse",
"type": "object"
}

View File

@@ -22,13 +22,6 @@
},
"Model": {
"properties": {
"additionalSpeedTiers": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"availabilityNux": {
"anyOf": [
{

View File

@@ -430,7 +430,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1268,22 +1267,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1305,14 +1288,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -518,7 +518,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1043,13 +1042,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1857,22 +1849,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1894,14 +1870,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -0,0 +1,61 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ThreadJob": {
"properties": {
"createdAt": {
"format": "int64",
"type": "integer"
},
"cronExpression": {
"type": "string"
},
"id": {
"type": "string"
},
"lastRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"nextRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"prompt": {
"type": "string"
},
"runOnce": {
"type": "boolean"
}
},
"required": [
"createdAt",
"cronExpression",
"id",
"prompt",
"runOnce"
],
"type": "object"
}
},
"properties": {
"job": {
"$ref": "#/definitions/ThreadJob"
},
"threadId": {
"type": "string"
}
},
"required": [
"job",
"threadId"
],
"title": "ThreadJobFiredNotification",
"type": "object"
}

View File

@@ -0,0 +1,64 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ThreadJob": {
"properties": {
"createdAt": {
"format": "int64",
"type": "integer"
},
"cronExpression": {
"type": "string"
},
"id": {
"type": "string"
},
"lastRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"nextRunAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"prompt": {
"type": "string"
},
"runOnce": {
"type": "boolean"
}
},
"required": [
"createdAt",
"cronExpression",
"id",
"prompt",
"runOnce"
],
"type": "object"
}
},
"properties": {
"jobs": {
"items": {
"$ref": "#/definitions/ThreadJob"
},
"type": "array"
},
"threadId": {
"type": "string"
}
},
"required": [
"jobs",
"threadId"
],
"title": "ThreadJobUpdatedNotification",
"type": "object"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -1,18 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.",
"properties": {
"sdp": {
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"sdp",
"threadId"
],
"title": "ThreadRealtimeSdpNotification",
"type": "object"
}

View File

@@ -518,7 +518,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1043,13 +1042,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1857,22 +1849,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1894,14 +1870,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -518,7 +518,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1043,13 +1042,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1857,22 +1849,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1894,14 +1870,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -456,7 +456,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -801,13 +800,6 @@
"description": "Whether the thread is ephemeral and should not be materialized on disk.",
"type": "boolean"
},
"forkedFromId": {
"description": "Source thread id when this thread was created by forking another thread.",
"type": [
"string",
"null"
]
},
"gitInfo": {
"anyOf": [
{
@@ -1615,22 +1607,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1652,14 +1628,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -430,7 +430,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1268,22 +1267,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1305,14 +1288,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -430,7 +430,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1268,22 +1267,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1305,14 +1288,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

View File

@@ -430,7 +430,6 @@
},
"McpToolCallResult": {
"properties": {
"_meta": true,
"content": {
"items": true,
"type": "array"
@@ -1268,22 +1267,6 @@
},
"Turn": {
"properties": {
"completedAt": {
"description": "Unix timestamp (in seconds) when the turn completed.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"durationMs": {
"description": "Duration between turn start and completion in milliseconds, if known.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"error": {
"anyOf": [
{
@@ -1305,14 +1288,6 @@
},
"type": "array"
},
"startedAt": {
"description": "Unix timestamp (in seconds) when the turn started.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"status": {
"$ref": "#/definitions/TurnStatus"
}

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "./serde_json/JsonValue";
/**
* Contents returned when reading a resource from an MCP server.
*/
export type ResourceContent = {
/**
* The URI of this resource.
*/
uri: string, mimeType?: string, text: string, _meta?: JsonValue, } | {
/**
* The URI of this resource.
*/
uri: string, mimeType?: string, blob: string, _meta?: JsonValue, };

View File

@@ -36,12 +36,13 @@ import type { SkillsChangedNotification } from "./v2/SkillsChangedNotification";
import type { TerminalInteractionNotification } from "./v2/TerminalInteractionNotification";
import type { ThreadArchivedNotification } from "./v2/ThreadArchivedNotification";
import type { ThreadClosedNotification } from "./v2/ThreadClosedNotification";
import type { ThreadJobFiredNotification } from "./v2/ThreadJobFiredNotification";
import type { ThreadJobUpdatedNotification } from "./v2/ThreadJobUpdatedNotification";
import type { ThreadNameUpdatedNotification } from "./v2/ThreadNameUpdatedNotification";
import type { ThreadRealtimeClosedNotification } from "./v2/ThreadRealtimeClosedNotification";
import type { ThreadRealtimeErrorNotification } from "./v2/ThreadRealtimeErrorNotification";
import type { ThreadRealtimeItemAddedNotification } from "./v2/ThreadRealtimeItemAddedNotification";
import type { ThreadRealtimeOutputAudioDeltaNotification } from "./v2/ThreadRealtimeOutputAudioDeltaNotification";
import type { ThreadRealtimeSdpNotification } from "./v2/ThreadRealtimeSdpNotification";
import type { ThreadRealtimeStartedNotification } from "./v2/ThreadRealtimeStartedNotification";
import type { ThreadRealtimeTranscriptUpdatedNotification } from "./v2/ThreadRealtimeTranscriptUpdatedNotification";
import type { ThreadStartedNotification } from "./v2/ThreadStartedNotification";
@@ -58,4 +59,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
/**
* Notification sent from the server to the client.
*/
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/job/updated", "params": ThreadJobUpdatedNotification } | { "method": "thread/job/fired", "params": ThreadJobFiredNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcriptUpdated", "params": ThreadRealtimeTranscriptUpdatedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };

View File

@@ -55,7 +55,6 @@ export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSumm
export type { ReasoningSummary } from "./ReasoningSummary";
export type { RequestId } from "./RequestId";
export type { Resource } from "./Resource";
export type { ResourceContent } from "./ResourceContent";
export type { ResourceTemplate } from "./ResourceTemplate";
export type { ResponseItem } from "./ResponseItem";
export type { ReviewDecision } from "./ReviewDecision";

View File

@@ -8,7 +8,7 @@ import type { AbsolutePathBuf } from "../AbsolutePathBuf";
*/
export type FsChangedNotification = {
/**
* Watch identifier previously provided to `fs/watch`.
* Watch identifier returned by `fs/watch`.
*/
watchId: string,
/**

View File

@@ -7,6 +7,6 @@
*/
export type FsUnwatchParams = {
/**
* Watch identifier previously provided to `fs/watch`.
* Watch identifier returned by `fs/watch`.
*/
watchId: string, };

View File

@@ -7,10 +7,6 @@ import type { AbsolutePathBuf } from "../AbsolutePathBuf";
* Start filesystem watch notifications for an absolute path.
*/
export type FsWatchParams = {
/**
* Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
*/
watchId: string,
/**
* Absolute file or directory path to watch.
*/

View File

@@ -4,9 +4,13 @@
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
/**
* Successful response for `fs/watch`.
* Created watch handle returned by `fs/watch`.
*/
export type FsWatchResponse = {
/**
* Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
*/
watchId: string,
/**
* Canonicalized path associated with the watch.
*/

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GuardianCommandSource } from "./GuardianCommandSource";
import type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol";
export type GuardianApprovalReviewAction = { "type": "command", source: GuardianCommandSource, command: string, cwd: string, } | { "type": "execve", source: GuardianCommandSource, program: string, argv: Array<string>, cwd: string, } | { "type": "applyPatch", cwd: string, files: Array<string>, } | { "type": "networkAccess", target: string, host: string, protocol: NetworkApprovalProtocol, port: number, } | { "type": "mcpToolCall", server: string, toolName: string, connectorId: string | null, connectorName: string | null, toolTitle: string | null, };

View File

@@ -1,8 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "../serde_json/JsonValue";
import type { GuardianApprovalReview } from "./GuardianApprovalReview";
import type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
/**
* [UNSTABLE] Temporary notification payload for guardian automatic approval
@@ -12,4 +12,4 @@ import type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewActio
* lifecycle instead of sending separate standalone review notifications so the
* app-server API can persist and replay review state via `thread/read`.
*/
export type ItemGuardianApprovalReviewCompletedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: GuardianApprovalReviewAction, };
export type ItemGuardianApprovalReviewCompletedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: JsonValue | null, };

View File

@@ -1,8 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "../serde_json/JsonValue";
import type { GuardianApprovalReview } from "./GuardianApprovalReview";
import type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
/**
* [UNSTABLE] Temporary notification payload for guardian automatic approval
@@ -12,4 +12,4 @@ import type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewActio
* lifecycle instead of sending separate standalone review notifications so the
* app-server API can persist and replay review state via `thread/read`.
*/
export type ItemGuardianApprovalReviewStartedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: GuardianApprovalReviewAction, };
export type ItemGuardianApprovalReviewStartedNotification = { threadId: string, turnId: string, targetItemId: string, review: GuardianApprovalReview, action: JsonValue | null, };

View File

@@ -1,7 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { McpServerStatusDetail } from "./McpServerStatusDetail";
export type ListMcpServerStatusParams = {
/**
@@ -11,9 +10,4 @@ cursor?: string | null,
/**
* Optional page size; defaults to a server-defined value.
*/
limit?: number | null,
/**
* Controls how much MCP inventory data to fetch for each server.
* Defaults to `Full` when omitted.
*/
detail?: McpServerStatusDetail | null, };
limit?: number | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type McpResourceReadParams = { threadId: string, server: string, uri: string, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ResourceContent } from "../ResourceContent";
export type McpResourceReadResponse = { contents: Array<ResourceContent>, };

View File

@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "../serde_json/JsonValue";
export type McpToolCallResult = { content: Array<JsonValue>, structuredContent: JsonValue | null, _meta: JsonValue | null, };
export type McpToolCallResult = { content: Array<JsonValue>, structuredContent: JsonValue | null, };

View File

@@ -7,4 +7,4 @@ import type { ModelAvailabilityNux } from "./ModelAvailabilityNux";
import type { ModelUpgradeInfo } from "./ModelUpgradeInfo";
import type { ReasoningEffortOption } from "./ReasoningEffortOption";
export type Model = { id: string, model: string, upgrade: string | null, upgradeInfo: ModelUpgradeInfo | null, availabilityNux: ModelAvailabilityNux | null, displayName: string, description: string, hidden: boolean, supportedReasoningEfforts: Array<ReasoningEffortOption>, defaultReasoningEffort: ReasoningEffort, inputModalities: Array<InputModality>, supportsPersonality: boolean, additionalSpeedTiers: Array<string>, isDefault: boolean, };
export type Model = { id: string, model: string, upgrade: string | null, upgradeInfo: ModelUpgradeInfo | null, availabilityNux: ModelAvailabilityNux | null, displayName: string, description: string, hidden: boolean, supportedReasoningEfforts: Array<ReasoningEffortOption>, defaultReasoningEffort: ReasoningEffort, inputModalities: Array<InputModality>, supportsPersonality: boolean, isDefault: boolean, };

View File

@@ -29,4 +29,4 @@ unixSockets: { [key in string]?: NetworkUnixSocketPermission } | null,
/**
* Legacy compatibility view derived from `unix_sockets`.
*/
allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, dangerFullAccessDenylistOnly: boolean | null, };
allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };

View File

@@ -7,10 +7,6 @@ import type { ThreadStatus } from "./ThreadStatus";
import type { Turn } from "./Turn";
export type Thread = { id: string,
/**
* Source thread id when this thread was created by forking another thread.
*/
forkedFromId: string | null,
/**
* Usually the first user message in the thread, if available.
*/

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadJob = { id: string, cronExpression: string, prompt: string, runOnce: boolean, createdAt: number, nextRunAt: number | null, lastRunAt: number | null, };

View File

@@ -1,5 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadJob } from "./ThreadJob";
export type McpServerStatusDetail = "full" | "toolsAndAuthOnly";
export type ThreadJobFiredNotification = { threadId: string, job: ThreadJob, };

View File

@@ -1,5 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadJob } from "./ThreadJob";
export type GuardianCommandSource = "shell" | "unifiedExec";
export type ThreadJobUpdatedNotification = { threadId: string, jobs: Array<ThreadJob>, };

View File

@@ -1,8 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.
*/
export type ThreadRealtimeSdpNotification = { threadId: string, sdp: string, };

View File

@@ -1,13 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* EXPERIMENTAL - transport used by thread realtime.
*/
export type ThreadRealtimeStartTransport = { "type": "websocket" } | { "type": "webrtc",
/**
* SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the
* realtime events data channel.
*/
sdp: string, };

View File

@@ -15,16 +15,4 @@ items: Array<ThreadItem>, status: TurnStatus,
/**
* Only populated when the Turn's status is failed.
*/
error: TurnError | null,
/**
* Unix timestamp (in seconds) when the turn started.
*/
startedAt: number | null,
/**
* Unix timestamp (in seconds) when the turn completed.
*/
completedAt: number | null,
/**
* Duration between turn start and completion in milliseconds, if known.
*/
durationMs: number | null, };
error: TurnError | null, };

View File

@@ -123,9 +123,7 @@ export type { GetAccountResponse } from "./GetAccountResponse";
export type { GitInfo } from "./GitInfo";
export type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
export type { GuardianApprovalReview } from "./GuardianApprovalReview";
export type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
export type { GuardianCommandSource } from "./GuardianCommandSource";
export type { GuardianRiskLevel } from "./GuardianRiskLevel";
export type { HookCompletedNotification } from "./HookCompletedNotification";
export type { HookEventName } from "./HookEventName";
@@ -172,8 +170,6 @@ export type { McpElicitationTitledSingleSelectEnumSchema } from "./McpElicitatio
export type { McpElicitationUntitledEnumItems } from "./McpElicitationUntitledEnumItems";
export type { McpElicitationUntitledMultiSelectEnumSchema } from "./McpElicitationUntitledMultiSelectEnumSchema";
export type { McpElicitationUntitledSingleSelectEnumSchema } from "./McpElicitationUntitledSingleSelectEnumSchema";
export type { McpResourceReadParams } from "./McpResourceReadParams";
export type { McpResourceReadResponse } from "./McpResourceReadResponse";
export type { McpServerElicitationAction } from "./McpServerElicitationAction";
export type { McpServerElicitationRequestParams } from "./McpServerElicitationRequestParams";
export type { McpServerElicitationRequestResponse } from "./McpServerElicitationRequestResponse";
@@ -183,7 +179,6 @@ export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse"
export type { McpServerRefreshResponse } from "./McpServerRefreshResponse";
export type { McpServerStartupState } from "./McpServerStartupState";
export type { McpServerStatus } from "./McpServerStatus";
export type { McpServerStatusDetail } from "./McpServerStatusDetail";
export type { McpServerStatusUpdatedNotification } from "./McpServerStatusUpdatedNotification";
export type { McpToolCallError } from "./McpToolCallError";
export type { McpToolCallProgressNotification } from "./McpToolCallProgressNotification";
@@ -279,6 +274,9 @@ export type { ThreadCompactStartResponse } from "./ThreadCompactStartResponse";
export type { ThreadForkParams } from "./ThreadForkParams";
export type { ThreadForkResponse } from "./ThreadForkResponse";
export type { ThreadItem } from "./ThreadItem";
export type { ThreadJob } from "./ThreadJob";
export type { ThreadJobFiredNotification } from "./ThreadJobFiredNotification";
export type { ThreadJobUpdatedNotification } from "./ThreadJobUpdatedNotification";
export type { ThreadListParams } from "./ThreadListParams";
export type { ThreadListResponse } from "./ThreadListResponse";
export type { ThreadLoadedListParams } from "./ThreadLoadedListParams";
@@ -294,8 +292,6 @@ export type { ThreadRealtimeClosedNotification } from "./ThreadRealtimeClosedNot
export type { ThreadRealtimeErrorNotification } from "./ThreadRealtimeErrorNotification";
export type { ThreadRealtimeItemAddedNotification } from "./ThreadRealtimeItemAddedNotification";
export type { ThreadRealtimeOutputAudioDeltaNotification } from "./ThreadRealtimeOutputAudioDeltaNotification";
export type { ThreadRealtimeSdpNotification } from "./ThreadRealtimeSdpNotification";
export type { ThreadRealtimeStartTransport } from "./ThreadRealtimeStartTransport";
export type { ThreadRealtimeStartedNotification } from "./ThreadRealtimeStartedNotification";
export type { ThreadRealtimeTranscriptUpdatedNotification } from "./ThreadRealtimeTranscriptUpdatedNotification";
export type { ThreadResumeParams } from "./ThreadResumeParams";

View File

@@ -15,7 +15,6 @@ pub use export::generate_ts_with_options;
pub use export::generate_types;
pub use jsonrpc_lite::*;
pub use protocol::common::*;
pub use protocol::item_builders::*;
pub use protocol::thread_history::*;
pub use protocol::v1::ApplyPatchApprovalParams;
pub use protocol::v1::ApplyPatchApprovalResponse;

View File

@@ -317,6 +317,21 @@ client_request_definitions! {
params: v2::ThreadReadParams,
response: v2::ThreadReadResponse,
},
#[experimental("thread/job/create")]
ThreadJobCreate => "thread/job/create" {
params: v2::ThreadJobCreateParams,
response: v2::ThreadJobCreateResponse,
},
#[experimental("thread/job/delete")]
ThreadJobDelete => "thread/job/delete" {
params: v2::ThreadJobDeleteParams,
response: v2::ThreadJobDeleteResponse,
},
#[experimental("thread/job/list")]
ThreadJobList => "thread/job/list" {
params: v2::ThreadJobListParams,
response: v2::ThreadJobListResponse,
},
SkillsList => "skills/list" {
params: v2::SkillsListParams,
response: v2::SkillsListResponse,
@@ -459,11 +474,6 @@ client_request_definitions! {
response: v2::ListMcpServerStatusResponse,
},
McpResourceRead => "mcpServer/resource/read" {
params: v2::McpResourceReadParams,
response: v2::McpResourceReadResponse,
},
WindowsSandboxSetupStart => "windowsSandbox/setupStart" {
params: v2::WindowsSandboxSetupStartParams,
response: v2::WindowsSandboxSetupStartResponse,
@@ -620,41 +630,6 @@ macro_rules! server_request_definitions {
}
}
/// Typed response from the client to the server.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "method", rename_all = "camelCase")]
pub enum ServerResponse {
$(
$(#[$variant_meta])*
$(#[serde(rename = $wire)])?
$variant {
#[serde(rename = "id")]
request_id: RequestId,
response: $response,
},
)*
}
impl ServerResponse {
pub fn id(&self) -> &RequestId {
match self {
$(Self::$variant { request_id, .. } => request_id,)*
}
}
pub fn method(&self) -> String {
serde_json::to_value(self)
.ok()
.and_then(|value| {
value
.get("method")
.and_then(serde_json::Value::as_str)
.map(str::to_owned)
})
.unwrap_or_else(|| "<unknown>".to_string())
}
}
#[derive(Debug, Clone, PartialEq, JsonSchema)]
#[allow(clippy::large_enum_variant)]
pub enum ServerRequestPayload {
@@ -958,6 +933,10 @@ server_notification_definitions! {
ThreadClosed => "thread/closed" (v2::ThreadClosedNotification),
SkillsChanged => "skills/changed" (v2::SkillsChangedNotification),
ThreadNameUpdated => "thread/name/updated" (v2::ThreadNameUpdatedNotification),
#[experimental("thread/job/updated")]
ThreadJobUpdated => "thread/job/updated" (v2::ThreadJobUpdatedNotification),
#[experimental("thread/job/fired")]
ThreadJobFired => "thread/job/fired" (v2::ThreadJobFiredNotification),
ThreadTokenUsageUpdated => "thread/tokenUsage/updated" (v2::ThreadTokenUsageUpdatedNotification),
TurnStarted => "turn/started" (v2::TurnStartedNotification),
HookStarted => "hook/started" (v2::HookStartedNotification),
@@ -1005,8 +984,6 @@ server_notification_definitions! {
ThreadRealtimeTranscriptUpdated => "thread/realtime/transcriptUpdated" (v2::ThreadRealtimeTranscriptUpdatedNotification),
#[experimental("thread/realtime/outputAudio/delta")]
ThreadRealtimeOutputAudioDelta => "thread/realtime/outputAudio/delta" (v2::ThreadRealtimeOutputAudioDeltaNotification),
#[experimental("thread/realtime/sdp")]
ThreadRealtimeSdp => "thread/realtime/sdp" (v2::ThreadRealtimeSdpNotification),
#[experimental("thread/realtime/error")]
ThreadRealtimeError => "thread/realtime/error" (v2::ThreadRealtimeErrorNotification),
#[experimental("thread/realtime/closed")]
@@ -1267,30 +1244,6 @@ mod tests {
Ok(())
}
#[test]
fn serialize_server_response() -> Result<()> {
let response = ServerResponse::CommandExecutionRequestApproval {
request_id: RequestId::Integer(8),
response: v2::CommandExecutionRequestApprovalResponse {
decision: v2::CommandExecutionApprovalDecision::AcceptForSession,
},
};
assert_eq!(response.id(), &RequestId::Integer(8));
assert_eq!(response.method(), "item/commandExecution/requestApproval");
assert_eq!(
json!({
"method": "item/commandExecution/requestApproval",
"id": 8,
"response": {
"decision": "acceptForSession"
}
}),
serde_json::to_value(&response)?,
);
Ok(())
}
#[test]
fn serialize_mcp_server_elicitation_request() -> Result<()> {
let requested_schema: v2::McpElicitationSchema = serde_json::from_value(json!({
@@ -1373,7 +1326,6 @@ mod tests {
response: v2::ThreadStartResponse {
thread: v2::Thread {
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
forked_from_id: None,
preview: "first prompt".to_string(),
ephemeral: true,
model_provider: "openai".to_string(),
@@ -1410,7 +1362,6 @@ mod tests {
"response": {
"thread": {
"id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
"forkedFromId": null,
"preview": "first prompt",
"ephemeral": true,
"modelProvider": "openai",
@@ -1696,7 +1647,6 @@ mod tests {
let request = ClientRequest::FsWatch {
request_id: RequestId::Integer(10),
params: v2::FsWatchParams {
watch_id: "watch-git".to_string(),
path: absolute_path("tmp/repo/.git"),
},
};
@@ -1705,7 +1655,6 @@ mod tests {
"method": "fs/watch",
"id": 10,
"params": {
"watchId": "watch-git",
"path": absolute_path_string("tmp/repo/.git")
}
}),
@@ -1763,7 +1712,6 @@ mod tests {
thread_id: "thr_123".to_string(),
prompt: "You are on a call".to_string(),
session_id: Some("sess_456".to_string()),
transport: None,
},
};
assert_eq!(
@@ -1773,8 +1721,7 @@ mod tests {
"params": {
"threadId": "thr_123",
"prompt": "You are on a call",
"sessionId": "sess_456",
"transport": null
"sessionId": "sess_456"
}
}),
serde_json::to_value(&request)?,
@@ -1854,7 +1801,6 @@ mod tests {
thread_id: "thr_123".to_string(),
prompt: "You are on a call".to_string(),
session_id: None,
transport: None,
},
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);

View File

@@ -1,299 +0,0 @@
//! Shared builders for synthetic [`ThreadItem`] values emitted by the app-server layer.
//!
//! These items do not come from first-class core `ItemStarted` / `ItemCompleted` events.
//! Instead, the app-server synthesizes them so clients can render a coherent lifecycle for
//! approvals and other pre-execution flows before the underlying tool has started or when the
//! tool never starts at all.
//!
//! Keeping these builders in one place is useful for two reasons:
//! - Live notifications and rebuilt `thread/read` history both need to construct the same
//! synthetic items, so sharing the logic avoids drift between those paths.
//! - The projection is presentation-specific. Core protocol events stay generic, while the
//! app-server protocol decides how to surface those events as `ThreadItem`s for clients.
use crate::protocol::common::ServerNotification;
use crate::protocol::v2::CommandAction;
use crate::protocol::v2::CommandExecutionSource;
use crate::protocol::v2::CommandExecutionStatus;
use crate::protocol::v2::FileUpdateChange;
use crate::protocol::v2::GuardianApprovalReview;
use crate::protocol::v2::GuardianApprovalReviewStatus;
use crate::protocol::v2::ItemGuardianApprovalReviewCompletedNotification;
use crate::protocol::v2::ItemGuardianApprovalReviewStartedNotification;
use crate::protocol::v2::PatchApplyStatus;
use crate::protocol::v2::PatchChangeKind;
use crate::protocol::v2::ThreadItem;
use codex_protocol::ThreadId;
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
use codex_protocol::protocol::ExecApprovalRequestEvent;
use codex_protocol::protocol::ExecCommandBeginEvent;
use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::GuardianAssessmentAction;
use codex_protocol::protocol::GuardianAssessmentEvent;
use codex_protocol::protocol::PatchApplyBeginEvent;
use codex_protocol::protocol::PatchApplyEndEvent;
use codex_shell_command::parse_command::parse_command;
use codex_shell_command::parse_command::shlex_join;
use std::collections::HashMap;
use std::path::PathBuf;
pub fn build_file_change_approval_request_item(
payload: &ApplyPatchApprovalRequestEvent,
) -> ThreadItem {
ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status: PatchApplyStatus::InProgress,
}
}
pub fn build_file_change_begin_item(payload: &PatchApplyBeginEvent) -> ThreadItem {
ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status: PatchApplyStatus::InProgress,
}
}
pub fn build_file_change_end_item(payload: &PatchApplyEndEvent) -> ThreadItem {
ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status: (&payload.status).into(),
}
}
pub fn build_command_execution_approval_request_item(
payload: &ExecApprovalRequestEvent,
) -> ThreadItem {
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
process_id: None,
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::InProgress,
command_actions: payload
.parsed_cmd
.iter()
.cloned()
.map(CommandAction::from)
.collect(),
aggregated_output: None,
exit_code: None,
duration_ms: None,
}
}
pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> ThreadItem {
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: CommandExecutionStatus::InProgress,
command_actions: payload
.parsed_cmd
.iter()
.cloned()
.map(CommandAction::from)
.collect(),
aggregated_output: None,
exit_code: None,
duration_ms: None,
}
}
pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> ThreadItem {
let aggregated_output = if payload.aggregated_output.is_empty() {
None
} else {
Some(payload.aggregated_output.clone())
};
let duration_ms = i64::try_from(payload.duration.as_millis()).unwrap_or(i64::MAX);
ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command: shlex_join(&payload.command),
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: (&payload.status).into(),
command_actions: payload
.parsed_cmd
.iter()
.cloned()
.map(CommandAction::from)
.collect(),
aggregated_output,
exit_code: Some(payload.exit_code),
duration_ms: Some(duration_ms),
}
}
/// Build a guardian-derived [`ThreadItem`].
///
/// Currently this only synthesizes [`ThreadItem::CommandExecution`] for
/// [`GuardianAssessmentAction::Command`] and [`GuardianAssessmentAction::Execve`].
pub fn build_item_from_guardian_event(
assessment: &GuardianAssessmentEvent,
status: CommandExecutionStatus,
) -> Option<ThreadItem> {
match &assessment.action {
GuardianAssessmentAction::Command { command, cwd, .. } => {
let command = command.clone();
let command_actions = vec![CommandAction::Unknown {
command: command.clone(),
}];
Some(ThreadItem::CommandExecution {
id: assessment.id.clone(),
command,
cwd: cwd.clone(),
process_id: None,
source: CommandExecutionSource::Agent,
status,
command_actions,
aggregated_output: None,
exit_code: None,
duration_ms: None,
})
}
GuardianAssessmentAction::Execve {
program, argv, cwd, ..
} => {
let argv = if argv.is_empty() {
vec![program.clone()]
} else {
std::iter::once(program.clone())
.chain(argv.iter().skip(1).cloned())
.collect::<Vec<_>>()
};
let command = shlex_join(&argv);
let parsed_cmd = parse_command(&argv);
let command_actions = if parsed_cmd.is_empty() {
vec![CommandAction::Unknown {
command: command.clone(),
}]
} else {
parsed_cmd.into_iter().map(CommandAction::from).collect()
};
Some(ThreadItem::CommandExecution {
id: assessment.id.clone(),
command,
cwd: cwd.clone(),
process_id: None,
source: CommandExecutionSource::Agent,
status,
command_actions,
aggregated_output: None,
exit_code: None,
duration_ms: None,
})
}
GuardianAssessmentAction::ApplyPatch { .. }
| GuardianAssessmentAction::NetworkAccess { .. }
| GuardianAssessmentAction::McpToolCall { .. } => None,
}
}
pub fn guardian_auto_approval_review_notification(
conversation_id: &ThreadId,
event_turn_id: &str,
assessment: &GuardianAssessmentEvent,
) -> ServerNotification {
// TODO(ccunningham): Attach guardian review state to the reviewed tool
// item's lifecycle instead of sending standalone review notifications so
// the app-server API can persist and replay review state via `thread/read`.
let turn_id = if assessment.turn_id.is_empty() {
event_turn_id.to_string()
} else {
assessment.turn_id.clone()
};
let review = GuardianApprovalReview {
status: match assessment.status {
codex_protocol::protocol::GuardianAssessmentStatus::InProgress => {
GuardianApprovalReviewStatus::InProgress
}
codex_protocol::protocol::GuardianAssessmentStatus::Approved => {
GuardianApprovalReviewStatus::Approved
}
codex_protocol::protocol::GuardianAssessmentStatus::Denied => {
GuardianApprovalReviewStatus::Denied
}
codex_protocol::protocol::GuardianAssessmentStatus::Aborted => {
GuardianApprovalReviewStatus::Aborted
}
},
risk_score: assessment.risk_score,
risk_level: assessment.risk_level.map(Into::into),
rationale: assessment.rationale.clone(),
};
let action = assessment.action.clone().into();
match assessment.status {
codex_protocol::protocol::GuardianAssessmentStatus::InProgress => {
ServerNotification::ItemGuardianApprovalReviewStarted(
ItemGuardianApprovalReviewStartedNotification {
thread_id: conversation_id.to_string(),
turn_id,
target_item_id: assessment.id.clone(),
review,
action,
},
)
}
codex_protocol::protocol::GuardianAssessmentStatus::Approved
| codex_protocol::protocol::GuardianAssessmentStatus::Denied
| codex_protocol::protocol::GuardianAssessmentStatus::Aborted => {
ServerNotification::ItemGuardianApprovalReviewCompleted(
ItemGuardianApprovalReviewCompletedNotification {
thread_id: conversation_id.to_string(),
turn_id,
target_item_id: assessment.id.clone(),
review,
action,
},
)
}
}
}
pub fn convert_patch_changes(changes: &HashMap<PathBuf, FileChange>) -> Vec<FileUpdateChange> {
let mut converted: Vec<FileUpdateChange> = changes
.iter()
.map(|(path, change)| FileUpdateChange {
path: path.to_string_lossy().into_owned(),
kind: map_patch_change_kind(change),
diff: format_file_change_diff(change),
})
.collect();
converted.sort_by(|a, b| a.path.cmp(&b.path));
converted
}
fn map_patch_change_kind(change: &FileChange) -> PatchChangeKind {
match change {
FileChange::Add { .. } => PatchChangeKind::Add,
FileChange::Delete { .. } => PatchChangeKind::Delete,
FileChange::Update { move_path, .. } => PatchChangeKind::Update {
move_path: move_path.clone(),
},
}
}
fn format_file_change_diff(change: &FileChange) -> String {
match change {
FileChange::Add { content } => content.clone(),
FileChange::Delete { content } => content.clone(),
FileChange::Update {
unified_diff,
move_path,
} => {
if let Some(path) = move_path {
format!("{unified_diff}\n\nMoved to: {}", path.display())
} else {
unified_diff.clone()
}
}
}
}

View File

@@ -2,7 +2,6 @@
// Exposes protocol pieces used by `lib.rs` via `pub use protocol::common::*;`.
pub mod common;
pub mod item_builders;
mod mappers;
mod serde_helpers;
pub mod thread_history;

View File

@@ -1,18 +1,16 @@
use crate::protocol::item_builders::build_command_execution_begin_item;
use crate::protocol::item_builders::build_command_execution_end_item;
use crate::protocol::item_builders::build_file_change_approval_request_item;
use crate::protocol::item_builders::build_file_change_begin_item;
use crate::protocol::item_builders::build_file_change_end_item;
use crate::protocol::item_builders::build_item_from_guardian_event;
use crate::protocol::v2::CollabAgentState;
use crate::protocol::v2::CollabAgentTool;
use crate::protocol::v2::CollabAgentToolCallStatus;
use crate::protocol::v2::CommandAction;
use crate::protocol::v2::CommandExecutionStatus;
use crate::protocol::v2::DynamicToolCallOutputContentItem;
use crate::protocol::v2::DynamicToolCallStatus;
use crate::protocol::v2::FileUpdateChange;
use crate::protocol::v2::McpToolCallError;
use crate::protocol::v2::McpToolCallResult;
use crate::protocol::v2::McpToolCallStatus;
use crate::protocol::v2::PatchApplyStatus;
use crate::protocol::v2::PatchChangeKind;
use crate::protocol::v2::ThreadItem;
use crate::protocol::v2::Turn;
use crate::protocol::v2::TurnError as V2TurnError;
@@ -33,8 +31,6 @@ use codex_protocol::protocol::ErrorEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandBeginEvent;
use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::GuardianAssessmentEvent;
use codex_protocol::protocol::GuardianAssessmentStatus;
use codex_protocol::protocol::ImageGenerationBeginEvent;
use codex_protocol::protocol::ImageGenerationEndEvent;
use codex_protocol::protocol::ItemCompletedEvent;
@@ -57,14 +53,6 @@ use std::collections::HashMap;
use tracing::warn;
use uuid::Uuid;
#[cfg(test)]
use crate::protocol::v2::CommandAction;
#[cfg(test)]
use crate::protocol::v2::FileUpdateChange;
#[cfg(test)]
use crate::protocol::v2::PatchApplyStatus;
#[cfg(test)]
use crate::protocol::v2::PatchChangeKind;
#[cfg(test)]
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
#[cfg(test)]
@@ -161,7 +149,6 @@ impl ThreadHistoryBuilder {
EventMsg::WebSearchEnd(payload) => self.handle_web_search_end(payload),
EventMsg::ExecCommandBegin(payload) => self.handle_exec_command_begin(payload),
EventMsg::ExecCommandEnd(payload) => self.handle_exec_command_end(payload),
EventMsg::GuardianAssessment(payload) => self.handle_guardian_assessment(payload),
EventMsg::ApplyPatchApprovalRequest(payload) => {
self.handle_apply_patch_approval_request(payload)
}
@@ -388,12 +375,57 @@ impl ThreadHistoryBuilder {
}
fn handle_exec_command_begin(&mut self, payload: &ExecCommandBeginEvent) {
let item = build_command_execution_begin_item(payload);
let command = shlex::try_join(payload.command.iter().map(String::as_str))
.unwrap_or_else(|_| payload.command.join(" "));
let command_actions = payload
.parsed_cmd
.iter()
.cloned()
.map(CommandAction::from)
.collect();
let item = ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command,
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status: CommandExecutionStatus::InProgress,
command_actions,
aggregated_output: None,
exit_code: None,
duration_ms: None,
};
self.upsert_item_in_turn_id(&payload.turn_id, item);
}
fn handle_exec_command_end(&mut self, payload: &ExecCommandEndEvent) {
let item = build_command_execution_end_item(payload);
let status: CommandExecutionStatus = (&payload.status).into();
let duration_ms = i64::try_from(payload.duration.as_millis()).unwrap_or(i64::MAX);
let aggregated_output = if payload.aggregated_output.is_empty() {
None
} else {
Some(payload.aggregated_output.clone())
};
let command = shlex::try_join(payload.command.iter().map(String::as_str))
.unwrap_or_else(|_| payload.command.join(" "));
let command_actions = payload
.parsed_cmd
.iter()
.cloned()
.map(CommandAction::from)
.collect();
let item = ThreadItem::CommandExecution {
id: payload.call_id.clone(),
command,
cwd: payload.cwd.clone(),
process_id: payload.process_id.clone(),
source: payload.source.into(),
status,
command_actions,
aggregated_output,
exit_code: Some(payload.exit_code),
duration_ms: Some(duration_ms),
};
// Command completions can arrive out of order. Unified exec may return
// while a PTY is still running, then emit ExecCommandEnd later from a
// background exit watcher when that process finally exits. By then, a
@@ -402,26 +434,12 @@ impl ThreadHistoryBuilder {
self.upsert_item_in_turn_id(&payload.turn_id, item);
}
fn handle_guardian_assessment(&mut self, payload: &GuardianAssessmentEvent) {
let status = match payload.status {
GuardianAssessmentStatus::InProgress => CommandExecutionStatus::InProgress,
GuardianAssessmentStatus::Denied | GuardianAssessmentStatus::Aborted => {
CommandExecutionStatus::Declined
}
GuardianAssessmentStatus::Approved => return,
};
let Some(item) = build_item_from_guardian_event(payload, status) else {
return;
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
self.upsert_item_in_turn_id(&payload.turn_id, item);
}
}
fn handle_apply_patch_approval_request(&mut self, payload: &ApplyPatchApprovalRequestEvent) {
let item = build_file_change_approval_request_item(payload);
let item = ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status: PatchApplyStatus::InProgress,
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
@@ -430,7 +448,11 @@ impl ThreadHistoryBuilder {
}
fn handle_patch_apply_begin(&mut self, payload: &PatchApplyBeginEvent) {
let item = build_file_change_begin_item(payload);
let item = ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status: PatchApplyStatus::InProgress,
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
@@ -439,7 +461,12 @@ impl ThreadHistoryBuilder {
}
fn handle_patch_apply_end(&mut self, payload: &PatchApplyEndEvent) {
let item = build_file_change_end_item(payload);
let status: PatchApplyStatus = (&payload.status).into();
let item = ThreadItem::FileChange {
id: payload.call_id.clone(),
changes: convert_patch_changes(&payload.changes),
status,
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
@@ -520,7 +547,6 @@ impl ThreadHistoryBuilder {
Some(McpToolCallResult {
content: value.content.clone(),
structured_content: value.structured_content.clone(),
meta: value.meta.clone(),
}),
None,
),
@@ -865,29 +891,22 @@ impl ThreadHistoryBuilder {
}
fn handle_turn_aborted(&mut self, payload: &TurnAbortedEvent) {
let apply_abort = |turn: &mut PendingTurn| {
turn.status = TurnStatus::Interrupted;
turn.completed_at = payload.completed_at;
turn.duration_ms = payload.duration_ms;
};
if let Some(turn_id) = payload.turn_id.as_deref() {
// Prefer an exact ID match so we interrupt the turn explicitly targeted by the event.
if let Some(turn) = self.current_turn.as_mut().filter(|turn| turn.id == turn_id) {
apply_abort(turn);
turn.status = TurnStatus::Interrupted;
return;
}
if let Some(turn) = self.turns.iter_mut().find(|turn| turn.id == turn_id) {
turn.status = TurnStatus::Interrupted;
turn.completed_at = payload.completed_at;
turn.duration_ms = payload.duration_ms;
return;
}
}
// If the event has no ID (or refers to an unknown turn), fall back to the active turn.
if let Some(turn) = self.current_turn.as_mut() {
apply_abort(turn);
turn.status = TurnStatus::Interrupted;
}
}
@@ -896,18 +915,15 @@ impl ThreadHistoryBuilder {
self.current_turn = Some(
self.new_turn(Some(payload.turn_id.clone()))
.with_status(TurnStatus::InProgress)
.with_started_at(payload.started_at)
.opened_explicitly(),
);
}
fn handle_turn_complete(&mut self, payload: &TurnCompleteEvent) {
let mark_completed = |turn: &mut PendingTurn| {
if matches!(turn.status, TurnStatus::Completed | TurnStatus::InProgress) {
turn.status = TurnStatus::Completed;
let mark_completed = |status: &mut TurnStatus| {
if matches!(*status, TurnStatus::Completed | TurnStatus::InProgress) {
*status = TurnStatus::Completed;
}
turn.completed_at = payload.completed_at;
turn.duration_ms = payload.duration_ms;
};
// Prefer an exact ID match from the active turn and then close it.
@@ -916,7 +932,7 @@ impl ThreadHistoryBuilder {
.as_mut()
.filter(|turn| turn.id == payload.turn_id)
{
mark_completed(current_turn);
mark_completed(&mut current_turn.status);
self.finish_current_turn();
return;
}
@@ -926,17 +942,13 @@ impl ThreadHistoryBuilder {
.iter_mut()
.find(|turn| turn.id == payload.turn_id)
{
if matches!(turn.status, TurnStatus::Completed | TurnStatus::InProgress) {
turn.status = TurnStatus::Completed;
}
turn.completed_at = payload.completed_at;
turn.duration_ms = payload.duration_ms;
mark_completed(&mut turn.status);
return;
}
// If the completion event cannot be matched, apply it to the active turn.
if let Some(current_turn) = self.current_turn.as_mut() {
mark_completed(current_turn);
mark_completed(&mut current_turn.status);
self.finish_current_turn();
}
}
@@ -969,7 +981,7 @@ impl ThreadHistoryBuilder {
if turn.items.is_empty() && !turn.opened_explicitly && !turn.saw_compaction {
return;
}
self.turns.push(Turn::from(turn));
self.turns.push(turn.into());
}
}
@@ -979,9 +991,6 @@ impl ThreadHistoryBuilder {
items: Vec::new(),
error: None,
status: TurnStatus::Completed,
started_at: None,
completed_at: None,
duration_ms: None,
opened_explicitly: false,
saw_compaction: false,
rollout_start_index: self.current_rollout_index,
@@ -1067,6 +1076,21 @@ fn render_review_output_text(output: &ReviewOutputEvent) -> String {
}
}
pub fn convert_patch_changes(
changes: &HashMap<std::path::PathBuf, codex_protocol::protocol::FileChange>,
) -> Vec<FileUpdateChange> {
let mut converted: Vec<FileUpdateChange> = changes
.iter()
.map(|(path, change)| FileUpdateChange {
path: path.to_string_lossy().into_owned(),
kind: map_patch_change_kind(change),
diff: format_file_change_diff(change),
})
.collect();
converted.sort_by(|a, b| a.path.cmp(&b.path));
converted
}
fn convert_dynamic_tool_content_items(
items: &[codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem],
) -> Vec<DynamicToolCallOutputContentItem> {
@@ -1084,6 +1108,33 @@ fn convert_dynamic_tool_content_items(
.collect()
}
fn map_patch_change_kind(change: &codex_protocol::protocol::FileChange) -> PatchChangeKind {
match change {
codex_protocol::protocol::FileChange::Add { .. } => PatchChangeKind::Add,
codex_protocol::protocol::FileChange::Delete { .. } => PatchChangeKind::Delete,
codex_protocol::protocol::FileChange::Update { move_path, .. } => PatchChangeKind::Update {
move_path: move_path.clone(),
},
}
}
fn format_file_change_diff(change: &codex_protocol::protocol::FileChange) -> String {
match change {
codex_protocol::protocol::FileChange::Add { content } => content.clone(),
codex_protocol::protocol::FileChange::Delete { content } => content.clone(),
codex_protocol::protocol::FileChange::Update {
unified_diff,
move_path,
} => {
if let Some(path) = move_path {
format!("{unified_diff}\n\nMoved to: {}", path.display())
} else {
unified_diff.clone()
}
}
}
}
fn upsert_turn_item(items: &mut Vec<ThreadItem>, item: ThreadItem) {
if let Some(existing_item) = items
.iter_mut()
@@ -1100,9 +1151,6 @@ struct PendingTurn {
items: Vec<ThreadItem>,
error: Option<TurnError>,
status: TurnStatus,
started_at: Option<i64>,
completed_at: Option<i64>,
duration_ms: Option<i64>,
/// True when this turn originated from an explicit `turn_started`/`turn_complete`
/// boundary, so we preserve it even if it has no renderable items.
opened_explicitly: bool,
@@ -1123,11 +1171,6 @@ impl PendingTurn {
self.status = status;
self
}
fn with_started_at(mut self, started_at: Option<i64>) -> Self {
self.started_at = started_at;
self
}
}
impl From<PendingTurn> for Turn {
@@ -1137,9 +1180,6 @@ impl From<PendingTurn> for Turn {
items: value.items,
error: value.error,
status: value.status,
started_at: value.started_at,
completed_at: value.completed_at,
duration_ms: value.duration_ms,
}
}
}
@@ -1151,9 +1191,6 @@ impl From<&PendingTurn> for Turn {
items: value.items.clone(),
error: value.error.clone(),
status: value.status.clone(),
started_at: value.started_at,
completed_at: value.completed_at,
duration_ms: value.duration_ms,
}
}
}
@@ -1168,7 +1205,6 @@ mod tests {
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::items::UserMessageItem as CoreUserMessageItem;
use codex_protocol::items::build_hook_prompt_message;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::MessagePhase as CoreMessagePhase;
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
use codex_protocol::parse_command::ParsedCommand;
@@ -1306,7 +1342,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.to_string(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -1327,8 +1362,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: turn_id.to_string(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -1381,7 +1414,6 @@ mod tests {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-image".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
@@ -1401,8 +1433,6 @@ mod tests {
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-image".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
})),
];
@@ -1414,9 +1444,6 @@ mod tests {
id: "turn-image".into(),
status: TurnStatus::Completed,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
items: vec![
ThreadItem::UserMessage {
id: "item-1".into(),
@@ -1506,8 +1533,6 @@ mod tests {
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some("turn-1".into()),
reason: TurnAbortReason::Replaced,
completed_at: None,
duration_ms: None,
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Let's try again".into(),
@@ -1705,7 +1730,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -1724,8 +1748,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -1762,7 +1784,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -1863,73 +1884,11 @@ mod tests {
);
}
#[test]
fn reconstructs_mcp_tool_result_meta_from_persisted_completion_events() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
EventMsg::McpToolCallEnd(McpToolCallEndEvent {
call_id: "mcp-1".into(),
invocation: McpInvocation {
server: "docs".into(),
tool: "lookup".into(),
arguments: Some(serde_json::json!({"id":"123"})),
},
duration: Duration::from_millis(8),
result: Ok(CallToolResult {
content: vec![serde_json::json!({
"type": "text",
"text": "result"
})],
structured_content: Some(serde_json::json!({"id":"123"})),
is_error: Some(false),
meta: Some(serde_json::json!({
"ui/resourceUri": "ui://widget/lookup.html"
})),
}),
}),
];
let items = events
.into_iter()
.map(RolloutItem::EventMsg)
.collect::<Vec<_>>();
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert_eq!(
turns[0].items[0],
ThreadItem::McpToolCall {
id: "mcp-1".into(),
server: "docs".into(),
tool: "lookup".into(),
status: McpToolCallStatus::Completed,
arguments: serde_json::json!({"id":"123"}),
result: Some(McpToolCallResult {
content: vec![serde_json::json!({
"type": "text",
"text": "result"
})],
structured_content: Some(serde_json::json!({"id":"123"})),
meta: Some(serde_json::json!({
"ui/resourceUri": "ui://widget/lookup.html"
})),
}),
error: None,
duration_ms: Some(8),
}
);
}
#[test]
fn reconstructs_dynamic_tool_items_from_request_and_response_events() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -1989,7 +1948,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2072,144 +2030,11 @@ mod tests {
);
}
#[test]
fn reconstructs_declined_guardian_command_item() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "review this command".into(),
images: None,
text_elements: Vec::new(),
local_images: Vec::new(),
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
id: "guardian-exec".into(),
turn_id: "turn-1".into(),
status: GuardianAssessmentStatus::InProgress,
risk_score: None,
risk_level: None,
rationale: None,
action: serde_json::from_value(serde_json::json!({
"type": "command",
"source": "shell",
"command": "rm -rf /tmp/guardian",
"cwd": "/tmp",
}))
.expect("guardian action"),
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
id: "guardian-exec".into(),
turn_id: "turn-1".into(),
status: GuardianAssessmentStatus::Denied,
risk_score: Some(97),
risk_level: Some(codex_protocol::protocol::GuardianRiskLevel::High),
rationale: Some("Would delete user data.".into()),
action: serde_json::from_value(serde_json::json!({
"type": "command",
"source": "shell",
"command": "rm -rf /tmp/guardian",
"cwd": "/tmp",
}))
.expect("guardian action"),
}),
];
let items = events
.into_iter()
.map(RolloutItem::EventMsg)
.collect::<Vec<_>>();
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert_eq!(turns[0].items.len(), 2);
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
id: "guardian-exec".into(),
command: "rm -rf /tmp/guardian".into(),
cwd: PathBuf::from("/tmp"),
process_id: None,
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::Declined,
command_actions: vec![CommandAction::Unknown {
command: "rm -rf /tmp/guardian".into(),
}],
aggregated_output: None,
exit_code: None,
duration_ms: None,
}
);
}
#[test]
fn reconstructs_in_progress_guardian_execve_item() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "run a subcommand".into(),
images: None,
text_elements: Vec::new(),
local_images: Vec::new(),
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
id: "guardian-execve".into(),
turn_id: "turn-1".into(),
status: GuardianAssessmentStatus::InProgress,
risk_score: None,
risk_level: None,
rationale: None,
action: serde_json::from_value(serde_json::json!({
"type": "execve",
"source": "shell",
"program": "/bin/rm",
"argv": ["/usr/bin/rm", "-f", "/tmp/file.sqlite"],
"cwd": "/tmp",
}))
.expect("guardian action"),
}),
];
let items = events
.into_iter()
.map(RolloutItem::EventMsg)
.collect::<Vec<_>>();
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert_eq!(turns[0].items.len(), 2);
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
id: "guardian-execve".into(),
command: "/bin/rm -f /tmp/file.sqlite".into(),
cwd: PathBuf::from("/tmp"),
process_id: None,
source: CommandExecutionSource::Agent,
status: CommandExecutionStatus::InProgress,
command_actions: vec![CommandAction::Unknown {
command: "/bin/rm -f /tmp/file.sqlite".into(),
}],
aggregated_output: None,
exit_code: None,
duration_ms: None,
}
);
}
#[test]
fn assigns_late_exec_completion_to_original_turn() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2222,12 +2047,9 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-b".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2259,8 +2081,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-b".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -2298,7 +2118,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2311,12 +2130,9 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-b".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2348,8 +2164,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-b".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -2382,7 +2196,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.to_string(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2446,7 +2259,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.to_string(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2509,7 +2321,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2522,12 +2333,9 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-b".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2540,8 +2348,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "still in b".into(),
@@ -2551,8 +2357,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-b".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -2572,7 +2376,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2585,12 +2388,9 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-b".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2603,8 +2403,6 @@ mod tests {
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some("turn-a".into()),
reason: TurnAbortReason::Replaced,
completed_at: None,
duration_ms: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "still in b".into(),
@@ -2630,7 +2428,6 @@ mod tests {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-compact".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
@@ -2641,8 +2438,6 @@ mod tests {
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-compact".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
})),
];
@@ -2653,9 +2448,6 @@ mod tests {
id: "turn-compact".into(),
status: TurnStatus::Completed,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
items: Vec::new(),
}]
);
@@ -2873,7 +2665,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2886,8 +2677,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
EventMsg::Error(ErrorEvent {
message: "request-level failure".into(),
@@ -2907,9 +2696,6 @@ mod tests {
id: "turn-a".into(),
status: TurnStatus::Completed,
error: None,
started_at: None,
completed_at: None,
duration_ms: None,
items: vec![ThreadItem::UserMessage {
id: "item-1".into(),
content: vec![UserInput::Text {
@@ -2926,7 +2712,6 @@ mod tests {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
@@ -2945,8 +2730,6 @@ mod tests {
EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
}),
];
@@ -2982,7 +2765,6 @@ mod tests {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
@@ -2996,8 +2778,6 @@ mod tests {
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
})),
];
@@ -3028,7 +2808,6 @@ mod tests {
let items = vec![
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-a".into(),
started_at: None,
model_context_window: None,
collaboration_mode_kind: Default::default(),
})),
@@ -3044,8 +2823,6 @@ mod tests {
RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent {
turn_id: "turn-a".into(),
last_agent_message: None,
completed_at: None,
duration_ms: None,
})),
];

View File

@@ -8,8 +8,6 @@ use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::account::PlanType;
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
use codex_protocol::approvals::GuardianAssessmentAction as CoreGuardianAssessmentAction;
use codex_protocol::approvals::GuardianCommandSource as CoreGuardianCommandSource;
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment;
@@ -29,7 +27,6 @@ use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::mcp::Resource as McpResource;
pub use codex_protocol::mcp::ResourceContent as McpResourceContent;
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
use codex_protocol::mcp::Tool as McpTool;
use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;
@@ -851,8 +848,6 @@ pub struct ConfigReadResponse {
pub struct ConfigRequirements {
#[experimental(nested)]
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
#[experimental("configRequirements/read.allowedApprovalsReviewers")]
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
pub feature_requirements: Option<BTreeMap<String, bool>>,
@@ -885,7 +880,6 @@ pub struct NetworkRequirements {
/// Legacy compatibility view derived from `unix_sockets`.
pub allow_unix_sockets: Option<Vec<String>>,
pub allow_local_binding: Option<bool>,
pub danger_full_access_denylist_only: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
@@ -1791,8 +1785,6 @@ pub struct Model {
pub input_modalities: Vec<InputModality>,
#[serde(default)]
pub supports_personality: bool,
#[serde(default)]
pub additional_speed_tiers: Vec<String>,
// Only one model should be marked as default.
pub is_default: bool,
}
@@ -1953,18 +1945,6 @@ pub struct ListMcpServerStatusParams {
/// Optional page size; defaults to a server-defined value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
/// Controls how much MCP inventory data to fetch for each server.
/// Defaults to `Full` when omitted.
#[ts(optional = nullable)]
pub detail: Option<McpServerStatusDetail>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum McpServerStatusDetail {
Full,
ToolsAndAuthOnly,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1988,22 +1968,6 @@ pub struct ListMcpServerStatusResponse {
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpResourceReadParams {
pub thread_id: String,
pub server: String,
pub uri: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpResourceReadResponse {
pub contents: Vec<McpResourceContent>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2351,17 +2315,17 @@ pub struct FsCopyResponse {}
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWatchParams {
/// Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
pub watch_id: String,
/// Absolute file or directory path to watch.
pub path: AbsolutePathBuf,
}
/// Successful response for `fs/watch`.
/// Created watch handle returned by `fs/watch`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWatchResponse {
/// Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
pub watch_id: String,
/// Canonicalized path associated with the watch.
pub path: AbsolutePathBuf,
}
@@ -2371,7 +2335,7 @@ pub struct FsWatchResponse {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsUnwatchParams {
/// Watch identifier previously provided to `fs/watch`.
/// Watch identifier returned by `fs/watch`.
pub watch_id: String,
}
@@ -2386,7 +2350,7 @@ pub struct FsUnwatchResponse {}
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsChangedNotification {
/// Watch identifier previously provided to `fs/watch`.
/// Watch identifier returned by `fs/watch`.
pub watch_id: String,
/// File or directory paths associated with this event.
pub changed_paths: Vec<AbsolutePathBuf>,
@@ -3185,6 +3149,85 @@ pub struct ThreadReadResponse {
pub thread: Thread,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJob {
pub id: String,
pub cron_expression: String,
pub prompt: String,
pub run_once: bool,
#[ts(type = "number")]
pub created_at: i64,
#[ts(type = "number | null")]
pub next_run_at: Option<i64>,
#[ts(type = "number | null")]
pub last_run_at: Option<i64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobCreateParams {
pub thread_id: String,
pub cron_expression: String,
pub prompt: String,
#[ts(optional = nullable)]
pub run_once: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobCreateResponse {
pub job: ThreadJob,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobDeleteParams {
pub thread_id: String,
pub id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobDeleteResponse {
pub deleted: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobListParams {
pub thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobListResponse {
pub data: Vec<ThreadJob>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobUpdatedNotification {
pub thread_id: String,
pub jobs: Vec<ThreadJob>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadJobFiredNotification {
pub thread_id: String,
pub job: ThreadJob,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3608,8 +3651,6 @@ impl From<CoreSkillErrorInfo> for SkillErrorInfo {
#[ts(export_to = "v2/")]
pub struct Thread {
pub id: String,
/// Source thread id when this thread was created by forking another thread.
pub forked_from_id: Option<String>,
/// Usually the first user message in the thread, if available.
pub preview: String,
/// Whether the thread is ephemeral and should not be materialized on disk.
@@ -3725,15 +3766,6 @@ pub struct Turn {
pub status: TurnStatus,
/// Only populated when the Turn's status is failed.
pub error: Option<TurnError>,
/// Unix timestamp (in seconds) when the turn started.
#[ts(type = "number | null")]
pub started_at: Option<i64>,
/// Unix timestamp (in seconds) when the turn completed.
#[ts(type = "number | null")]
pub completed_at: Option<i64>,
/// Duration between turn start and completion in milliseconds, if known.
#[ts(type = "number | null")]
pub duration_ms: Option<i64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -3856,21 +3888,6 @@ pub struct ThreadRealtimeStartParams {
pub prompt: String,
#[ts(optional = nullable)]
pub session_id: Option<String>,
#[ts(optional = nullable)]
pub transport: Option<ThreadRealtimeStartTransport>,
}
/// EXPERIMENTAL - transport used by thread realtime.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(export_to = "v2/", tag = "type")]
pub enum ThreadRealtimeStartTransport {
Websocket,
Webrtc {
/// SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the
/// realtime events data channel.
sdp: String,
},
}
/// EXPERIMENTAL - response for starting thread realtime.
@@ -3962,15 +3979,6 @@ pub struct ThreadRealtimeOutputAudioDeltaNotification {
pub audio: ThreadRealtimeAudioChunk,
}
/// EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeSdpNotification {
pub thread_id: String,
pub sdp: String,
}
/// EXPERIMENTAL - emitted when thread realtime encounters an error.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
@@ -4516,237 +4524,14 @@ impl From<CoreGuardianRiskLevel> for GuardianRiskLevel {
#[ts(export_to = "v2/")]
pub struct GuardianApprovalReview {
pub status: GuardianApprovalReviewStatus,
#[serde(alias = "risk_score")]
#[ts(type = "number | null")]
pub risk_score: Option<u8>,
#[serde(alias = "risk_level")]
pub risk_level: Option<GuardianRiskLevel>,
pub rationale: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum GuardianCommandSource {
Shell,
UnifiedExec,
}
impl From<CoreGuardianCommandSource> for GuardianCommandSource {
fn from(value: CoreGuardianCommandSource) -> Self {
match value {
CoreGuardianCommandSource::Shell => Self::Shell,
CoreGuardianCommandSource::UnifiedExec => Self::UnifiedExec,
}
}
}
impl From<GuardianCommandSource> for CoreGuardianCommandSource {
fn from(value: GuardianCommandSource) -> Self {
match value {
GuardianCommandSource::Shell => Self::Shell,
GuardianCommandSource::UnifiedExec => Self::UnifiedExec,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianCommandReviewAction {
pub source: GuardianCommandSource,
pub command: String,
pub cwd: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianExecveReviewAction {
pub source: GuardianCommandSource,
pub program: String,
pub argv: Vec<String>,
pub cwd: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianApplyPatchReviewAction {
pub cwd: PathBuf,
pub files: Vec<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianNetworkAccessReviewAction {
pub target: String,
pub host: String,
pub protocol: NetworkApprovalProtocol,
pub port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianMcpToolCallReviewAction {
pub server: String,
pub tool_name: String,
pub connector_id: Option<String>,
pub connector_name: Option<String>,
pub tool_title: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum GuardianApprovalReviewAction {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Command {
source: GuardianCommandSource,
command: String,
cwd: PathBuf,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Execve {
source: GuardianCommandSource,
program: String,
argv: Vec<String>,
cwd: PathBuf,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ApplyPatch { cwd: PathBuf, files: Vec<PathBuf> },
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
NetworkAccess {
target: String,
host: String,
protocol: NetworkApprovalProtocol,
port: u16,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
McpToolCall {
server: String,
tool_name: String,
connector_id: Option<String>,
connector_name: Option<String>,
tool_title: Option<String>,
},
}
impl From<CoreGuardianAssessmentAction> for GuardianApprovalReviewAction {
fn from(value: CoreGuardianAssessmentAction) -> Self {
match value {
CoreGuardianAssessmentAction::Command {
source,
command,
cwd,
} => Self::Command {
source: source.into(),
command,
cwd,
},
CoreGuardianAssessmentAction::Execve {
source,
program,
argv,
cwd,
} => Self::Execve {
source: source.into(),
program,
argv,
cwd,
},
CoreGuardianAssessmentAction::ApplyPatch { cwd, files } => {
Self::ApplyPatch { cwd, files }
}
CoreGuardianAssessmentAction::NetworkAccess {
target,
host,
protocol,
port,
} => Self::NetworkAccess {
target,
host,
protocol: protocol.into(),
port,
},
CoreGuardianAssessmentAction::McpToolCall {
server,
tool_name,
connector_id,
connector_name,
tool_title,
} => Self::McpToolCall {
server,
tool_name,
connector_id,
connector_name,
tool_title,
},
}
}
}
impl From<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
fn from(value: GuardianApprovalReviewAction) -> Self {
match value {
GuardianApprovalReviewAction::Command {
source,
command,
cwd,
} => Self::Command {
source: source.into(),
command,
cwd,
},
GuardianApprovalReviewAction::Execve {
source,
program,
argv,
cwd,
} => Self::Execve {
source: source.into(),
program,
argv,
cwd,
},
GuardianApprovalReviewAction::ApplyPatch { cwd, files } => {
Self::ApplyPatch { cwd, files }
}
GuardianApprovalReviewAction::NetworkAccess {
target,
host,
protocol,
port,
} => Self::NetworkAccess {
target,
host,
protocol: protocol.to_core(),
port,
},
GuardianApprovalReviewAction::McpToolCall {
server,
tool_name,
connector_id,
connector_name,
tool_title,
} => Self::McpToolCall {
server,
tool_name,
connector_id,
connector_name,
tool_title,
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", rename_all = "camelCase")]
@@ -5038,9 +4823,6 @@ pub struct McpToolCallResult {
// representations). Using `JsonValue` keeps the payload wire-shaped and easy to export.
pub content: Vec<JsonValue>,
pub structured_content: Option<JsonValue>,
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
pub meta: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -5230,7 +5012,7 @@ pub struct ItemGuardianApprovalReviewStartedNotification {
pub turn_id: String,
pub target_item_id: String,
pub review: GuardianApprovalReview,
pub action: GuardianApprovalReviewAction,
pub action: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -5247,7 +5029,7 @@ pub struct ItemGuardianApprovalReviewCompletedNotification {
pub turn_id: String,
pub target_item_id: String,
pub review: GuardianApprovalReview,
pub action: GuardianApprovalReviewAction,
pub action: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -7385,7 +7167,6 @@ mod tests {
request_permissions: false,
mcp_elicitations: false,
}]),
allowed_approvals_reviewers: None,
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
feature_requirements: None,
@@ -7785,6 +7566,26 @@ mod tests {
);
}
#[test]
fn automatic_approval_review_deserializes_legacy_snake_case_risk_fields() {
let review: GuardianApprovalReview = serde_json::from_value(json!({
"status": "denied",
"risk_score": 91,
"risk_level": "high",
"rationale": "too risky"
}))
.expect("legacy snake_case automatic review should deserialize");
assert_eq!(
review,
GuardianApprovalReview {
status: GuardianApprovalReviewStatus::Denied,
risk_score: Some(91),
risk_level: Some(GuardianRiskLevel::High),
rationale: Some("too risky".to_string()),
}
);
}
#[test]
fn automatic_approval_review_deserializes_aborted_status() {
let review: GuardianApprovalReview = serde_json::from_value(json!({
@@ -7805,31 +7606,6 @@ mod tests {
);
}
#[test]
fn guardian_approval_review_action_round_trips_command_shape() {
let value = json!({
"type": "command",
"source": "shell",
"command": "rm -rf /tmp/example.sqlite",
"cwd": "/tmp",
});
let action: GuardianApprovalReviewAction =
serde_json::from_value(value.clone()).expect("guardian review action");
assert_eq!(
action,
GuardianApprovalReviewAction::Command {
source: GuardianCommandSource::Shell,
command: "rm -rf /tmp/example.sqlite".to_string(),
cwd: "/tmp".into(),
}
);
assert_eq!(
serde_json::to_value(&action).expect("serialize guardian review action"),
value
);
}
#[test]
fn network_requirements_deserializes_legacy_fields() {
let requirements: NetworkRequirements = serde_json::from_value(json!({
@@ -7850,7 +7626,6 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
unix_sockets: None,
@@ -7877,7 +7652,6 @@ mod tests {
),
])),
managed_allowed_domains_only: Some(true),
danger_full_access_denylist_only: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
unix_sockets: Some(BTreeMap::from([
@@ -7908,7 +7682,6 @@ mod tests {
"blocked.example.com": "deny"
},
"managedAllowedDomainsOnly": true,
"dangerFullAccessDenylistOnly": true,
"allowedDomains": ["api.openai.com"],
"deniedDomains": ["blocked.example.com"],
"unixSockets": {

View File

@@ -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"],
)

View File

@@ -29,10 +29,8 @@ axum = { workspace = true, default-features = false, features = [
"tokio",
"ws",
] }
codex-analytics = { workspace = true }
codex-arg0 = { workspace = true }
codex-cloud-requirements = { workspace = true }
codex-config = { workspace = true }
codex-core = { workspace = true }
codex-exec-server = { workspace = true }
codex-features = { workspace = true }
@@ -45,24 +43,19 @@ codex-backend-client = { workspace = true }
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 }
codex-rmcp-client = { workspace = true }
codex-rollout = { workspace = true }
codex-sandboxing = { workspace = true }
codex-state = { workspace = true }
codex-tools = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-json-to-toml = { workspace = true }
codex-utils-rustls-provider = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive"] }
constant_time_eq = { workspace = true }
futures = { workspace = true }
gethostname = { workspace = true }
hmac = { workspace = true }
jsonwebtoken = { workspace = true }
owo-colors = { workspace = true, features = ["supports-colors"] }
@@ -83,7 +76,6 @@ tokio-util = { workspace = true }
tokio-tungstenite = { workspace = true }
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json"] }
url = { workspace = true }
uuid = { workspace = true, features = ["serde", "v7"] }
[dev-dependencies]

View File

@@ -25,7 +25,6 @@ Supported transports:
- stdio (`--listen stdio://`, default): newline-delimited JSON (JSONL)
- websocket (`--listen ws://IP:PORT`): one JSON-RPC message per websocket text frame (**experimental / unsupported**)
- off (`--listen off`): do not expose a local transport
When running with `--listen ws://IP:PORT`, the same listener also serves basic HTTP health probes:
@@ -133,9 +132,9 @@ Example with notification opt-out:
## API Overview
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`.
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
- `thread/loaded/list` — list the thread ids currently loaded in memory.
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
@@ -145,6 +144,11 @@ Example with notification opt-out:
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server shuts down and unloads the thread, then emits `thread/closed`.
- `thread/name/set` — set or update a threads user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success and emits `thread/name/updated` to initialized, opted-in clients. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.
- `thread/job/create` — create a runtime-only job on a loaded thread; accepts `cronExpression`, `prompt`, and optional `runOnce`, returns the created job, and requires the experimental feature flag `job_scheduler` to be enabled for that thread.
- `thread/job/delete` — delete one runtime-only job from a loaded thread by id; returns `{}` and requires `job_scheduler`.
- `thread/job/list` — list runtime-only jobs for a loaded thread; returns `{ data }` and requires `job_scheduler`.
- `thread/job/updated` — notification emitted when a loaded threads job list changes, including run-once jobs deleting themselves after execution.
- `thread/job/fired` — notification emitted when a thread job fires and starts a synthetic follow-on turn.
- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.
- `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream.
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
@@ -152,7 +156,7 @@ Example with notification opt-out:
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. Review and manual compaction turns reject `turn/steer`.
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
- `thread/realtime/start` — start a thread-scoped realtime session (experimental); returns `{}` and streams `thread/realtime/*` notifications. Omit `transport` for the websocket transport, or pass `{ "type": "webrtc", "sdp": "..." }` to create a WebRTC session from a browser-generated SDP offer; the remote answer SDP is emitted as `thread/realtime/sdp`.
- `thread/realtime/start` — start a thread-scoped realtime session (experimental); returns `{}` and streams `thread/realtime/*` notifications.
- `thread/realtime/appendAudio` — append an input audio chunk to the active realtime session (experimental); returns `{}`.
- `thread/realtime/appendText` — append text input to the active realtime session (experimental); returns `{}`.
- `thread/realtime/stop` — stop the active realtime session for the thread (experimental); returns `{}`.
@@ -169,10 +173,10 @@ Example with notification opt-out:
- `fs/readDirectory` — list direct child entries for an absolute directory path; each entry contains `fileName`, `isDirectory`, and `isFile`, and `fileName` is just the child name, not a path.
- `fs/remove` — remove an absolute file or directory tree; `recursive` and `force` default to `true`.
- `fs/copy` — copy between absolute paths; directory copies require `recursive: true`.
- `fs/watch` — subscribe this connection to filesystem change notifications for an absolute file or directory path and caller-provided `watchId`; returns the canonicalized `path`.
- `fs/watch` — subscribe this connection to filesystem change notifications for an absolute file or directory path; returns a `watchId` and canonicalized `path`.
- `fs/unwatch` — stop sending notifications for a prior `fs/watch`; returns `{}`.
- `fs/changed` — notification emitted when watched paths change, including the `watchId` and `changedPaths`.
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, `additionalSpeedTiers`, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
- `model/list` — list available models (set `includeHidden: true` to include entries with `hidden: true`), with reasoning effort options, optional legacy `upgrade` model ids, optional `upgradeInfo` metadata (`model`, `upgradeCopy`, `modelLink`, `migrationMarkdown`), and optional `availabilityNux` metadata.
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `experimentalFeature/enablement/set` — patch the in-memory process-wide runtime feature enablement for the currently supported feature keys (`apps`, `plugins`). For each feature, precedence is: cloud requirements > --enable <feature_name> > config.toml > experimentalFeature/enablement/set (new) > code default.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
@@ -187,8 +191,7 @@ Example with notification opt-out:
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
- `tool/requestUserInput` — prompt the user with 13 short questions for a tool call and return their answers (experimental).
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools and auth status, plus resources/resource templates for `full` detail; supports cursor+limit pagination. If `detail` is omitted, the server defaults to `full`.
- `mcpServer/resource/read` — read a resource from a thread's configured MCP server by `threadId`, `server`, and `uri`, returning text/blob resource `contents`.
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
- `config/read` — fetch the effective config on disk after resolving config layering.
@@ -196,7 +199,7 @@ Example with notification opt-out:
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home).
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly`.
### Example: Start or resume a thread
@@ -275,7 +278,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.
@@ -563,51 +566,6 @@ Invoke a plugin by including a UI mention token such as `@sample` in the text in
} } }
```
### Example: Start realtime with WebRTC
Use `thread/realtime/start` with `transport.type: "webrtc"` when a browser or webview owns the `RTCPeerConnection` and app-server should create the server-side realtime session. The transport `sdp` must be the offer SDP produced by `RTCPeerConnection.createOffer()`, not a hand-written or minimal SDP string.
The offer should include the media sections the client wants to negotiate. For the standard realtime UI flow, create the audio track/transceiver and the `oai-events` data channel before calling `createOffer()`:
```javascript
const pc = new RTCPeerConnection();
audioElement.autoplay = true;
pc.ontrack = (event) => {
audioElement.srcObject = event.streams[0];
};
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
pc.addTrack(mediaStream.getAudioTracks()[0], mediaStream);
pc.createDataChannel("oai-events");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
```
Then send `offer.sdp` to app-server. Core uses `experimental_realtime_ws_backend_prompt` for the backend instructions and the thread conversation id for the realtime session id. The start response is `{}`; the remote answer SDP arrives later as `thread/realtime/sdp` and should be passed to `setRemoteDescription()`:
```json
{ "method": "thread/realtime/start", "id": 40, "params": {
"threadId": "thr_123",
"prompt": "You are on a call.",
"sessionId": null,
"transport": { "type": "webrtc", "sdp": "v=0\r\no=..." }
} }
{ "id": 40, "result": {} }
{ "method": "thread/realtime/sdp", "params": {
"threadId": "thr_123",
"sdp": "v=0\r\no=..."
} }
```
```javascript
await pc.setRemoteDescription({
type: "answer",
sdp: notification.params.sdp,
});
```
### Example: Interrupt an active turn
You can cancel a running Turn with `turn/interrupt`.
@@ -861,10 +819,10 @@ All filesystem paths in this section must be absolute.
```json
{ "method": "fs/watch", "id": 44, "params": {
"watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
"path": "/Users/me/project/.git/HEAD"
} }
{ "id": 44, "result": {
"watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1",
"path": "/Users/me/project/.git/HEAD"
} }
{ "method": "fs/changed", "params": {
@@ -960,10 +918,10 @@ All items emit shared lifecycle events:
- `item/started` — emits the full `item` when a new unit of work begins so the UI can render it immediately; the `item.id` in this payload matches the `itemId` used by deltas.
- `item/completed` — sends the final `item` once that work itself finishes (for example, after a tool call or message completes); treat this as the authoritative execution/result state.
- `item/autoApprovalReview/started` — [UNSTABLE] temporary guardian notification carrying `{threadId, turnId, targetItemId, review, action}` when guardian approval review begins. This shape is expected to change soon.
- `item/autoApprovalReview/completed` — [UNSTABLE] temporary guardian notification carrying `{threadId, turnId, targetItemId, review, action}` when guardian approval review resolves. This shape is expected to change soon.
- `item/autoApprovalReview/started` — [UNSTABLE] temporary guardian notification carrying `{threadId, turnId, targetItemId, review, action?}` when guardian approval review begins. This shape is expected to change soon.
- `item/autoApprovalReview/completed` — [UNSTABLE] temporary guardian notification carrying `{threadId, turnId, targetItemId, review, action?}` when guardian approval review resolves. This shape is expected to change soon.
`review` is [UNSTABLE] and currently has `{status, riskScore?, riskLevel?, rationale?}`, where `status` is one of `inProgress`, `approved`, `denied`, or `aborted`. `action` is a tagged union with `type: "command" | "execve" | "applyPatch" | "networkAccess" | "mcpToolCall"`. Command-like actions include a `source` discriminator (`"shell"` or `"unifiedExec"`). These notifications are separate from the target item's own `item/completed` lifecycle and are intentionally temporary while the guardian app protocol is still being designed.
`review` is [UNSTABLE] and currently has `{status, riskScore?, riskLevel?, rationale?}`, where `status` is one of `inProgress`, `approved`, `denied`, or `aborted`. `action` is the guardian action summary payload from core when available and is intended to support temporary standalone pending-review UI. These notifications are separate from the target item's own `item/completed` lifecycle and are intentionally temporary while the guardian app protocol is still being designed.
There are additional item-specific events:

View File

@@ -86,7 +86,6 @@ fn transport_name(transport: AppServerTransport) -> &'static str {
match transport {
AppServerTransport::Stdio => "stdio",
AppServerTransport::WebSocket { .. } => "websocket",
AppServerTransport::Off => "off",
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,12 @@ use std::sync::Arc;
use codex_app_server_protocol::McpServerOauthLoginCompletedNotification;
use codex_app_server_protocol::ServerNotification;
use codex_config::types::McpServerConfig;
use codex_core::config::Config;
use codex_mcp::McpOAuthLoginSupport;
use codex_mcp::oauth_login_support;
use codex_mcp::resolve_oauth_scopes;
use codex_mcp::should_retry_without_scopes;
use codex_core::config::types::McpServerConfig;
use codex_core::mcp::auth::McpOAuthLoginSupport;
use codex_core::mcp::auth::oauth_login_support;
use codex_core::mcp::auth::resolve_oauth_scopes;
use codex_core::mcp::auth::should_retry_without_scopes;
use codex_rmcp_client::perform_oauth_login_silent;
use tracing::warn;

View File

@@ -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;

View File

@@ -1,7 +1,6 @@
use crate::error_code::INTERNAL_ERROR_CODE;
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
use async_trait::async_trait;
use codex_analytics::AnalyticsEventsClient;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigReadResponse;
@@ -17,6 +16,7 @@ use codex_app_server_protocol::NetworkDomainPermission;
use codex_app_server_protocol::NetworkRequirements;
use codex_app_server_protocol::NetworkUnixSocketPermission;
use codex_app_server_protocol::SandboxMode;
use codex_core::AnalyticsEventsClient;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config::ConfigService;
@@ -366,12 +366,6 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
.map(codex_app_server_protocol::AskForApproval::from)
.collect()
}),
allowed_approvals_reviewers: requirements.allowed_approvals_reviewers.map(|reviewers| {
reviewers
.into_iter()
.map(codex_app_server_protocol::ApprovalsReviewer::from)
.collect()
}),
allowed_sandbox_modes: requirements.allowed_sandbox_modes.map(|modes| {
modes
.into_iter()
@@ -449,7 +443,6 @@ fn map_network_requirements_to_api(
.collect()
}),
managed_allowed_domains_only: network.managed_allowed_domains_only,
danger_full_access_denylist_only: network.danger_full_access_denylist_only,
allowed_domains,
denied_domains,
unix_sockets: network.unix_sockets.map(|unix_sockets| {
@@ -517,16 +510,15 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) ->
#[cfg(test)]
mod tests {
use super::*;
use codex_analytics::AnalyticsEventsClient;
use codex_core::AnalyticsEventsClient;
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::config_loader::NetworkDomainPermissionToml as CoreNetworkDomainPermissionToml;
use codex_core::config_loader::NetworkDomainPermissionsToml as CoreNetworkDomainPermissionsToml;
use codex_core::config_loader::NetworkRequirementsToml as CoreNetworkRequirementsToml;
use codex_core::config_loader::NetworkUnixSocketPermissionToml as CoreNetworkUnixSocketPermissionToml;
use codex_core::config_loader::NetworkUnixSocketPermissionsToml as CoreNetworkUnixSocketPermissionsToml;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -553,10 +545,6 @@ mod tests {
CoreAskForApproval::Never,
CoreAskForApproval::OnRequest,
]),
allowed_approvals_reviewers: Some(vec![
CoreApprovalsReviewer::User,
CoreApprovalsReviewer::GuardianSubagent,
]),
allowed_sandbox_modes: Some(vec![
CoreSandboxModeRequirement::ReadOnly,
CoreSandboxModeRequirement::ExternalSandbox,
@@ -595,7 +583,6 @@ mod tests {
]),
}),
managed_allowed_domains_only: Some(false),
danger_full_access_denylist_only: Some(true),
unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml {
entries: std::collections::BTreeMap::from([(
"/tmp/proxy.sock".to_string(),
@@ -615,13 +602,6 @@ mod tests {
codex_app_server_protocol::AskForApproval::OnRequest,
])
);
assert_eq!(
mapped.allowed_approvals_reviewers,
Some(vec![
codex_app_server_protocol::ApprovalsReviewer::User,
codex_app_server_protocol::ApprovalsReviewer::GuardianSubagent,
])
);
assert_eq!(
mapped.allowed_sandbox_modes,
Some(vec![SandboxMode::ReadOnly]),
@@ -655,7 +635,6 @@ mod tests {
("example.com".to_string(), NetworkDomainPermission::Deny),
])),
managed_allowed_domains_only: Some(false),
danger_full_access_denylist_only: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
unix_sockets: Some(std::collections::BTreeMap::from([(
@@ -672,7 +651,6 @@ mod tests {
fn map_requirements_toml_to_api_omits_unix_socket_none_entries_from_legacy_network_fields() {
let requirements = ConfigRequirementsToml {
allowed_approval_policies: None,
allowed_approvals_reviewers: None,
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
guardian_developer_instructions: None,
@@ -690,7 +668,6 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
unix_sockets: Some(CoreNetworkUnixSocketPermissionsToml {
entries: std::collections::BTreeMap::from([(
"/tmp/ignored.sock".to_string(),
@@ -714,7 +691,6 @@ mod tests {
dangerously_allow_all_unix_sockets: None,
domains: None,
managed_allowed_domains_only: None,
danger_full_access_denylist_only: None,
allowed_domains: None,
denied_domains: None,
unix_sockets: Some(std::collections::BTreeMap::from([(
@@ -731,7 +707,6 @@ mod tests {
fn map_requirements_toml_to_api_normalizes_allowed_web_search_modes() {
let requirements = ConfigRequirementsToml {
allowed_approval_policies: None,
allowed_approvals_reviewers: None,
allowed_sandbox_modes: None,
allowed_web_search_modes: Some(Vec::new()),
guardian_developer_instructions: None,

View File

@@ -1,4 +1,3 @@
use crate::fs_api::invalid_request;
use crate::outgoing_message::ConnectionId;
use crate::outgoing_message::OutgoingMessageSender;
use codex_app_server_protocol::FsChangedNotification;
@@ -17,7 +16,6 @@ use codex_core::file_watcher::WatchRegistration;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::hash_map::Entry;
use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
@@ -28,6 +26,7 @@ use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio::time::Instant;
use tracing::warn;
use uuid::Uuid;
const FS_CHANGED_NOTIFICATION_DEBOUNCE: Duration = Duration::from_millis(200);
@@ -121,11 +120,7 @@ impl FsWatchManager {
connection_id: ConnectionId,
params: FsWatchParams,
) -> Result<FsWatchResponse, JSONRPCErrorError> {
let watch_id = params.watch_id;
let watch_key = WatchKey {
connection_id,
watch_id: watch_id.clone(),
};
let watch_id = Uuid::now_v7().to_string();
let outgoing = self.outgoing.clone();
let (subscriber, rx) = self.file_watcher.add_subscriber();
let watch_root = params.path.to_path_buf().clone();
@@ -135,20 +130,17 @@ impl FsWatchManager {
}]);
let (terminate_tx, terminate_rx) = oneshot::channel();
match self.state.lock().await.entries.entry(watch_key) {
Entry::Occupied(_) => {
return Err(invalid_request(format!(
"watchId already exists: {watch_id}"
)));
}
Entry::Vacant(entry) => {
entry.insert(WatchEntry {
terminate_tx,
_subscriber: subscriber,
_registration: registration,
});
}
}
self.state.lock().await.entries.insert(
WatchKey {
connection_id,
watch_id: watch_id.clone(),
},
WatchEntry {
terminate_tx,
_subscriber: subscriber,
_registration: registration,
},
);
let task_watch_id = watch_id.clone();
tokio::spawn(async move {
@@ -166,7 +158,19 @@ impl FsWatchManager {
let mut changed_paths = event
.paths
.into_iter()
.map(|path| AbsolutePathBuf::resolve_path_against_base(&path, &watch_root))
.filter_map(|path| {
match AbsolutePathBuf::resolve_path_against_base(&path, &watch_root) {
Ok(path) => Some(path),
Err(err) => {
warn!(
"failed to normalize watch event path ({}) for {}: {err}",
path.display(),
watch_root.display()
);
None
}
}
})
.collect::<Vec<_>>();
changed_paths.sort_by(|left, right| left.as_path().cmp(right.as_path()));
if !changed_paths.is_empty() {
@@ -183,7 +187,10 @@ impl FsWatchManager {
}
});
Ok(FsWatchResponse { path: params.path })
Ok(FsWatchResponse {
watch_id,
path: params.path,
})
}
pub(crate) async fn unwatch(
@@ -221,6 +228,7 @@ mod tests {
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use uuid::Version;
fn absolute_path(path: PathBuf) -> AbsolutePathBuf {
assert!(
@@ -241,33 +249,28 @@ mod tests {
}
#[tokio::test]
async fn watch_uses_client_id_and_tracks_the_owner_scoped_entry() {
async fn watch_returns_a_v7_id_and_tracks_the_owner_scoped_entry() {
let temp_dir = TempDir::new().expect("temp dir");
let head_path = temp_dir.path().join("HEAD");
std::fs::write(&head_path, "ref: refs/heads/main\n").expect("write HEAD");
let manager = manager_with_noop_watcher();
let path = absolute_path(head_path);
let watch_id = "watch-head".to_string();
let response = manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: watch_id.clone(),
path: path.clone(),
},
)
.watch(ConnectionId(1), FsWatchParams { path: path.clone() })
.await
.expect("watch should succeed");
assert_eq!(response.path, path);
let watch_id = Uuid::parse_str(&response.watch_id).expect("watch id should be a UUID");
assert_eq!(watch_id.get_version(), Some(Version::SortRand));
let state = manager.state.lock().await;
assert_eq!(
state.entries.keys().cloned().collect::<HashSet<_>>(),
HashSet::from([WatchKey {
connection_id: ConnectionId(1),
watch_id,
watch_id: response.watch_id,
}])
);
}
@@ -279,11 +282,10 @@ mod tests {
std::fs::write(&head_path, "ref: refs/heads/main\n").expect("write HEAD");
let manager = manager_with_noop_watcher();
manager
let response = manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: "watch-head".to_string(),
path: absolute_path(head_path),
},
)
@@ -291,14 +293,14 @@ mod tests {
.expect("watch should succeed");
let watch_key = WatchKey {
connection_id: ConnectionId(1),
watch_id: "watch-head".to_string(),
watch_id: response.watch_id.clone(),
};
manager
.unwatch(
ConnectionId(2),
FsUnwatchParams {
watch_id: "watch-head".to_string(),
watch_id: response.watch_id.clone(),
},
)
.await
@@ -309,7 +311,7 @@ mod tests {
.unwatch(
ConnectionId(1),
FsUnwatchParams {
watch_id: "watch-head".to_string(),
watch_id: response.watch_id,
},
)
.await
@@ -317,41 +319,6 @@ mod tests {
assert!(!manager.state.lock().await.entries.contains_key(&watch_key));
}
#[tokio::test]
async fn watch_rejects_duplicate_id_for_the_same_connection() {
let temp_dir = TempDir::new().expect("temp dir");
let head_path = temp_dir.path().join("HEAD");
let fetch_head_path = temp_dir.path().join("FETCH_HEAD");
std::fs::write(&head_path, "ref: refs/heads/main\n").expect("write HEAD");
std::fs::write(&fetch_head_path, "old-fetch\n").expect("write FETCH_HEAD");
let manager = manager_with_noop_watcher();
manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: "watch-head".to_string(),
path: absolute_path(head_path),
},
)
.await
.expect("first watch should succeed");
let error = manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: "watch-head".to_string(),
path: absolute_path(fetch_head_path),
},
)
.await
.expect_err("duplicate watch should fail");
assert_eq!(error.message, "watchId already exists: watch-head");
assert_eq!(manager.state.lock().await.entries.len(), 1);
}
#[tokio::test]
async fn connection_closed_removes_only_that_connections_watches() {
let temp_dir = TempDir::new().expect("temp dir");
@@ -363,31 +330,28 @@ mod tests {
std::fs::write(&packed_refs_path, "refs\n").expect("write packed-refs");
let manager = manager_with_noop_watcher();
let response = manager
let response_1 = manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: "watch-head".to_string(),
path: absolute_path(head_path.clone()),
path: absolute_path(head_path),
},
)
.await
.expect("first watch should succeed");
manager
let response_2 = manager
.watch(
ConnectionId(1),
FsWatchParams {
watch_id: "watch-fetch-head".to_string(),
path: absolute_path(fetch_head_path),
},
)
.await
.expect("second watch should succeed");
manager
let response_3 = manager
.watch(
ConnectionId(2),
FsWatchParams {
watch_id: "watch-packed-refs".to_string(),
path: absolute_path(packed_refs_path),
},
)
@@ -407,9 +371,9 @@ mod tests {
.collect::<HashSet<_>>(),
HashSet::from([WatchKey {
connection_id: ConnectionId(2),
watch_id: "watch-packed-refs".to_string(),
watch_id: response_3.watch_id,
}])
);
assert_eq!(response.path, absolute_path(head_path));
assert_ne!(response_1.watch_id, response_2.watch_id);
}
}

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