Merge remote-tracking branch 'origin/main' into abhinav/managed-hook-visibility-hint

# Conflicts:
#	codex-rs/app-server-protocol/schema/json/ServerNotification.json
#	codex-rs/tui/src/history_cell.rs
This commit is contained in:
Abhinav Vedmala
2026-05-19 14:32:55 -07:00
1034 changed files with 65632 additions and 36917 deletions
+5
View File
@@ -193,5 +193,10 @@ common --@v8//:v8_enable_sandbox=True
common:v8-release-compat --@v8//:v8_enable_pointer_compression=False
common:v8-release-compat --@v8//:v8_enable_sandbox=False
# Match rusty_v8's upstream GN release contract for published artifacts: every
# target object uses Chromium's custom libc++ headers and the archive folds in
# the matching runtime objects.
common:rusty-v8-upstream-libcxx --@v8//:v8_use_rusty_v8_custom_libcxx=True
# Optional per-user local overrides.
try-import %workspace%/user.bazelrc
+72
View File
@@ -0,0 +1,72 @@
---
name: update-v8-version
description: Update Codex's pinned `v8` / `rusty_v8` versions, validate the release-candidate path, and investigate failed V8 canary or artifact builds. Use when asked to bump V8, update `rusty_v8` artifacts, prepare or validate a V8 release candidate, check `v8-canary`, or diagnose why a V8 version update no longer builds.
---
# Update V8 Version
## Core Workflow
1. Read `third_party/v8/README.md` and follow its version-bump sequence. Treat
that document as the release-process source of truth.
2. Inspect and update the concrete repo surfaces that carry the pin:
- `codex-rs/Cargo.toml`
- `codex-rs/Cargo.lock`
- `MODULE.bazel`
- `third_party/v8/BUILD.bazel`
- `third_party/v8/README.md`
- the matching `third_party/v8/rusty_v8_<version>.sha256` manifest when the
remaining prebuilt inputs change
3. Keep the existing checksum helpers in the loop:
```bash
python3 .github/scripts/rusty_v8_bazel.py update-module-bazel
python3 .github/scripts/rusty_v8_bazel.py check-module-bazel
python3 -m unittest discover -s .github/scripts -p test_rusty_v8_bazel.py
```
4. Validate the release-candidate path before broadening the work:
- Prefer checking the `v8-canary` CI result for the candidate branch or PR
when one exists, using GitHub check tooling or `gh` as appropriate.
- If CI is unavailable or the user asked for a local-only check, run the
closest local validation that is practical for the changed surface and say
explicitly that it is a local substitute, not the full hosted canary.
5. If the canary path passes, stop there. Summarize the result and encourage the
user to commit the candidate changes or proceed with the release flow they
requested. Do not publish tags, releases, or pushes unless the user asked.
## Failure Path
Enter this path only when the canary or local build path fails.
1. Capture the failing target, workflow job, and first actionable error.
2. Compare the currently pinned version with the target version at the relevant
upstream tag or SHA. Inspect both:
- `denoland/rusty_v8`
- upstream V8 source at the target Bazel-pinned version
3. Track build-relevant deltas rather than broad source churn:
- generated binding layout changes
- archive or asset naming changes
- GN/Bazel target changes
- custom libc++ / libc++abi / llvm-libc inputs
- sandbox or pointer-compression feature relationships
- patch hunks in `patches/` that no longer apply or no longer match upstream
4. Trace each failing delta back into Codex's build graph:
- `MODULE.bazel`
- `third_party/v8/BUILD.bazel`
- `.github/scripts/rusty_v8_bazel.py`
- `.github/workflows/v8-canary.yml`
- `.github/workflows/rusty-v8-release.yml`
5. Update only the pieces required to restore the target version's build and
artifact contract. Keep patch explanations and doc changes close to the
affected files.
6. Re-run the focused validation. If it becomes green, return to the normal
workflow and stop with a concise summary plus the remaining release step.
## Reporting
- Say whether validation came from hosted `v8-canary` or from a local
substitute.
- Distinguish "version bump complete" from "release published".
- When blocked, report the upstream delta that matters, the Codex file it hits,
and the next concrete fix to try.
@@ -0,0 +1,4 @@
interface:
display_name: "Update V8 Version"
short_description: "Guide V8 bumps and release validation"
default_prompt: "Use $update-v8-version to update Codex to a new v8 release and validate the release-candidate path."
+12
View File
@@ -11,6 +11,8 @@ body:
Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed.
If your version supports it, please run `codex doctor --json` and paste the output in the "Codex doctor report" field below. This helps us diagnose install, config, auth, terminal, MCP, network, and local state issues.
- type: input
id: version
attributes:
@@ -43,6 +45,16 @@ body:
description: |
Also note any multiplexer in use (screen / tmux / zellij).
E.g., VS Code, Terminal.app, iTerm2, Ghostty, Windows Terminal (WSL / PowerShell)
- type: textarea
id: doctor
attributes:
label: Codex doctor report
description: |
If available, run `codex doctor --json` and paste the full output here.
The report is designed to redact secrets, but please review it before submitting.
If your Codex version does not support `doctor`, write `not available`.
render: json
- type: textarea
id: actual
attributes:
@@ -31,16 +31,14 @@ runs:
archive_path="${binding_dir}/librusty_v8_release_${TARGET}.a.gz"
binding_path="${binding_dir}/src_binding_release_${TARGET}.rs"
checksums_path="${binding_dir}/rusty_v8_release_${TARGET}.sha256"
checksums_source="${GITHUB_WORKSPACE}/third_party/v8/rusty_v8_${version//./_}.sha256"
mkdir -p "${binding_dir}"
curl -fsSL "${base_url}/librusty_v8_release_${TARGET}.a.gz" -o "${archive_path}"
curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}"
grep -E " (librusty_v8_release_${TARGET}[.]a[.]gz|src_binding_release_${TARGET}[.]rs)$" \
"${checksums_source}" > "${checksums_path}"
curl -fsSL "${base_url}/rusty_v8_release_${TARGET}.sha256" -o "${checksums_path}"
if [[ "$(wc -l < "${checksums_path}")" -ne 2 ]]; then
echo "Expected exactly two checksums for ${TARGET} in ${checksums_source}" >&2
echo "Expected exactly two checksums for ${TARGET} in ${checksums_path}" >&2
exit 1
fi
+131 -100
View File
@@ -5,17 +5,18 @@ from __future__ import annotations
import argparse
import gzip
import hashlib
import os
import re
import shutil
import subprocess
import sys
import tempfile
import tomllib
from pathlib import Path
from rusty_v8_module_bazel import (
RustyV8ChecksumError,
check_module_bazel,
rusty_v8_http_file_versions,
update_module_bazel,
)
@@ -23,12 +24,16 @@ from rusty_v8_module_bazel import (
ROOT = Path(__file__).resolve().parents[2]
MODULE_BAZEL = ROOT / "MODULE.bazel"
RUSTY_V8_CHECKSUMS_DIR = ROOT / "third_party" / "v8"
MUSL_RUNTIME_ARCHIVE_LABELS = [
"@llvm//runtimes/libcxx:libcxx.static",
"@llvm//runtimes/libcxx:libcxxabi.static",
]
LLVM_AR_LABEL = "@llvm//tools:llvm-ar"
LLVM_RANLIB_LABEL = "@llvm//tools:llvm-ranlib"
RELEASE_ARTIFACT_PROFILE = "release"
SANDBOX_ARTIFACT_PROFILE = "ptrcomp_sandbox_release"
ARTIFACT_BAZEL_CONFIGS = ["rusty-v8-upstream-libcxx"]
def bazel_remote_args() -> list[str]:
buildbuddy_api_key = os.environ.get("BUILDBUDDY_API_KEY")
if not buildbuddy_api_key:
return []
return [f"--remote_header=x-buildbuddy-api-key={buildbuddy_api_key}"]
def bazel_execroot() -> Path:
@@ -75,6 +80,7 @@ def bazel_output_files(
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
*bazel_remote_args(),
"--output=files",
expression,
],
@@ -91,8 +97,10 @@ def bazel_build(
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
download_toplevel: bool = False,
) -> None:
bazel_configs = bazel_configs or []
download_args = ["--remote_download_toplevel"] if download_toplevel else []
subprocess.run(
[
"bazel",
@@ -101,6 +109,8 @@ def bazel_build(
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
*bazel_remote_args(),
*download_args,
*labels,
],
cwd=ROOT,
@@ -114,11 +124,15 @@ def ensure_bazel_output_files(
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> list[Path]:
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
if all(path.exists() for path in outputs):
return outputs
bazel_build(platform, labels, compilation_mode, bazel_configs)
# Bazel output paths can be reused across config flips, so existence alone
# does not prove the files match the requested flags.
bazel_build(
platform,
labels,
compilation_mode,
bazel_configs,
download_toplevel=True,
)
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
missing = [str(path) for path in outputs if not path.exists()]
if missing:
@@ -126,9 +140,18 @@ def ensure_bazel_output_files(
return outputs
def release_pair_label(target: str) -> str:
def artifact_bazel_configs(bazel_configs: list[str] | None = None) -> list[str]:
configured = list(ARTIFACT_BAZEL_CONFIGS)
for config in bazel_configs or []:
if config not in configured:
configured.append(config)
return configured
def release_pair_label(target: str, sandbox: bool = False) -> str:
target_suffix = target.replace("-", "_")
return f"//third_party/v8:rusty_v8_release_pair_{target_suffix}"
pair_kind = "sandbox_release_pair" if sandbox else "release_pair"
return f"//third_party/v8:rusty_v8_{pair_kind}_{target_suffix}"
def resolved_v8_crate_version() -> str:
@@ -169,6 +192,16 @@ def rusty_v8_checksum_manifest_path(version: str) -> Path:
def command_version(version: str | None) -> str:
if version is not None:
return version
manifest_versions = rusty_v8_http_file_versions(MODULE_BAZEL.read_text())
if len(manifest_versions) == 1:
return manifest_versions[0]
if len(manifest_versions) > 1:
raise SystemExit(
"expected at most one rusty_v8 http_file version in MODULE.bazel, "
f"found: {manifest_versions}; pass --version explicitly"
)
return resolved_v8_crate_version()
@@ -180,66 +213,76 @@ def command_manifest_path(manifest: Path | None, version: str) -> Path:
return ROOT / manifest
def staged_archive_name(target: str, source_path: Path) -> str:
if source_path.suffix == ".lib":
return f"rusty_v8_release_{target}.lib.gz"
return f"librusty_v8_release_{target}.a.gz"
def staged_archive_name(target: str, source_path: Path, artifact_profile: str) -> str:
if target.endswith("-pc-windows-msvc"):
return f"rusty_v8_{artifact_profile}_{target}.lib.gz"
return f"librusty_v8_{artifact_profile}_{target}.a.gz"
def is_musl_archive_target(target: str, source_path: Path) -> bool:
return target.endswith("-unknown-linux-musl") and source_path.suffix == ".a"
def staged_binding_name(target: str, artifact_profile: str) -> str:
return f"src_binding_{artifact_profile}_{target}.rs"
def single_bazel_output_file(
platform: str,
label: str,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
outputs = ensure_bazel_output_files(platform, [label], compilation_mode, bazel_configs)
if len(outputs) != 1:
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
return outputs[0]
def staged_checksums_name(target: str, artifact_profile: str) -> str:
return f"rusty_v8_{artifact_profile}_{target}.sha256"
def merged_musl_archive(
platform: str,
def stage_artifacts(
target: str,
lib_path: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode, bazel_configs)
llvm_ranlib = single_bazel_output_file(
platform,
LLVM_RANLIB_LABEL,
compilation_mode,
bazel_configs,
)
runtime_archives = [
single_bazel_output_file(platform, label, compilation_mode, bazel_configs)
for label in MUSL_RUNTIME_ARCHIVE_LABELS
]
binding_path: Path,
output_dir: Path,
sandbox: bool,
) -> None:
missing_paths = [str(path) for path in [lib_path, binding_path] if not path.exists()]
if missing_paths:
raise SystemExit(f"missing release outputs for {target}: {missing_paths}")
temp_dir = Path(tempfile.mkdtemp(prefix="rusty-v8-musl-stage-"))
merged_archive = temp_dir / lib_path.name
merge_commands = "\n".join(
[
f"create {merged_archive}",
f"addlib {lib_path}",
*[f"addlib {archive}" for archive in runtime_archives],
"save",
"end",
]
)
subprocess.run(
[str(llvm_ar), "-M"],
cwd=ROOT,
check=True,
input=merge_commands,
text=True,
)
subprocess.run([str(llvm_ranlib), str(merged_archive)], cwd=ROOT, check=True)
return merged_archive
output_dir.mkdir(parents=True, exist_ok=True)
artifact_profile = SANDBOX_ARTIFACT_PROFILE if sandbox else RELEASE_ARTIFACT_PROFILE
staged_library = output_dir / staged_archive_name(target, lib_path, artifact_profile)
staged_binding = output_dir / staged_binding_name(target, artifact_profile)
with lib_path.open("rb") as src, staged_library.open("wb") as dst:
with gzip.GzipFile(
filename="",
mode="wb",
fileobj=dst,
compresslevel=6,
mtime=0,
) as gz:
shutil.copyfileobj(src, gz)
shutil.copyfile(binding_path, staged_binding)
staged_checksums = output_dir / staged_checksums_name(target, artifact_profile)
with staged_checksums.open("w", encoding="utf-8") as checksums:
for path in [staged_library, staged_binding]:
digest = hashlib.sha256()
with path.open("rb") as artifact:
for chunk in iter(lambda: artifact.read(1024 * 1024), b""):
digest.update(chunk)
checksums.write(f"{digest.hexdigest()} {path.name}\n")
print(staged_library)
print(staged_binding)
print(staged_checksums)
def upstream_release_pair_paths(source_root: Path, target: str) -> tuple[Path, Path]:
lib_name = "rusty_v8.lib" if target.endswith("-pc-windows-msvc") else "librusty_v8.a"
gn_out = source_root / "target" / target / "release" / "gn_out"
return gn_out / "obj" / lib_name, gn_out / "src_binding.rs"
def stage_upstream_release_pair(
source_root: Path,
target: str,
output_dir: Path,
sandbox: bool = False,
) -> None:
lib_path, binding_path = upstream_release_pair_paths(source_root, target)
stage_artifacts(target, lib_path, binding_path, output_dir, sandbox)
def stage_release_pair(
@@ -248,10 +291,12 @@ def stage_release_pair(
output_dir: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
sandbox: bool = False,
) -> None:
bazel_configs = artifact_bazel_configs(bazel_configs)
outputs = ensure_bazel_output_files(
platform,
[release_pair_label(target)],
[release_pair_label(target, sandbox)],
compilation_mode,
bazel_configs,
)
@@ -266,39 +311,7 @@ def stage_release_pair(
except StopIteration as exc:
raise SystemExit(f"missing Rust binding output for {target}") from exc
output_dir.mkdir(parents=True, exist_ok=True)
staged_library = output_dir / staged_archive_name(target, lib_path)
staged_binding = output_dir / f"src_binding_release_{target}.rs"
source_archive = (
merged_musl_archive(platform, lib_path, compilation_mode, bazel_configs)
if is_musl_archive_target(target, lib_path)
else lib_path
)
with source_archive.open("rb") as src, staged_library.open("wb") as dst:
with gzip.GzipFile(
filename="",
mode="wb",
fileobj=dst,
compresslevel=6,
mtime=0,
) as gz:
shutil.copyfileobj(src, gz)
shutil.copyfile(binding_path, staged_binding)
staged_checksums = output_dir / f"rusty_v8_release_{target}.sha256"
with staged_checksums.open("w", encoding="utf-8") as checksums:
for path in [staged_library, staged_binding]:
digest = hashlib.sha256()
with path.open("rb") as artifact:
for chunk in iter(lambda: artifact.read(1024 * 1024), b""):
digest.update(chunk)
checksums.write(f"{digest.hexdigest()} {path.name}\n")
print(staged_library)
print(staged_binding)
print(staged_checksums)
stage_artifacts(target, lib_path, binding_path, output_dir, sandbox)
def parse_args() -> argparse.Namespace:
@@ -309,6 +322,7 @@ def parse_args() -> argparse.Namespace:
stage_release_pair_parser.add_argument("--platform", required=True)
stage_release_pair_parser.add_argument("--target", required=True)
stage_release_pair_parser.add_argument("--output-dir", required=True)
stage_release_pair_parser.add_argument("--sandbox", action="store_true")
stage_release_pair_parser.add_argument(
"--bazel-config",
action="append",
@@ -321,6 +335,14 @@ def parse_args() -> argparse.Namespace:
choices=["fastbuild", "opt", "dbg"],
)
stage_upstream_release_pair_parser = subparsers.add_parser(
"stage-upstream-release-pair"
)
stage_upstream_release_pair_parser.add_argument("--source-root", type=Path, required=True)
stage_upstream_release_pair_parser.add_argument("--target", required=True)
stage_upstream_release_pair_parser.add_argument("--output-dir", required=True)
stage_upstream_release_pair_parser.add_argument("--sandbox", action="store_true")
subparsers.add_parser("resolved-v8-crate-version")
check_module_bazel_parser = subparsers.add_parser("check-module-bazel")
@@ -353,6 +375,15 @@ def main() -> int:
output_dir=Path(args.output_dir),
compilation_mode=args.compilation_mode,
bazel_configs=args.bazel_configs,
sandbox=args.sandbox,
)
return 0
if args.command == "stage-upstream-release-pair":
stage_upstream_release_pair(
source_root=args.source_root,
target=args.target,
output_dir=Path(args.output_dir),
sandbox=args.sandbox,
)
return 0
if args.command == "resolved-v8-crate-version":
+13
View File
@@ -9,6 +9,7 @@ from pathlib import Path
SHA256_RE = re.compile(r"[0-9a-f]{64}")
HTTP_FILE_BLOCK_RE = re.compile(r"(?ms)^http_file\(\n.*?^\)\n?")
HTTP_FILE_VERSION_RE = re.compile(r"^rusty_v8_([0-9]+)_([0-9]+)_([0-9]+)_")
class RustyV8ChecksumError(ValueError):
@@ -95,6 +96,18 @@ def rusty_v8_http_files(module_bazel: str, version: str) -> list[RustyV8HttpFile
return entries
def rusty_v8_http_file_versions(module_bazel: str) -> list[str]:
versions = set()
for match in HTTP_FILE_BLOCK_RE.finditer(module_bazel):
name = string_field(match.group(0), "name")
if not name:
continue
version_match = HTTP_FILE_VERSION_RE.match(name)
if version_match:
versions.add(".".join(version_match.groups()))
return sorted(versions)
def module_entry_set_errors(
entries: list[RustyV8HttpFile],
checksums: dict[str, str],
+287
View File
@@ -4,11 +4,270 @@ from __future__ import annotations
import textwrap
import unittest
from os import environ
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import patch
import rusty_v8_bazel
import rusty_v8_module_bazel
class RustyV8BazelTest(unittest.TestCase):
def test_consumer_selectors_track_resolved_crate_version(self) -> None:
build_bazel = (
rusty_v8_bazel.ROOT / "third_party" / "v8" / "BUILD.bazel"
).read_text()
version_suffix = rusty_v8_bazel.resolved_v8_crate_version().replace(".", "_")
for selector in [
"aarch64_apple_darwin_bazel",
"aarch64_pc_windows_gnullvm",
"aarch64_pc_windows_msvc",
"aarch64_unknown_linux_gnu_bazel",
"aarch64_unknown_linux_musl_release_base",
"x86_64_apple_darwin_bazel",
"x86_64_pc_windows_gnullvm",
"x86_64_pc_windows_msvc",
"x86_64_unknown_linux_gnu_bazel",
"x86_64_unknown_linux_musl_release",
]:
self.assertIn(
f":v8_{version_suffix}_{selector}",
build_bazel,
)
for selector in [
"aarch64_apple_darwin",
"aarch64_pc_windows_gnullvm",
"aarch64_pc_windows_msvc",
"aarch64_unknown_linux_gnu",
"aarch64_unknown_linux_musl",
"x86_64_apple_darwin",
"x86_64_pc_windows_gnullvm",
"x86_64_pc_windows_msvc",
"x86_64_unknown_linux_gnu",
"x86_64_unknown_linux_musl",
]:
self.assertIn(
f":src_binding_release_{selector}_{version_suffix}_release",
build_bazel,
)
def test_command_version_tracks_remaining_http_file_assets(self) -> None:
with TemporaryDirectory() as temp_dir:
module_bazel = Path(temp_dir) / "MODULE.bazel"
module_bazel.write_text(
textwrap.dedent(
"""\
http_file(
name = "rusty_v8_146_4_0_x86_64_unknown_linux_gnu_archive",
downloaded_file_path = "librusty_v8_release_x86_64-unknown-linux-gnu.a.gz",
urls = ["https://example.test/archive.gz"],
)
"""
)
)
with patch.object(rusty_v8_bazel, "MODULE_BAZEL", module_bazel):
self.assertEqual("146.4.0", rusty_v8_bazel.command_version(None))
def test_artifact_bazel_configs_always_enable_upstream_libcxx(self) -> None:
self.assertEqual(
["rusty-v8-upstream-libcxx"],
rusty_v8_bazel.artifact_bazel_configs(),
)
self.assertEqual(
["rusty-v8-upstream-libcxx", "v8-release-compat"],
rusty_v8_bazel.artifact_bazel_configs(["v8-release-compat"]),
)
self.assertEqual(
["rusty-v8-upstream-libcxx", "v8-release-compat"],
rusty_v8_bazel.artifact_bazel_configs(
["rusty-v8-upstream-libcxx", "v8-release-compat"]
),
)
def test_bazel_remote_args_include_buildbuddy_header_when_present(self) -> None:
with patch.dict(environ, {"BUILDBUDDY_API_KEY": "token"}, clear=False):
self.assertEqual(
["--remote_header=x-buildbuddy-api-key=token"],
rusty_v8_bazel.bazel_remote_args(),
)
with patch.dict(environ, {}, clear=True):
self.assertEqual([], rusty_v8_bazel.bazel_remote_args())
def test_release_pair_labels_and_staged_names_distinguish_sandbox_artifacts(self) -> None:
self.assertEqual(
"//third_party/v8:rusty_v8_release_pair_x86_64_unknown_linux_musl",
rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl"),
)
self.assertEqual(
"//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_unknown_linux_musl",
rusty_v8_bazel.release_pair_label("x86_64-unknown-linux-musl", sandbox=True),
)
self.assertEqual(
"//third_party/v8:rusty_v8_sandbox_release_pair_x86_64_apple_darwin",
rusty_v8_bazel.release_pair_label("x86_64-apple-darwin", sandbox=True),
)
self.assertEqual(
"librusty_v8_release_x86_64-unknown-linux-musl.a.gz",
rusty_v8_bazel.staged_archive_name(
"x86_64-unknown-linux-musl",
Path("libv8.a"),
rusty_v8_bazel.RELEASE_ARTIFACT_PROFILE,
),
)
self.assertEqual(
"rusty_v8_ptrcomp_sandbox_release_x86_64-pc-windows-msvc.lib.gz",
rusty_v8_bazel.staged_archive_name(
"x86_64-pc-windows-msvc",
Path("v8.a"),
rusty_v8_bazel.SANDBOX_ARTIFACT_PROFILE,
),
)
self.assertEqual(
"src_binding_ptrcomp_sandbox_release_x86_64-unknown-linux-musl.rs",
rusty_v8_bazel.staged_binding_name(
"x86_64-unknown-linux-musl",
rusty_v8_bazel.SANDBOX_ARTIFACT_PROFILE,
),
)
self.assertEqual(
"rusty_v8_ptrcomp_sandbox_release_x86_64-unknown-linux-musl.sha256",
rusty_v8_bazel.staged_checksums_name(
"x86_64-unknown-linux-musl",
rusty_v8_bazel.SANDBOX_ARTIFACT_PROFILE,
),
)
def test_stage_artifacts(self) -> None:
with TemporaryDirectory() as source_dir, TemporaryDirectory() as output_dir:
source_root = Path(source_dir)
archive = source_root / "librusty_v8.a"
binding = source_root / "src_binding.rs"
archive.write_bytes(b"archive")
binding.write_text("binding")
rusty_v8_bazel.stage_artifacts(
"aarch64-apple-darwin",
archive,
binding,
Path(output_dir),
sandbox=True,
)
self.assertEqual(
{
"librusty_v8_ptrcomp_sandbox_release_aarch64-apple-darwin.a.gz",
"src_binding_ptrcomp_sandbox_release_aarch64-apple-darwin.rs",
"rusty_v8_ptrcomp_sandbox_release_aarch64-apple-darwin.sha256",
},
{path.name for path in Path(output_dir).iterdir()},
)
def test_upstream_release_pair_paths(self) -> None:
self.assertEqual(
(
Path(
"/tmp/rusty_v8/target/x86_64-apple-darwin/release/gn_out/obj/"
"librusty_v8.a"
),
Path(
"/tmp/rusty_v8/target/x86_64-apple-darwin/release/gn_out/"
"src_binding.rs"
),
),
rusty_v8_bazel.upstream_release_pair_paths(
Path("/tmp/rusty_v8"),
"x86_64-apple-darwin",
),
)
self.assertEqual(
(
Path(
"/tmp/rusty_v8/target/x86_64-pc-windows-msvc/release/gn_out/"
"obj/rusty_v8.lib"
),
Path(
"/tmp/rusty_v8/target/x86_64-pc-windows-msvc/release/gn_out/"
"src_binding.rs"
),
),
rusty_v8_bazel.upstream_release_pair_paths(
Path("/tmp/rusty_v8"),
"x86_64-pc-windows-msvc",
),
)
def test_stage_upstream_release_pair(self) -> None:
with TemporaryDirectory() as source_dir, TemporaryDirectory() as output_dir:
source_root = Path(source_dir)
gn_out = (
source_root
/ "target"
/ "x86_64-pc-windows-msvc"
/ "release"
/ "gn_out"
)
(gn_out / "obj").mkdir(parents=True)
(gn_out / "obj" / "rusty_v8.lib").write_bytes(b"archive")
(gn_out / "src_binding.rs").write_text("binding")
rusty_v8_bazel.stage_upstream_release_pair(
source_root,
"x86_64-pc-windows-msvc",
Path(output_dir),
sandbox=True,
)
self.assertEqual(
{
"rusty_v8_ptrcomp_sandbox_release_x86_64-pc-windows-msvc.lib.gz",
"src_binding_ptrcomp_sandbox_release_x86_64-pc-windows-msvc.rs",
"rusty_v8_ptrcomp_sandbox_release_x86_64-pc-windows-msvc.sha256",
},
{path.name for path in Path(output_dir).iterdir()},
)
def test_ensure_bazel_output_files_rebuilds_existing_outputs(self) -> None:
with TemporaryDirectory() as output_dir:
output = Path(output_dir) / "libv8.a"
output.write_bytes(b"archive")
with (
patch.object(rusty_v8_bazel, "bazel_build") as bazel_build,
patch.object(
rusty_v8_bazel,
"bazel_output_files",
return_value=[output],
) as bazel_output_files,
):
self.assertEqual(
[output],
rusty_v8_bazel.ensure_bazel_output_files(
"macos_arm64",
["//third_party/v8:pair"],
"opt",
["rusty-v8-upstream-libcxx"],
),
)
bazel_build.assert_called_once_with(
"macos_arm64",
["//third_party/v8:pair"],
"opt",
["rusty-v8-upstream-libcxx"],
download_toplevel=True,
)
bazel_output_files.assert_called_once_with(
"macos_arm64",
["//third_party/v8:pair"],
"opt",
["rusty-v8-upstream-libcxx"],
)
def test_update_module_bazel_replaces_and_inserts_sha256(self) -> None:
module_bazel = textwrap.dedent(
"""\
@@ -121,6 +380,34 @@ class RustyV8BazelTest(unittest.TestCase):
"146.4.0",
)
def test_rusty_v8_http_file_versions(self) -> None:
module_bazel = textwrap.dedent(
"""\
http_file(
name = "rusty_v8_146_4_0_x86_64_unknown_linux_gnu_archive",
downloaded_file_path = "archive.gz",
urls = ["https://example.test/archive.gz"],
)
http_file(
name = "rusty_v8_147_4_0_x86_64_unknown_linux_gnu_archive",
downloaded_file_path = "new-archive.gz",
urls = ["https://example.test/new-archive.gz"],
)
http_file(
name = "unrelated_archive",
downloaded_file_path = "other.gz",
urls = ["https://example.test/other.gz"],
)
"""
)
self.assertEqual(
["146.4.0", "147.4.0"],
rusty_v8_module_bazel.rusty_v8_http_file_versions(module_bazel),
)
if __name__ == "__main__":
unittest.main()
+116 -17
View File
@@ -17,10 +17,10 @@ concurrency:
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
test:
# PRs use a fast Windows cross-compiled test leg for pre-merge signal.
# Post-merge pushes to main also run the native Windows test job below for
# broader Windows signal without putting PR latency back on the critical
# path. Cargo CI owns V8/code-mode test coverage for now.
# PRs use the sharded Windows cross-compiled test jobs below. Post-merge
# pushes to main also run the native Windows test job for broader Windows
# signal without putting PR latency back on the critical path. Cargo CI
# owns V8/code-mode test coverage for now.
timeout-minutes: 30
strategy:
fail-fast: false
@@ -44,12 +44,6 @@ jobs:
# - os: ubuntu-24.04-arm
# target: aarch64-unknown-linux-gnu
# Windows fast path: build the windows-gnullvm binaries with Linux
# RBE, then run the resulting Windows tests on the Windows runner.
# Cargo CI preserves V8/code-mode coverage while Bazel CI keeps broad
# non-code-mode signal.
- os: windows-latest
target: x86_64-pc-windows-gnullvm
runs-on: ${{ matrix.os }}
# Configure a human readable name for each job
@@ -108,13 +102,6 @@ jobs:
--test_verbose_timeout_warnings
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
)
if [[ "${RUNNER_OS}" == "Windows" ]]; then
bazel_wrapper_args+=(
--windows-cross-compile
--remote-download-toplevel
)
fi
./.github/scripts/run-bazel-ci.sh \
"${bazel_wrapper_args[@]}" \
-- \
@@ -141,6 +128,118 @@ jobs:
path: ${{ steps.prepare_bazel.outputs.repository-cache-path }}
key: ${{ steps.prepare_bazel.outputs.repository-cache-key }}
test-windows-shard:
# Split the Windows Bazel test leg across separate Windows
# hosts. Each shard still uses Linux RBE for build actions, but the test
# execution itself happens on its own Windows runner.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard:
- 1
- 2
- 3
- 4
runs-on: windows-latest
name: Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard ${{ matrix.shard }}/4
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- name: Prepare Bazel CI
id: prepare_bazel
uses: ./.github/actions/prepare-bazel-ci
with:
target: x86_64-pc-windows-gnullvm
# Reuse the former monolithic Windows test cache for restores. Do
# not save it from every shard below; duplicate uploads would sit on
# the PR-blocking critical path after the useful test work is done.
cache-scope: bazel-test
install-test-prereqs: "true"
- name: bazel test shard
env:
BAZEL_TEST_SHARD: ${{ matrix.shard }}
BAZEL_TEST_SHARD_COUNT: 4
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
set -euo pipefail
bazel_test_query='tests(//...) except tests(//third_party/v8:all) except //codex-rs/code-mode:code-mode-unit-tests except //codex-rs/v8-poc:v8-poc-unit-tests except attr(tags, "manual", tests(//...))'
mapfile -t bazel_targets < <(
MSYS2_ARG_CONV_EXCL='*' bazel query --output=label "${bazel_test_query}" \
| LC_ALL=C sort
)
selected_targets=()
for bazel_target in "${bazel_targets[@]}"; do
target_bucket="$(
printf '%s\n' "${bazel_target}" \
| cksum \
| awk -v shard_count="${BAZEL_TEST_SHARD_COUNT}" '{ print ($1 % shard_count) + 1 }'
)"
if [[ "${target_bucket}" == "${BAZEL_TEST_SHARD}" ]]; then
selected_targets+=("${bazel_target}")
fi
done
if [[ ${#selected_targets[@]} -eq 0 ]]; then
echo "No Bazel test targets selected for Windows shard ${BAZEL_TEST_SHARD}/${BAZEL_TEST_SHARD_COUNT}." >&2
exit 1
fi
echo "Selected ${#selected_targets[@]} of ${#bazel_targets[@]} Bazel test targets for Windows shard ${BAZEL_TEST_SHARD}/${BAZEL_TEST_SHARD_COUNT}."
bazel_test_args=(
test
--skip_incompatible_explicit_targets
--test_tag_filters=-argument-comment-lint
--test_verbose_timeout_warnings
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
--build_metadata=TAG_windows_test_shard=${BAZEL_TEST_SHARD}
)
./.github/scripts/run-bazel-ci.sh \
--print-failed-action-summary \
--print-failed-test-logs \
--windows-cross-compile \
--remote-download-toplevel \
-- \
"${bazel_test_args[@]}" \
-- \
"${selected_targets[@]}"
- name: Upload Bazel execution logs
if: always() && !cancelled()
continue-on-error: true
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bazel-execution-logs-test-x86_64-pc-windows-gnullvm-shard-${{ matrix.shard }}
path: ${{ runner.temp }}/bazel-execution-logs
if-no-files-found: ignore
test-windows:
# Preserve the existing required-check surface while the real work happens
# in the sharded Windows jobs above.
if: always()
needs: test-windows-shard
runs-on: ubuntu-24.04
name: Bazel test on windows-latest for x86_64-pc-windows-gnullvm
steps:
- name: Confirm Windows Bazel test shards passed
shell: bash
run: |
if [[ "${{ needs.test-windows-shard.result }}" != "success" ]]; then
echo "Windows Bazel test shards finished with result: ${{ needs.test-windows-shard.result }}" >&2
exit 1
fi
test-windows-native-main:
# Native Windows Bazel tests are slower and frequently approach the
# 30-minute PR budget. Run this only for post-merge commits to main and give
+42 -28
View File
@@ -15,14 +15,8 @@ jobs:
permissions:
contents: read
outputs:
issues_json: ${{ steps.normalize-all.outputs.issues_json }}
reason: ${{ steps.normalize-all.outputs.reason }}
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
codex_output: ${{ steps.codex-all.outputs.final-message }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prepare Codex inputs
env:
GH_TOKEN: ${{ github.token }}
@@ -67,6 +61,8 @@ jobs:
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
@@ -100,10 +96,21 @@ jobs:
"additionalProperties": false
}
normalize-duplicates-all:
name: Normalize pass 1 output
needs: gather-duplicates-all
if: ${{ needs.gather-duplicates-all.result == 'success' }}
runs-on: ubuntu-latest
permissions: {}
outputs:
issues_json: ${{ steps.normalize-all.outputs.issues_json }}
reason: ${{ steps.normalize-all.outputs.reason }}
has_matches: ${{ steps.normalize-all.outputs.has_matches }}
steps:
- id: normalize-all
name: Normalize pass 1 output
env:
CODEX_OUTPUT: ${{ steps.codex-all.outputs.final-message }}
CODEX_OUTPUT: ${{ needs.gather-duplicates-all.outputs.codex_output }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
@@ -146,21 +153,15 @@ jobs:
gather-duplicates-open:
name: Identify potential duplicates (open issues fallback)
# Pass 1 may drop sudo on the runner, so run the fallback in a fresh job.
needs: gather-duplicates-all
if: ${{ needs.gather-duplicates-all.result == 'success' && needs.gather-duplicates-all.outputs.has_matches != 'true' }}
# Pass 1 Codex execution drops sudo on its runner, so run the fallback in a fresh job.
needs: normalize-duplicates-all
if: ${{ needs.normalize-duplicates-all.result == 'success' && needs.normalize-duplicates-all.outputs.has_matches != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
issues_json: ${{ steps.normalize-open.outputs.issues_json }}
reason: ${{ steps.normalize-open.outputs.reason }}
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
codex_output: ${{ steps.codex-open.outputs.final-message }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prepare Codex inputs
env:
GH_TOKEN: ${{ github.token }}
@@ -203,6 +204,8 @@ jobs:
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that triages new GitHub issues by identifying potential duplicates.
@@ -236,10 +239,21 @@ jobs:
"additionalProperties": false
}
normalize-duplicates-open:
name: Normalize pass 2 output
needs: gather-duplicates-open
if: ${{ needs.gather-duplicates-open.result == 'success' }}
runs-on: ubuntu-latest
permissions: {}
outputs:
issues_json: ${{ steps.normalize-open.outputs.issues_json }}
reason: ${{ steps.normalize-open.outputs.reason }}
has_matches: ${{ steps.normalize-open.outputs.has_matches }}
steps:
- id: normalize-open
name: Normalize pass 2 output
env:
CODEX_OUTPUT: ${{ steps.codex-open.outputs.final-message }}
CODEX_OUTPUT: ${{ needs.gather-duplicates-open.outputs.codex_output }}
CURRENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
set -eo pipefail
@@ -283,9 +297,9 @@ jobs:
select-final:
name: Select final duplicate set
needs:
- gather-duplicates-all
- gather-duplicates-open
if: ${{ always() && needs.gather-duplicates-all.result == 'success' && (needs.gather-duplicates-open.result == 'success' || needs.gather-duplicates-open.result == 'skipped') }}
- normalize-duplicates-all
- normalize-duplicates-open
if: ${{ always() && needs.normalize-duplicates-all.result == 'success' && (needs.normalize-duplicates-open.result == 'success' || needs.normalize-duplicates-open.result == 'skipped') }}
runs-on: ubuntu-latest
permissions:
contents: read
@@ -295,12 +309,12 @@ jobs:
- id: select-final
name: Select final duplicate set
env:
PASS1_ISSUES: ${{ needs.gather-duplicates-all.outputs.issues_json }}
PASS1_REASON: ${{ needs.gather-duplicates-all.outputs.reason }}
PASS2_ISSUES: ${{ needs.gather-duplicates-open.outputs.issues_json }}
PASS2_REASON: ${{ needs.gather-duplicates-open.outputs.reason }}
PASS1_HAS_MATCHES: ${{ needs.gather-duplicates-all.outputs.has_matches }}
PASS2_HAS_MATCHES: ${{ needs.gather-duplicates-open.outputs.has_matches }}
PASS1_ISSUES: ${{ needs.normalize-duplicates-all.outputs.issues_json }}
PASS1_REASON: ${{ needs.normalize-duplicates-all.outputs.reason }}
PASS2_ISSUES: ${{ needs.normalize-duplicates-open.outputs.issues_json }}
PASS2_REASON: ${{ needs.normalize-duplicates-open.outputs.reason }}
PASS1_HAS_MATCHES: ${{ needs.normalize-duplicates-all.outputs.has_matches }}
PASS2_HAS_MATCHES: ${{ needs.normalize-duplicates-open.outputs.has_matches }}
run: |
set -eo pipefail
+2 -4
View File
@@ -17,15 +17,13 @@ jobs:
outputs:
codex_output: ${{ steps.codex.outputs.final-message }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- id: codex
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02 # v1.7
with:
openai-api-key: ${{ secrets.CODEX_OPENAI_API_KEY }}
allow-users: "*"
safety-strategy: drop-sudo
sandbox: read-only
prompt: |
You are an assistant that reviews GitHub issues for the repository.
+11 -4
View File
@@ -524,10 +524,9 @@ jobs:
tests:
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
# Perhaps we can bring this back down to 30m once we finish the cutover
# from tui_app_server/ to tui/. Incidentally, windows-arm64 was the main
# offender for exceeding the timeout.
timeout-minutes: 45
# Windows ARM64 is the long pole here, and nextest retries plus targeted
# Windows timeout headroom need more than 45m to finish reliably.
timeout-minutes: 60
defaults:
run:
working-directory: codex-rs
@@ -688,6 +687,14 @@ jobs:
RUST_MIN_STACK: "8388608" # 8 MiB
NEXTEST_STATUS_LEVEL: leak
- name: Upload nextest JUnit report
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: nextest-junit-rust-ci-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}
path: codex-rs/target/nextest/default/junit.xml
if-no-files-found: warn
- name: Upload Cargo timings (nextest)
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
@@ -16,6 +16,9 @@ jobs:
prepare:
# Prevent scheduled runs on forks (no secrets, wastes Actions minutes)
if: github.repository == 'openai/codex'
environment:
name: rust-release-prepare
deployment: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+586 -38
View File
@@ -4,12 +4,46 @@
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
# git push origin rust-v0.1.0
# ```
#
# To use external macOS signing, manually dispatch `release_mode=build_unsigned`,
# sign the unsigned macOS artifacts in a secure enclave, upload the signed handoff
# archive as a GitHub Release asset, then manually dispatch
# `release_mode=promote_signed` with `unsigned_run_id` and `signed_macos_asset`.
# The signed handoff archive should contain target or artifact directories such
# as `aarch64-apple-darwin/` with signed binaries.
name: rust-release
on:
push:
tags:
- "rust-v*.*.*"
workflow_dispatch:
inputs:
release_mode:
description: "build_unsigned creates unsigned macOS handoff artifacts; promote_signed finishes a release from signed macOS handoff artifacts."
required: false
type: choice
default: build_unsigned
options:
- build_unsigned
- promote_signed
sign_macos:
description: "Deprecated compatibility input; use release_mode instead."
required: false
type: boolean
default: false
unsigned_run_id:
description: "For promote_signed: workflow run id from the build_unsigned run."
required: false
type: string
signed_macos_asset:
description: "For promote_signed: exact GitHub Release asset name containing signed macOS handoff artifacts."
required: false
type: string
signed_macos_sha256:
description: "For promote_signed: optional SHA-256 of signed_macos_asset."
required: false
type: string
concurrency:
group: ${{ github.workflow }}
@@ -25,10 +59,60 @@ jobs:
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
- name: Validate tag matches Cargo.toml version
shell: bash
env:
RELEASE_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode || 'signed' }}
REQUESTED_SIGN_MACOS: ${{ inputs.sign_macos }}
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
UNSIGNED_RUN_ID: ${{ inputs.unsigned_run_id }}
run: |
set -euo pipefail
echo "::group::Tag validation"
case "${RELEASE_MODE}" in
signed)
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
echo "❌ Manual rust-release runs must use release_mode=build_unsigned or release_mode=promote_signed"
exit 1
fi
;;
build_unsigned)
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ]]; then
echo "❌ release_mode=build_unsigned is only valid for manual runs"
exit 1
fi
;;
promote_signed)
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ]]; then
echo "❌ release_mode=promote_signed is only valid for manual runs"
exit 1
fi
if [[ ! "${UNSIGNED_RUN_ID}" =~ ^[0-9]+$ ]]; then
echo "❌ release_mode=promote_signed requires unsigned_run_id to be a workflow run id"
exit 1
fi
if [[ -z "${SIGNED_MACOS_ASSET}" ]]; then
echo "❌ release_mode=promote_signed requires signed_macos_asset"
exit 1
fi
if [[ "${SIGNED_MACOS_ASSET}" == */* || "${SIGNED_MACOS_ASSET}" == *"*"* || "${SIGNED_MACOS_ASSET}" == *"?"* || "${SIGNED_MACOS_ASSET}" == *"["* ]]; then
echo "❌ signed_macos_asset must be an exact release asset name, not a path or glob"
exit 1
fi
if [[ "${UNSIGNED_RUN_ID}" == "${GITHUB_RUN_ID}" ]]; then
echo "❌ unsigned_run_id must refer to the earlier build_unsigned run, not this run"
exit 1
fi
;;
*)
echo "❌ Unknown release_mode '${RELEASE_MODE}'"
exit 1
;;
esac
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${REQUESTED_SIGN_MACOS}" == "true" ]]; then
echo "::warning title=Deprecated sign_macos input ignored::Use release_mode=build_unsigned or release_mode=promote_signed instead."
fi
# 1. Must be a tag and match the regex
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|| { echo "❌ Not a tag push"; exit 1; }
@@ -48,6 +132,7 @@ jobs:
echo "::endgroup::"
build:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
needs: tag-check
name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
@@ -64,11 +149,36 @@ jobs:
# 2026-03-04: temporarily change releases to use thin LTO because
# Ubuntu ARM is timing out at 60 minutes.
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'thin' }}
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-responses-api-proxy"
build_dmg: "true"
- runner: macos-15-xlarge
target: aarch64-apple-darwin
bundle: app-server
artifact_name: aarch64-apple-darwin-app-server
binaries: "codex-app-server"
build_dmg: "false"
- runner: macos-15-xlarge
target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-responses-api-proxy"
build_dmg: "true"
- runner: macos-15-xlarge
target: x86_64-apple-darwin
bundle: app-server
artifact_name: x86_64-apple-darwin-app-server
binaries: "codex-app-server"
build_dmg: "false"
# Release artifacts intentionally ship MUSL-linked Linux binaries.
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
@@ -95,7 +205,7 @@ jobs:
binaries: "codex-app-server"
build_dmg: "false"
steps: &rust_release_build_steps
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -271,6 +381,39 @@ jobs:
path: codex-rs/target/**/cargo-timings/cargo-timing.html
if-no-files-found: warn
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS != 'true' }}
name: Stage unsigned macOS artifacts
shell: bash
run: |
set -euo pipefail
target="${{ matrix.target }}"
release_dir="target/${target}/release"
dest="unsigned-dist/${target}"
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
binary_path="${release_dir}/${binary}"
unsigned_name="${binary}-${target}-unsigned"
unsigned_path="${dest}/${unsigned_name}"
if [[ ! -f "${binary_path}" ]]; then
echo "Binary ${binary_path} not found"
exit 1
fi
cp "${binary_path}" "${unsigned_path}"
tar -C "$dest" -czf "${unsigned_path}.tar.gz" "${unsigned_name}"
zstd -T0 -19 --rm "${unsigned_path}"
done
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS != 'true' }}
name: Upload unsigned macOS artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ matrix.artifact_name }}-unsigned
path: codex-rs/unsigned-dist/${{ matrix.target }}/*
if-no-files-found: error
- if: ${{ contains(matrix.target, 'linux') }}
name: Cosign Linux artifacts
uses: ./.github/actions/linux-code-sign
@@ -279,7 +422,7 @@ jobs:
artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release
binaries: ${{ matrix.binaries }}
- if: ${{ runner.os == 'macOS' }}
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS == 'true' }}
name: MacOS code signing (binaries)
uses: ./.github/actions/macos-code-sign
with:
@@ -293,7 +436,7 @@ jobs:
apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' }}
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
name: Build macOS dmg
shell: bash
run: |
@@ -333,7 +476,7 @@ jobs:
exit 1
fi
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' }}
- if: ${{ runner.os == 'macOS' && matrix.build_dmg == 'true' && env.SIGN_MACOS == 'true' }}
name: MacOS code signing (dmg)
uses: ./.github/actions/macos-code-sign
with:
@@ -347,6 +490,7 @@ jobs:
apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
- name: Stage artifacts
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
shell: bash
run: |
dest="dist/${{ matrix.target }}"
@@ -376,7 +520,7 @@ jobs:
fi
- name: Build Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
shell: bash
run: |
set -euo pipefail
@@ -389,10 +533,10 @@ jobs:
platform_tag="macosx_10_9_x86_64"
;;
aarch64-unknown-linux-musl)
platform_tag="musllinux_1_1_aarch64"
platform_tag="manylinux_2_17_aarch64"
;;
x86_64-unknown-linux-musl)
platform_tag="musllinux_1_1_x86_64"
platform_tag="manylinux_2_17_x86_64"
;;
*)
echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
@@ -427,7 +571,7 @@ jobs:
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
- name: Upload Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: python-runtime-wheel-${{ matrix.target }}
@@ -435,6 +579,7 @@ jobs:
if-no-files-found: error
- name: Compress artifacts
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
shell: bash
run: |
# Path that contains the uncompressed binaries for the current
@@ -471,6 +616,7 @@ jobs:
done
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
with:
name: ${{ matrix.artifact_name }}
# Upload the per-binary .zst files, .tar.gz equivalents, and any
@@ -478,56 +624,233 @@ jobs:
path: |
codex-rs/dist/${{ matrix.target }}/*
build-macos:
stage-signed-macos:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode == 'promote_signed' }}
needs: tag-check
name: Build - ${{ matrix.runner }} - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: ${{ matrix.runs_on || matrix.runner }}
# Release builds can take a long time, so leave some headroom to avoid
# having to restart the full workflow due to a timeout.
timeout-minutes: 90
environment: macos-signing
name: Stage signed macOS handoff - ${{ matrix.target }} - ${{ matrix.bundle }}
runs-on: macos-15-xlarge
timeout-minutes: 30
permissions:
contents: read
defaults:
run:
working-directory: codex-rs
env:
# Preserve the LTO setting from the original combined release build matrix.
# Linux ARM is why this currently resolves to thin for every target.
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'thin' }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-xlarge
target: aarch64-apple-darwin
- target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-responses-api-proxy"
build_dmg: "true"
- runner: macos-15-xlarge
target: aarch64-apple-darwin
build_dmg: "false"
- target: aarch64-apple-darwin
bundle: app-server
artifact_name: aarch64-apple-darwin-app-server
binaries: "codex-app-server"
build_dmg: "false"
- runner: macos-15-xlarge
target: x86_64-apple-darwin
- target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-responses-api-proxy"
build_dmg: "true"
- runner: macos-15-xlarge
target: x86_64-apple-darwin
build_dmg: "false"
- target: x86_64-apple-darwin
bundle: app-server
artifact_name: x86_64-apple-darwin-app-server
binaries: "codex-app-server"
build_dmg: "false"
steps: *rust_release_build_steps
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download signed macOS handoff
shell: bash
env:
GH_TOKEN: ${{ github.token }}
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
SIGNED_MACOS_SHA256: ${{ inputs.signed_macos_sha256 }}
run: |
set -euo pipefail
download_dir="${RUNNER_TEMP}/signed-macos-download"
handoff_dir="${RUNNER_TEMP}/signed-macos-handoff"
rm -rf "$download_dir" "$handoff_dir"
mkdir -p "$download_dir" "$handoff_dir"
gh release download "$GITHUB_REF_NAME" \
--repo "$GITHUB_REPOSITORY" \
--pattern "$SIGNED_MACOS_ASSET" \
--dir "$download_dir"
asset_count="$(find "$download_dir" -maxdepth 1 -type f | wc -l | tr -d '[:space:]')"
if [[ "$asset_count" != "1" ]]; then
echo "Expected exactly one signed macOS handoff asset named ${SIGNED_MACOS_ASSET}; found ${asset_count}"
find "$download_dir" -maxdepth 1 -type f -print
exit 1
fi
asset_path="$(find "$download_dir" -maxdepth 1 -type f -print -quit)"
if [[ -n "${SIGNED_MACOS_SHA256}" ]]; then
expected_sha="$(printf '%s' "$SIGNED_MACOS_SHA256" | tr '[:upper:]' '[:lower:]')"
actual_sha="$(shasum -a 256 "$asset_path" | awk '{print $1}')"
if [[ "$actual_sha" != "$expected_sha" ]]; then
echo "signed_macos_sha256 mismatch for ${SIGNED_MACOS_ASSET}"
echo "expected: ${expected_sha}"
echo "actual: ${actual_sha}"
exit 1
fi
fi
asset_name="$(basename "$asset_path")"
case "$asset_name" in
*.tar.zst)
zstd -dc "$asset_path" | tar -C "$handoff_dir" -xf -
;;
*.tar.gz|*.tgz)
tar -C "$handoff_dir" -xzf "$asset_path"
;;
*.zip)
ditto -x -k "$asset_path" "$handoff_dir"
;;
*)
echo "Unsupported signed macOS handoff archive format: ${asset_name}"
exit 1
;;
esac
echo "SIGNED_MACOS_HANDOFF_DIR=$handoff_dir" >> "$GITHUB_ENV"
- name: Stage signed macOS artifacts
shell: bash
run: |
set -euo pipefail
target="${{ matrix.target }}"
artifact_name="${{ matrix.artifact_name }}"
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/${artifact_name}"
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}" ]]; then
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}"
fi
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/${target}" ]]; then
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/${target}"
fi
if [[ ! -d "$source_dir" && -d "${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}" ]]; then
source_dir="${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}"
fi
if [[ ! -d "$source_dir" ]]; then
echo "Signed macOS handoff is missing ${artifact_name}/"
echo "Expected either:"
echo " ${SIGNED_MACOS_HANDOFF_DIR}/${artifact_name}"
echo " ${SIGNED_MACOS_HANDOFF_DIR}/dist/${artifact_name}"
echo " ${SIGNED_MACOS_HANDOFF_DIR}/${target}"
echo " ${SIGNED_MACOS_HANDOFF_DIR}/dist/${target}"
find "$SIGNED_MACOS_HANDOFF_DIR" -maxdepth 3 -type f -print
exit 1
fi
dest="dist/${target}"
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
source_path="${source_dir}/${binary}"
if [[ ! -f "$source_path" ]]; then
source_path="${source_dir}/${binary}-${target}"
fi
if [[ ! -f "$source_path" ]]; then
echo "Signed macOS handoff is missing ${binary} for ${artifact_name}"
exit 1
fi
release_path="${dest}/${binary}-${target}"
ditto "$source_path" "$release_path"
chmod 0755 "$release_path"
codesign --verify --strict --verbose=2 "$release_path"
done
# DMG staging is disabled for signed promotion because we no longer
# distribute DMGs from this release path. Keep the branch here so the
# handoff can opt back in by flipping matrix.build_dmg if needed.
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
dmg_name="codex-${target}.dmg"
dmg_source="${source_dir}/${dmg_name}"
if [[ ! -f "$dmg_source" ]]; then
echo "Signed macOS handoff is missing ${dmg_name} for ${artifact_name}"
exit 1
fi
codesign --verify --strict --verbose=2 "$dmg_source"
xcrun stapler validate "$dmg_source"
cp "$dmg_source" "$dest/$dmg_name"
fi
- name: Build Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
shell: bash
run: |
set -euo pipefail
case "${{ matrix.target }}" in
aarch64-apple-darwin)
platform_tag="macosx_11_0_arm64"
;;
x86_64-apple-darwin)
platform_tag="macosx_10_9_x86_64"
;;
*)
echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
exit 1
;;
esac
python3 -m venv "${RUNNER_TEMP}/python-runtime-build-venv"
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m pip install build
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
python3 \
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
stage-runtime \
"$stage_dir" \
"${GITHUB_WORKSPACE}/codex-rs/dist/${{ matrix.target }}/codex-${{ matrix.target }}" \
--codex-version "${GITHUB_REF_NAME}" \
--platform-tag "$platform_tag"
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
- name: Upload Python runtime wheel
if: ${{ matrix.bundle == 'primary' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: python-runtime-wheel-${{ matrix.target }}
path: python-runtime-dist/${{ matrix.target }}/*.whl
if-no-files-found: error
- name: Compress artifacts
shell: bash
run: |
set -euo pipefail
dest="dist/${{ matrix.target }}"
for f in "$dest"/*; do
base="$(basename "$f")"
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
zstd -T0 -19 --rm "$dest/$base"
done
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ matrix.artifact_name }}
path: |
codex-rs/dist/${{ matrix.target }}/*
build-windows:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
needs: tag-check
uses: ./.github/workflows/rust-release-windows.yml
with:
@@ -535,6 +858,7 @@ jobs:
secrets: inherit
argument-comment-lint-release-assets:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
name: argument-comment-lint release assets
needs: tag-check
uses: ./.github/workflows/rust-release-argument-comment-lint.yml
@@ -542,25 +866,57 @@ jobs:
publish: true
zsh-release-assets:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
name: zsh release assets
needs: tag-check
uses: ./.github/workflows/rust-release-zsh.yml
release:
needs:
- tag-check
- build
- build-macos
- stage-signed-macos
- build-windows
- argument-comment-lint-release-assets
- zsh-release-assets
if: >-
${{
always() &&
needs.tag-check.result == 'success' &&
(
(
github.event_name == 'workflow_dispatch' &&
inputs.release_mode == 'promote_signed' &&
needs.stage-signed-macos.result == 'success' &&
needs.build.result == 'skipped' &&
needs.build-windows.result == 'skipped' &&
needs.argument-comment-lint-release-assets.result == 'skipped' &&
needs.zsh-release-assets.result == 'skipped'
) ||
(
(github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed') &&
needs.build.result == 'success' &&
needs.stage-signed-macos.result == 'skipped' &&
needs.build-windows.result == 'success' &&
needs.argument-comment-lint-release-assets.result == 'success' &&
needs.zsh-release-assets.result == 'success'
)
)
}}
name: release
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
env:
RELEASE_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.release_mode || 'signed' }}
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode == 'promote_signed' }}
SIGNED_MACOS_ASSET: ${{ inputs.signed_macos_asset }}
UNSIGNED_RUN_ID: ${{ inputs.unsigned_run_id }}
outputs:
version: ${{ steps.release_name.outputs.name }}
tag: ${{ github.ref_name }}
sign_macos: ${{ steps.release_mode.outputs.sign_macos }}
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
should_publish_python_runtime: ${{ steps.python_runtime_publish_settings.outputs.should_publish }}
@@ -571,6 +927,12 @@ jobs:
with:
persist-credentials: false
- name: Define release mode
id: release_mode
run: |
echo "release_mode=${RELEASE_MODE}" >> "$GITHUB_OUTPUT"
echo "sign_macos=${SIGN_MACOS}" >> "$GITHUB_OUTPUT"
- name: Generate release notes from tag commit message
id: release_notes
shell: bash
@@ -595,9 +957,121 @@ jobs:
with:
path: dist
- name: Validate unsigned build run
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
run_summary="$(gh run view "$UNSIGNED_RUN_ID" \
--repo "$GITHUB_REPOSITORY" \
--json conclusion,event,headBranch,headSha,status,workflowName,url \
--jq '[.workflowName, .event, .headBranch, .headSha, .status, .conclusion, .url] | @tsv')"
IFS=$'\t' read -r workflow_name event head_branch head_sha status conclusion run_url <<< "$run_summary"
expected_head_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
if [[ "$workflow_name" != "$GITHUB_WORKFLOW" ]]; then
echo "unsigned_run_id ${UNSIGNED_RUN_ID} is for workflow '${workflow_name}', expected '${GITHUB_WORKFLOW}'"
echo "Run URL: ${run_url}"
exit 1
fi
if [[ "$event" != "workflow_dispatch" ]]; then
echo "unsigned_run_id ${UNSIGNED_RUN_ID} was triggered by '${event}', expected 'workflow_dispatch'"
echo "Run URL: ${run_url}"
exit 1
fi
if [[ "$head_branch" != "$GITHUB_REF_NAME" ]]; then
echo "unsigned_run_id ${UNSIGNED_RUN_ID} used ref '${head_branch}', expected '${GITHUB_REF_NAME}'"
echo "Run URL: ${run_url}"
exit 1
fi
if [[ "$head_sha" != "$expected_head_sha" ]]; then
echo "unsigned_run_id ${UNSIGNED_RUN_ID} used head SHA '${head_sha}', expected '${expected_head_sha}'"
echo "Run URL: ${run_url}"
exit 1
fi
if [[ "$status" != "completed" || "$conclusion" != "success" ]]; then
echo "unsigned_run_id ${UNSIGNED_RUN_ID} is ${status}/${conclusion}, expected completed/success"
echo "Run URL: ${run_url}"
exit 1
fi
- name: Download artifacts from unsigned build run
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh run download "$UNSIGNED_RUN_ID" \
--repo "$GITHUB_REPOSITORY" \
--dir dist
- name: Remove unsigned macOS staging artifacts
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
run: |
set -euo pipefail
find dist -mindepth 1 -maxdepth 1 -type d \
-name '*-apple-darwin*-unsigned' \
-exec rm -rf {} +
- name: Re-upload promoted Linux x64 artifacts
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: x86_64-unknown-linux-musl
path: dist/x86_64-unknown-linux-musl/*
if-no-files-found: error
- name: Re-upload promoted Linux arm64 artifacts
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aarch64-unknown-linux-musl
path: dist/aarch64-unknown-linux-musl/*
if-no-files-found: error
- name: Re-upload promoted Windows x64 artifacts
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: x86_64-pc-windows-msvc
path: dist/x86_64-pc-windows-msvc/*
if-no-files-found: error
- name: Re-upload promoted Windows arm64 artifacts
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: aarch64-pc-windows-msvc
path: dist/aarch64-pc-windows-msvc/*
if-no-files-found: error
- name: List
run: ls -R dist/
- name: Prune artifacts excluded from unsigned macOS release
if: ${{ env.SIGN_MACOS == 'false' }}
run: |
find dist -mindepth 1 -maxdepth 1 -type d \
! -name '*-apple-darwin*-unsigned' \
! -name 'aarch64-unknown-linux-musl' \
! -name 'aarch64-unknown-linux-musl-app-server' \
! -name 'x86_64-unknown-linux-musl' \
! -name 'x86_64-unknown-linux-musl-app-server' \
! -name 'aarch64-pc-windows-msvc' \
! -name 'x86_64-pc-windows-msvc' \
-exec rm -rf {} +
if ! find dist -type f -name '*-apple-darwin*-unsigned*' | grep -q .; then
echo "No unsigned macOS artifacts found in downloaded workflow artifacts."
exit 1
fi
- name: Delete entries from dist/ that should not go in the release
run: |
rm -rf dist/windows-binaries*
@@ -629,6 +1103,12 @@ jobs:
set -euo pipefail
version="${VERSION}"
if [[ "${SIGN_MACOS}" != "true" ]]; then
echo "should_publish=false" >> "$GITHUB_OUTPUT"
echo "npm_tag=" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "npm_tag=" >> "$GITHUB_OUTPUT"
@@ -648,6 +1128,11 @@ jobs:
set -euo pipefail
version="${VERSION}"
if [[ "${SIGN_MACOS}" != "true" ]]; then
echo "should_publish=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "should_publish=true" >> "$GITHUB_OUTPUT"
elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
@@ -657,32 +1142,39 @@ jobs:
fi
- name: Setup pnpm
if: ${{ env.SIGN_MACOS == 'true' }}
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
with:
run_install: false
- name: Setup Node.js for npm packaging
if: ${{ env.SIGN_MACOS == 'true' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Install dependencies
if: ${{ env.SIGN_MACOS == 'true' }}
run: pnpm install --frozen-lockfile
# stage_npm_packages.py requires DotSlash when staging releases.
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
- name: Stage npm packages
if: ${{ env.SIGN_MACOS == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ steps.release_name.outputs.name }}
run: |
workflow_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
./scripts/stage_npm_packages.py \
--release-version "$RELEASE_VERSION" \
--workflow-url "$workflow_url" \
--package codex \
--package codex-responses-api-proxy \
--package codex-sdk
- name: Stage installer scripts
if: ${{ env.SIGN_MACOS == 'true' }}
run: |
cp scripts/install/install.sh dist/install.sh
cp scripts/install/install.ps1 dist/install.ps1
@@ -694,25 +1186,56 @@ jobs:
tag_name: ${{ github.ref_name }}
body_path: ${{ steps.release_notes.outputs.path }}
files: dist/**
overwrite_files: true
make_latest: ${{ env.SIGN_MACOS == 'true' && !contains(steps.release_name.outputs.name, '-') }}
# Mark as prerelease only when the version has a suffix after x.y.z
# (e.g. -alpha, -beta). Otherwise publish a normal release.
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
- name: Clean up signed promotion handoff assets
if: ${{ env.RELEASE_MODE == 'promote_signed' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
release_id="$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}" --jq '.id')"
gh api --paginate "repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets" \
--jq '.[] | [.id, .name] | @tsv' |
while IFS=$'\t' read -r asset_id asset_name; do
if [[ -z "$asset_id" || -z "$asset_name" ]]; then
continue
fi
delete_asset=false
if [[ "$asset_name" == *unsigned* || "$asset_name" == "$SIGNED_MACOS_ASSET" ]]; then
delete_asset=true
fi
if [[ "$delete_asset" == "true" ]]; then
echo "Deleting release asset ${asset_name}"
gh api -X DELETE "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}"
fi
done
- if: ${{ env.SIGN_MACOS == 'true' }}
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
config: .github/dotslash-config.json
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
- if: ${{ env.SIGN_MACOS == 'true' }}
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ github.ref_name }}
config: .github/dotslash-zsh-config.json
- uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
- if: ${{ env.SIGN_MACOS == 'true' }}
uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -722,7 +1245,7 @@ jobs:
- name: Trigger developers.openai.com deploy
# Only trigger the deploy if the release is not a pre-release.
# The deploy is used to update the developers.openai.com website with the new config schema json file.
if: ${{ !contains(steps.release_name.outputs.name, '-') }}
if: ${{ env.SIGN_MACOS == 'true' && !contains(steps.release_name.outputs.name, '-') }}
continue-on-error: true
env:
DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL: ${{ secrets.DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL }}
@@ -737,7 +1260,15 @@ jobs:
# npm docs: https://docs.npmjs.com/trusted-publishers
publish-npm:
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
if: ${{ needs.release.outputs.should_publish_npm == 'true' }}
# promote_signed intentionally skips build jobs that are ancestors of release;
# include the !cancelled() status function so Actions does not apply its implicit
# success() check to the whole dependency chain before evaluating release outputs.
if: >-
${{
!cancelled() &&
needs.release.result == 'success' &&
needs.release.outputs.should_publish_npm == 'true'
}}
name: publish-npm
needs: release
runs-on: ubuntu-latest
@@ -895,7 +1426,12 @@ jobs:
# need release follow-up, but should not invalidate the Rust release itself.
publish-python-runtime:
# Publish to PyPI for stable releases and alpha pre-releases with numeric suffixes.
if: ${{ needs.release.outputs.should_publish_python_runtime == 'true' }}
if: >-
${{
!cancelled() &&
needs.release.result == 'success' &&
needs.release.outputs.should_publish_python_runtime == 'true'
}}
name: publish-python-runtime
needs: release
runs-on: ubuntu-latest
@@ -936,7 +1472,13 @@ jobs:
needs: release
# Only publish stable/mainline releases to WinGet; pre-releases include a
# '-' in the semver string (e.g., 1.2.3-alpha.1).
if: ${{ !contains(needs.release.outputs.version, '-') }}
if: >-
${{
!cancelled() &&
needs.release.result == 'success' &&
needs.release.outputs.sign_macos == 'true' &&
!contains(needs.release.outputs.version, '-')
}}
# This job only invokes a GitHub Action to open/update the winget-pkgs PR;
# it does not execute Windows-only tooling, so Linux is sufficient.
runs-on: ubuntu-latest
@@ -956,6 +1498,12 @@ jobs:
update-branch:
name: Update latest-alpha-cli branch
if: >-
${{
!cancelled() &&
needs.release.result == 'success' &&
needs.release.outputs.sign_macos == 'true'
}}
permissions:
contents: write
needs: release
+158 -24
View File
@@ -46,14 +46,14 @@ jobs:
expected_release_tag="rusty-v8-v${V8_VERSION}"
release_tag="${GITHUB_REF_NAME}"
if [[ "${release_tag}" != "${expected_release_tag}" ]]; then
echo "Tag ${release_tag} does not match resolved v8 crate version ${V8_VERSION}." >&2
echo "Tag ${release_tag} does not match expected release tag ${expected_release_tag}." >&2
exit 1
fi
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.target }}
name: Build ${{ matrix.variant }} ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
permissions:
@@ -64,11 +64,77 @@ jobs:
matrix:
include:
- runner: ubuntu-24.04
platform: linux_amd64_musl
target: x86_64-unknown-linux-musl
bazel_config: ci-v8
platform: linux_amd64
sandbox: false
target: x86_64-unknown-linux-gnu
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64
sandbox: true
target: x86_64-unknown-linux-gnu
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: false
target: aarch64-unknown-linux-gnu
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: true
target: aarch64-unknown-linux-gnu
variant: ptrcomp-sandbox
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_amd64
sandbox: false
target: x86_64-apple-darwin
variant: release
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_amd64
sandbox: true
target: x86_64-apple-darwin
variant: ptrcomp-sandbox
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_arm64
sandbox: false
target: aarch64-apple-darwin
variant: release
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_arm64
sandbox: true
target: aarch64-apple-darwin
variant: ptrcomp-sandbox
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: false
target: x86_64-unknown-linux-musl
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: false
target: aarch64-unknown-linux-musl
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: true
target: x86_64-unknown-linux-musl
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: true
target: aarch64-unknown-linux-musl
variant: ptrcomp-sandbox
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -85,61 +151,115 @@ jobs:
with:
python-version: "3.12"
- name: Set up Rust toolchain for Cargo smoke
uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
with:
toolchain: "1.93.0"
- name: Build Bazel V8 release pair
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
target_suffix="${TARGET//-/_}"
pair_target="//third_party/v8:rusty_v8_release_pair_${target_suffix}"
extra_targets=()
if [[ "${TARGET}" == *-unknown-linux-musl ]]; then
extra_targets=(
"@llvm//runtimes/libcxx:libcxx.static"
"@llvm//runtimes/libcxx:libcxxabi.static"
)
pair_kind="release_pair"
if [[ "${SANDBOX}" == "true" ]]; then
pair_kind="sandbox_release_pair"
fi
pair_target="//third_party/v8:rusty_v8_${pair_kind}_${target_suffix}"
bazel_args=(
build
-c
opt
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
--config=rusty-v8-upstream-libcxx
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
)
if [[ "${SANDBOX}" != "true" ]]; then
bazel_args+=(--config=v8-release-compat)
fi
bazel \
--noexperimental_remote_repo_contents_cache \
"${bazel_args[@]}" \
--config=ci-v8 \
"--config=${{ matrix.bazel_config }}" \
"--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
- name: Stage release pair
env:
BAZEL_CONFIG: ${{ matrix.bazel_config }}
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
--platform "${PLATFORM}" \
--target "${TARGET}" \
--compilation-mode opt \
--bazel-config v8-release-compat \
stage_args=(
--platform "${PLATFORM}"
--target "${TARGET}"
--compilation-mode opt
--output-dir "dist/${TARGET}"
--bazel-config "${BAZEL_CONFIG}"
)
if [[ "${SANDBOX}" == "true" ]]; then
stage_args+=(--sandbox)
else
stage_args+=(--bazel-config v8-release-compat)
fi
- name: Upload staged musl artifacts
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
- name: Smoke test staged artifact with Cargo
env:
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
host_arch="$(uname -m)"
case "${TARGET}:${host_arch}" in
x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
;;
*)
echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
exit 0
;;
esac
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
if [[ -z "${archive}" || -z "${binding}" ]]; then
echo "Missing staged archive or binding for ${TARGET}." >&2
exit 1
fi
cargo_args=(test -p codex-v8-poc)
if [[ "${SANDBOX}" == "true" ]]; then
cargo_args+=(--features sandbox)
fi
(
cd codex-rs
CARGO_TARGET_DIR="${RUNNER_TEMP}/rusty-v8-cargo-smoke-${TARGET}-${SANDBOX}" \
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
cargo "${cargo_args[@]}"
)
- name: Upload staged artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.variant }}-${{ matrix.target }}
path: dist/${{ matrix.target }}/*
publish-release:
@@ -152,7 +272,8 @@ jobs:
actions: read
steps:
- name: Ensure release tag is new
- name: Check whether release already exists
id: release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
@@ -161,8 +282,9 @@ jobs:
set -euo pipefail
if gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" > /dev/null 2>&1; then
echo "Release tag ${RELEASE_TAG} already exists; musl artifact tags are immutable." >&2
exit 1
echo "exists=true" >> "${GITHUB_OUTPUT}"
else
echo "exists=false" >> "${GITHUB_OUTPUT}"
fi
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -170,6 +292,7 @@ jobs:
path: dist
- name: Create GitHub Release
if: ${{ steps.release.outputs.exists != 'true' }}
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
tag_name: ${{ needs.metadata.outputs.release_tag }}
@@ -177,3 +300,14 @@ jobs:
files: dist/**
# Keep V8 artifact releases out of Codex's normal "latest release" channel.
prerelease: true
- name: Amend existing GitHub Release
if: ${{ steps.release.outputs.exists == 'true' }}
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
tag_name: ${{ needs.metadata.outputs.release_tag }}
name: ${{ needs.metadata.outputs.release_tag }}
files: dist/**
overwrite_files: true
# Keep V8 artifact releases out of Codex's normal "latest release" channel.
prerelease: true
+285 -18
View File
@@ -3,28 +3,36 @@ name: v8-canary
on:
pull_request:
paths:
- ".bazelrc"
- ".github/actions/setup-bazel-ci/**"
- ".github/scripts/rusty_v8_bazel.py"
- ".github/scripts/rusty_v8_module_bazel.py"
- ".github/workflows/rusty-v8-release.yml"
- ".github/workflows/v8-canary.yml"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "codex-rs/Cargo.toml"
- "patches/BUILD.bazel"
- "patches/llvm_*.patch"
- "patches/rules_cc_*.patch"
- "patches/v8_*.patch"
- "third_party/v8/**"
push:
branches:
- main
paths:
- ".bazelrc"
- ".github/actions/setup-bazel-ci/**"
- ".github/scripts/rusty_v8_bazel.py"
- ".github/scripts/rusty_v8_module_bazel.py"
- ".github/workflows/rusty-v8-release.yml"
- ".github/workflows/v8-canary.yml"
- "MODULE.bazel"
- "MODULE.bazel.lock"
- "codex-rs/Cargo.toml"
- "patches/BUILD.bazel"
- "patches/llvm_*.patch"
- "patches/rules_cc_*.patch"
- "patches/v8_*.patch"
- "third_party/v8/**"
workflow_dispatch:
@@ -59,7 +67,7 @@ jobs:
echo "version=${version}" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.target }}
name: Build ${{ matrix.variant }} ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
permissions:
@@ -70,12 +78,77 @@ jobs:
matrix:
include:
- runner: ubuntu-24.04
platform: linux_amd64_musl
target: x86_64-unknown-linux-musl
bazel_config: ci-v8
platform: linux_amd64
sandbox: false
target: x86_64-unknown-linux-gnu
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64
sandbox: true
target: x86_64-unknown-linux-gnu
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: false
target: aarch64-unknown-linux-gnu
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64
sandbox: true
target: aarch64-unknown-linux-gnu
variant: ptrcomp-sandbox
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_amd64
sandbox: false
target: x86_64-apple-darwin
variant: release
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_amd64
sandbox: true
target: x86_64-apple-darwin
variant: ptrcomp-sandbox
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_arm64
sandbox: false
target: aarch64-apple-darwin
variant: release
- runner: macos-15-xlarge
bazel_config: ci-macos
platform: macos_arm64
sandbox: true
target: aarch64-apple-darwin
variant: ptrcomp-sandbox
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: false
target: x86_64-unknown-linux-musl
variant: release
- runner: ubuntu-24.04
bazel_config: ci-v8
platform: linux_amd64_musl
sandbox: true
target: x86_64-unknown-linux-musl
variant: ptrcomp-sandbox
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: false
target: aarch64-unknown-linux-musl
variant: release
- runner: ubuntu-24.04-arm
bazel_config: ci-v8
platform: linux_arm64_musl
sandbox: true
target: aarch64-unknown-linux-musl
variant: ptrcomp-sandbox
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
@@ -92,53 +165,247 @@ jobs:
with:
python-version: "3.12"
- name: Set up Rust toolchain for Cargo smoke
uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
with:
toolchain: "1.93.0"
- name: Build Bazel V8 release pair
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
target_suffix="${TARGET//-/_}"
pair_target="//third_party/v8:rusty_v8_release_pair_${target_suffix}"
extra_targets=(
"@llvm//runtimes/libcxx:libcxx.static"
"@llvm//runtimes/libcxx:libcxxabi.static"
)
pair_kind="release_pair"
if [[ "${SANDBOX}" == "true" ]]; then
pair_kind="sandbox_release_pair"
fi
pair_target="//third_party/v8:rusty_v8_${pair_kind}_${target_suffix}"
bazel_args=(
build
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
--config=rusty-v8-upstream-libcxx
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
)
if [[ "${SANDBOX}" != "true" ]]; then
bazel_args+=(--config=v8-release-compat)
fi
bazel \
--noexperimental_remote_repo_contents_cache \
"${bazel_args[@]}" \
--config=ci-v8 \
"--config=${{ matrix.bazel_config }}" \
"--remote_header=x-buildbuddy-api-key=${BUILDBUDDY_API_KEY}"
- name: Stage release pair
env:
BAZEL_CONFIG: ${{ matrix.bazel_config }}
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
PLATFORM: ${{ matrix.platform }}
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
--platform "${PLATFORM}" \
--target "${TARGET}" \
--bazel-config v8-release-compat \
stage_args=(
--platform "${PLATFORM}"
--target "${TARGET}"
--output-dir "dist/${TARGET}"
--bazel-config "${BAZEL_CONFIG}"
)
if [[ "${SANDBOX}" == "true" ]]; then
stage_args+=(--sandbox)
else
stage_args+=(--bazel-config v8-release-compat)
fi
- name: Upload staged musl artifacts
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
- name: Smoke test staged artifact with Cargo
env:
SANDBOX: ${{ matrix.sandbox }}
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
host_arch="$(uname -m)"
case "${TARGET}:${host_arch}" in
x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
;;
*)
echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
exit 0
;;
esac
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
if [[ -z "${archive}" || -z "${binding}" ]]; then
echo "Missing staged archive or binding for ${TARGET}." >&2
exit 1
fi
cargo_args=(test -p codex-v8-poc)
if [[ "${SANDBOX}" == "true" ]]; then
cargo_args+=(--features sandbox)
fi
(
cd codex-rs
CARGO_TARGET_DIR="${RUNNER_TEMP}/rusty-v8-cargo-smoke-${TARGET}-${SANDBOX}" \
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
cargo "${cargo_args[@]}"
)
- name: Upload staged artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.variant }}-${{ matrix.target }}
path: dist/${{ matrix.target }}/*
build-windows-source:
name: Build ptrcomp-sandbox ${{ matrix.target }} from source
needs: metadata
runs-on: ${{ matrix.runner }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- runner: windows-2022
target: x86_64-pc-windows-msvc
- runner: windows-2022
target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Configure git for upstream checkout
shell: bash
run: git config --global core.symlinks true
- name: Check out upstream rusty_v8
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: denoland/rusty_v8
ref: v${{ needs.metadata.outputs.v8_version }}
path: upstream-rusty-v8
submodules: recursive
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
architecture: x64
- name: Set up Codex Rust toolchain for Cargo smoke
uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
with:
toolchain: "1.93.0"
targets: ${{ matrix.target }}
- name: Install rusty_v8 Rust toolchain
env:
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
rustup toolchain install 1.91.0 --profile minimal --no-self-update
rustup target add --toolchain 1.91.0 "${TARGET}"
- name: Write upstream submodule status
shell: bash
working-directory: upstream-rusty-v8
run: git submodule status --recursive > git_submodule_status.txt
- name: Restore upstream source-build cache
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: |
upstream-rusty-v8/target/sccache
upstream-rusty-v8/target/${{ matrix.target }}/release/gn_out
key: rusty-v8-source-${{ matrix.target }}-sandbox-${{ hashFiles('upstream-rusty-v8/Cargo.lock', 'upstream-rusty-v8/build.rs', 'upstream-rusty-v8/git_submodule_status.txt') }}
restore-keys: |
rusty-v8-source-${{ matrix.target }}-sandbox-
- name: Install and start sccache
shell: pwsh
env:
SCCACHE_CACHE_SIZE: 256M
SCCACHE_DIR: ${{ github.workspace }}/upstream-rusty-v8/target/sccache
SCCACHE_IDLE_TIMEOUT: 0
run: |
$version = "v0.8.2"
$platform = "x86_64-pc-windows-msvc"
$basename = "sccache-$version-$platform"
$url = "https://github.com/mozilla/sccache/releases/download/$version/$basename.tar.gz"
cd ~
curl -LO $url
tar -xzvf "$basename.tar.gz"
. $basename/sccache --start-server
echo "$(pwd)/$basename" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install Chromium clang for ARM64 MSVC cross build
if: matrix.target == 'aarch64-pc-windows-msvc'
shell: bash
working-directory: upstream-rusty-v8
run: python3 tools/clang/scripts/update.py
- name: Build upstream rusty_v8 sandbox release pair
env:
SCCACHE_IDLE_TIMEOUT: 0
TARGET: ${{ matrix.target }}
V8_FROM_SOURCE: "1"
shell: bash
working-directory: upstream-rusty-v8
run: cargo +1.91.0 build --locked --release --target "${TARGET}" --features v8_enable_sandbox
- name: Stage upstream sandbox release pair
env:
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
python3 .github/scripts/rusty_v8_bazel.py stage-upstream-release-pair \
--source-root upstream-rusty-v8 \
--target "${TARGET}" \
--output-dir "dist/${TARGET}" \
--sandbox
- name: Smoke link staged artifact with Cargo
env:
TARGET: ${{ matrix.target }}
shell: bash
run: |
set -euo pipefail
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'rusty_v8_*.lib.gz' -print -quit)"
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
if [[ -z "${archive}" || -z "${binding}" ]]; then
echo "Missing staged archive or binding for ${TARGET}." >&2
exit 1
fi
(
cd codex-rs
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
cargo +1.93.0 test -p codex-v8-poc --target "${TARGET}" --features sandbox --no-run
)
- name: Upload staged artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-ptrcomp-sandbox-${{ matrix.target }}
path: dist/${{ matrix.target }}/*
+48 -80
View File
@@ -10,6 +10,7 @@ single_version_override(
module_name = "llvm",
patch_strip = 1,
patches = [
"//patches:llvm_rusty_v8_custom_libcxx.patch",
"//patches:llvm_windows_symlink_extract.patch",
],
)
@@ -77,6 +78,13 @@ use_repo(osx, "macos_sdk")
# Needed to disable xcode...
bazel_dep(name = "apple_support", version = "2.1.0")
bazel_dep(name = "rules_cc", version = "0.2.16")
single_version_override(
module_name = "rules_cc",
patch_strip = 1,
patches = [
"//patches:rules_cc_rusty_v8_custom_libcxx.patch",
],
)
bazel_dep(name = "rules_platform", version = "0.1.0")
bazel_dep(name = "rules_rs", version = "0.0.58")
# `rules_rs` still does not model `windows-gnullvm` as a distinct Windows exec
@@ -407,18 +415,18 @@ crate.annotation(
inject_repo(crate, "alsa_lib")
bazel_dep(name = "v8", version = "14.6.202.9")
bazel_dep(name = "v8", version = "14.7.173.20")
archive_override(
module_name = "v8",
integrity = "sha256-JphDwLAzsd9KvgRZ7eQvNtPU6qGd3XjFt/a/1QITAJU=",
integrity = "sha256-v/x6I4X38a2wckzUIft3Dh0SUdkuOTokwxyF7lzW8Lc=",
patch_strip = 3,
patches = [
"//patches:v8_module_deps.patch",
"//patches:v8_bazel_rules.patch",
"//patches:v8_source_portability.patch",
],
strip_prefix = "v8-14.6.202.9",
urls = ["https://github.com/v8/v8/archive/refs/tags/14.6.202.9.tar.gz"],
strip_prefix = "v8-14.7.173.20",
urls = ["https://github.com/v8/v8/archive/refs/tags/14.7.173.20.tar.gz"],
)
http_archive(
@@ -430,93 +438,53 @@ http_archive(
urls = ["https://static.crates.io/crates/v8/v8-146.4.0.crate"],
)
http_file(
name = "rusty_v8_146_4_0_aarch64_apple_darwin_archive",
downloaded_file_path = "librusty_v8_release_aarch64-apple-darwin.a.gz",
sha256 = "bfe2c9be32a56c28546f0f965825ee68fbf606405f310cc4e17b448a568cf98a",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_aarch64-apple-darwin.a.gz",
],
http_archive(
name = "v8_crate_147_4_0",
build_file = "//third_party/v8:v8_crate.BUILD.bazel",
sha256 = "2df8fffd507fb18ed000673a83d937f58e60fb07f3306b2274284125b15137cd",
strip_prefix = "v8-147.4.0",
type = "tar.gz",
urls = ["https://static.crates.io/crates/v8/v8-147.4.0.crate"],
)
git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "rusty_v8_libcxx",
build_file = "//third_party/v8:libcxx.BUILD.bazel",
commit = "7ab65651aed6802d2599dcb7a73b1f82d5179d05",
remote = "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git",
)
git_repository(
name = "rusty_v8_libcxxabi",
build_file = "//third_party/v8:libcxxabi.BUILD.bazel",
commit = "8f11bb1d4438d0239d0dfc1bd9456a9f31629dda",
remote = "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git",
)
git_repository(
name = "rusty_v8_llvm_libc",
build_file = "//third_party/v8:llvm_libc.BUILD.bazel",
commit = "b3aa5bb702ff9e890179fd1e7d3ba346e17ecf8e",
remote = "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git",
)
http_file(
name = "rusty_v8_146_4_0_aarch64_unknown_linux_gnu_archive",
downloaded_file_path = "librusty_v8_release_aarch64-unknown-linux-gnu.a.gz",
sha256 = "dbf165b07c81bdb054bc046b43d23e69fcf7bcc1a4c1b5b4776983a71062ecd8",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_aarch64-unknown-linux-gnu.a.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_aarch64_pc_windows_msvc_archive",
name = "rusty_v8_147_4_0_aarch64_pc_windows_msvc_archive",
downloaded_file_path = "rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
sha256 = "ed13363659c6d08583ac8fdc40493445c5767d8b94955a4d5d7bb8d5a81f6bf8",
sha256 = "1fa3f94d9e09cff1f6bcce94c478e5cb072c0755f6a0357abadb9dd3b48d8127",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
"https://github.com/denoland/rusty_v8/releases/download/v147.4.0/rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_x86_64_apple_darwin_archive",
downloaded_file_path = "librusty_v8_release_x86_64-apple-darwin.a.gz",
sha256 = "630cd240f1bbecdb071417dc18387ab81cf67c549c1c515a0b4fcf9eba647bb7",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_x86_64-apple-darwin.a.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_x86_64_unknown_linux_gnu_archive",
downloaded_file_path = "librusty_v8_release_x86_64-unknown-linux-gnu.a.gz",
sha256 = "e64b4d99e4ae293a2e846244a89b80178ba10382c13fb591c1fa6968f5291153",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_x86_64-unknown-linux-gnu.a.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_x86_64_pc_windows_msvc_archive",
name = "rusty_v8_147_4_0_x86_64_pc_windows_msvc_archive",
downloaded_file_path = "rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
sha256 = "90a9a2346acd3685a355e98df85c24dbe406cb124367d16259a4b5d522621862",
sha256 = "e2827ff98b1a9d4c0343000fc5124ac30dfab3007bc0129c168c9355fc2fcd7c",
urls = [
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_aarch64_unknown_linux_musl_archive",
downloaded_file_path = "librusty_v8_release_aarch64-unknown-linux-musl.a.gz",
sha256 = "27a08ed26c34297bfd93e514692ccc44b85f8b15c6aa39cf34e784f84fb37e8e",
urls = [
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/librusty_v8_release_aarch64-unknown-linux-musl.a.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_aarch64_unknown_linux_musl_binding",
downloaded_file_path = "src_binding_release_aarch64-unknown-linux-musl.rs",
sha256 = "09f8900ced8297c229246c7a50b2e0ec23c54d0a554f369619cc29863f38dd1a",
urls = [
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/src_binding_release_aarch64-unknown-linux-musl.rs",
],
)
http_file(
name = "rusty_v8_146_4_0_x86_64_unknown_linux_musl_archive",
downloaded_file_path = "librusty_v8_release_x86_64-unknown-linux-musl.a.gz",
sha256 = "20d8271ad712323d352c1383c36e3c4b755abc41ece35819c49c75ec7134d2f8",
urls = [
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/librusty_v8_release_x86_64-unknown-linux-musl.a.gz",
],
)
http_file(
name = "rusty_v8_146_4_0_x86_64_unknown_linux_musl_binding",
downloaded_file_path = "src_binding_release_x86_64-unknown-linux-musl.rs",
sha256 = "09f8900ced8297c229246c7a50b2e0ec23c54d0a554f369619cc29863f38dd1a",
urls = [
"https://github.com/openai/codex/releases/download/rusty-v8-v146.4.0/src_binding_release_x86_64-unknown-linux-musl.rs",
"https://github.com/denoland/rusty_v8/releases/download/v147.4.0/rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
],
)
+33 -17
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -2,7 +2,7 @@
// Unified entry point for the Codex CLI.
import { spawn } from "node:child_process";
import { existsSync } from "fs";
import { existsSync, realpathSync } from "fs";
import { createRequire } from "node:module";
import path from "path";
import { fileURLToPath } from "url";
@@ -171,6 +171,7 @@ const packageManagerEnvVar =
? "CODEX_MANAGED_BY_BUN"
: "CODEX_MANAGED_BY_NPM";
env[packageManagerEnvVar] = "1";
env.CODEX_MANAGED_PACKAGE_ROOT = realpathSync(path.join(__dirname, ".."));
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
+1 -1
View File
@@ -1,5 +1,5 @@
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "link-arg=/STACK:8388608"]
rustflags = ["-C", "link-arg=/STACK:8388608", "-C", "target-feature=+crt-static"]
# MSVC emits a warning about code that may trip "Cortex-A53 MPCore processor bug #843419" (see
# https://developer.arm.com/documentation/epm048406/latest) which is sometimes emitted by LLVM.
+23 -1
View File
@@ -1,6 +1,10 @@
[profile.default]
# Do not increase, fix your test instead
# Retry once so one transient failure does not fail full-CI outright.
slow-timeout = { period = "15s", terminate-after = 2 }
retries = 1
[profile.default.junit]
path = "junit.xml"
[test-groups.app_server_protocol_codegen]
max-threads = 1
@@ -14,6 +18,9 @@ max-threads = 1
[test-groups.windows_sandbox_legacy_sessions]
max-threads = 1
[test-groups.windows_process_heavy]
max-threads = 2
[[profile.default.overrides]]
# Do not add new tests here
filter = 'test(rmcp_client) | test(humanlike_typing_1000_chars_appears_live_no_placeholder)'
@@ -44,3 +51,18 @@ test-group = 'core_apply_patch_cli_integration'
# Serialize them to avoid exhausting Windows session/global desktop resources in CI.
filter = 'package(codex-windows-sandbox) & test(legacy_)'
test-group = 'windows_sandbox_legacy_sessions'
[[profile.default.overrides]]
# This Codex-home startup path still exceeded the broader Windows-heavy ceiling
# in both Windows full-CI lanes after contention was reduced.
platform = 'cfg(windows)'
filter = 'test(start_thread_uses_all_default_environments_from_codex_home)'
slow-timeout = { period = "1m", terminate-after = 2 }
[[profile.default.overrides]]
# These Windows-heavy tests spawn subprocesses, session files, or JSON-RPC
# clients and have been the dominant source of 30s full-CI timeouts.
platform = 'cfg(windows)'
filter = 'test(suite::resume::) | test(suite::cli_stream::) | test(suite::auth_env::) | test(start_thread_uses_all_default_environments_from_codex_home) | test(connect_stdio_command_initializes_json_rpc_client_on_windows)'
test-group = 'windows_process_heavy'
slow-timeout = { period = "45s", terminate-after = 2 }
+130 -75
View File
@@ -1518,9 +1518,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "calendrical_calculations"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7"
checksum = "5abbd6eeda6885048d357edc66748eea6e0268e3dd11f326fff5bd248d779c26"
dependencies = [
"core_maths",
"displaydoc",
@@ -1902,12 +1902,12 @@ dependencies = [
"codex-feedback",
"codex-file-search",
"codex-file-watcher",
"codex-git-attribution",
"codex-git-utils",
"codex-guardian",
"codex-hooks",
"codex-login",
"codex-mcp",
"codex-memories-extension",
"codex-memories-write",
"codex-model-provider",
"codex-model-provider-info",
@@ -2221,6 +2221,7 @@ dependencies = [
"assert_matches",
"clap",
"clap_complete",
"codex-api",
"codex-app-server",
"codex-app-server-daemon",
"codex-app-server-protocol",
@@ -2235,11 +2236,14 @@ dependencies = [
"codex-exec-server",
"codex-execpolicy",
"codex-features",
"codex-install-context",
"codex-login",
"codex-mcp",
"codex-mcp-server",
"codex-memories-write",
"codex-model-provider",
"codex-models-manager",
"codex-plugin",
"codex-protocol",
"codex-responses-api-proxy",
"codex-rmcp-client",
@@ -2254,11 +2258,14 @@ dependencies = [
"codex-utils-cli",
"codex-utils-path",
"codex-windows-sandbox",
"crossterm",
"http 1.4.0",
"libc",
"owo-colors",
"predicates",
"pretty_assertions",
"regex-lite",
"serde",
"serde_json",
"sqlx",
"supports-color 3.0.2",
@@ -2432,6 +2439,7 @@ dependencies = [
"prost 0.14.3",
"schemars 0.8.22",
"serde",
"serde_ignored",
"serde_json",
"serde_path_to_error",
"sha2",
@@ -2628,6 +2636,7 @@ dependencies = [
"libc",
"pretty_assertions",
"reqwest",
"semver",
"serde",
"serde_json",
"tar",
@@ -2708,6 +2717,7 @@ dependencies = [
"codex-utils-cargo-bin",
"codex-utils-cli",
"codex-utils-oss",
"codex-utils-sandbox-summary",
"core_test_support",
"libc",
"opentelemetry",
@@ -2739,6 +2749,7 @@ dependencies = [
"axum",
"base64 0.22.1",
"bytes",
"codex-api",
"codex-app-server-protocol",
"codex-client",
"codex-file-system",
@@ -2750,6 +2761,7 @@ dependencies = [
"codex-utils-rustls-provider",
"ctor 0.6.3",
"futures",
"http 1.4.0",
"pretty_assertions",
"prost 0.14.3",
"reqwest",
@@ -2818,6 +2830,7 @@ dependencies = [
name = "codex-extension-api"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-protocol",
"codex-tools",
]
@@ -2911,16 +2924,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "codex-git-attribution"
version = "0.0.0"
dependencies = [
"codex-core",
"codex-extension-api",
"codex-features",
"pretty_assertions",
]
[[package]]
name = "codex-git-utils"
version = "0.0.0"
@@ -2945,10 +2948,23 @@ dependencies = [
"walkdir",
]
[[package]]
name = "codex-goal-extension"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-extension-api",
"codex-protocol",
"codex-tools",
"serde",
"serde_json",
]
[[package]]
name = "codex-guardian"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-core",
"codex-extension-api",
"codex-protocol",
@@ -3141,6 +3157,7 @@ dependencies = [
name = "codex-memories-extension"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-core",
"codex-extension-api",
"codex-features",
@@ -3481,6 +3498,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"axum",
"base64 0.22.1",
"bytes",
"codex-api",
"codex-client",
@@ -3732,6 +3750,7 @@ dependencies = [
name = "codex-tools"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-app-server-protocol",
"codex-code-mode",
"codex-features",
@@ -3785,6 +3804,7 @@ dependencies = [
"codex-protocol",
"codex-realtime-webrtc",
"codex-rollout",
"codex-sandboxing",
"codex-shell-command",
"codex-state",
"codex-terminal-detection",
@@ -3794,10 +3814,10 @@ dependencies = [
"codex-utils-cli",
"codex-utils-elapsed",
"codex-utils-fuzzy-match",
"codex-utils-home-dir",
"codex-utils-oss",
"codex-utils-path",
"codex-utils-plugins",
"codex-utils-pty",
"codex-utils-sandbox-summary",
"codex-utils-sleep-inhibitor",
"codex-utils-string",
@@ -3914,6 +3934,7 @@ version = "0.0.0"
dependencies = [
"clap",
"codex-protocol",
"codex-shell-command",
"pretty_assertions",
"serde",
"toml 0.9.11+spec-1.1.0",
@@ -5078,9 +5099,9 @@ dependencies = [
[[package]]
name = "diplomat"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9adb46b05e2f53dcf6a7dfc242e4ce9eb60c369b6b6eb10826a01e93167f59c6"
checksum = "7935649d00000f5c5d735448ad3dc07b9738160727017914cf42138b8e8e6611"
dependencies = [
"diplomat_core",
"proc-macro2",
@@ -5090,15 +5111,15 @@ dependencies = [
[[package]]
name = "diplomat-runtime"
version = "0.14.0"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0569bd3caaf13829da7ee4e83dbf9197a0e1ecd72772da6d08f0b4c9285c8d29"
checksum = "970ac38ad677632efcee6d517e783958da9bc78ec206d8d5e35b459ffc5e4864"
[[package]]
name = "diplomat_core"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51731530ed7f2d4495019abc7df3744f53338e69e2863a6a64ae91821c763df1"
checksum = "9cf41b94101a4bce993febaf0098092b0bb31deaf0ecaf6e0a2562465f61b383"
dependencies = [
"proc-macro2",
"quote",
@@ -5454,7 +5475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -5642,9 +5663,9 @@ dependencies = [
[[package]]
name = "fixed_decimal"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35eabf480f94d69182677e37571d3be065822acfafd12f2f085db44fbbcc8e57"
checksum = "79c3c892f121fff406e5dd6b28c1b30096b95111c30701a899d4f2b18da6d1bd"
dependencies = [
"displaydoc",
"smallvec",
@@ -7487,9 +7508,9 @@ dependencies = [
[[package]]
name = "icu_calendar"
version = "2.1.1"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e"
checksum = "a2b2acc6263f494f1df50685b53ff8e57869e47d5c6fe39c23d518ae9a4f3e45"
dependencies = [
"calendrical_calculations",
"displaydoc",
@@ -7503,18 +7524,19 @@ dependencies = [
[[package]]
name = "icu_calendar_data"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d"
checksum = "118577bcf3a0fa7c6ac0a7d6e951814da84ee56b9b1f68fb4d8d10b08cefaf4d"
[[package]]
name = "icu_collections"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
"yoke",
"zerofrom",
"zerovec",
@@ -7522,14 +7544,16 @@ dependencies = [
[[package]]
name = "icu_decimal"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38c52231bc348f9b982c1868a2af3195199623007ba2c7650f432038f5b3e8e"
checksum = "288247df2e32aa776ac54fdd64de552149ac43cb840f2761811f0e8d09719dd4"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_decimal_data",
"icu_locale",
"icu_locale_core",
"icu_plurals",
"icu_provider",
"writeable",
"zerovec",
@@ -7537,15 +7561,15 @@ dependencies = [
[[package]]
name = "icu_decimal_data"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2905b4044eab2dd848fe84199f9195567b63ab3a93094711501363f63546fef7"
checksum = "6f14a5ca9e8af29eef62064f269078424283d90dbaffeac5225addf62aaabc22"
[[package]]
name = "icu_locale"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60"
checksum = "d5a396343c7208121dc86e35623d3dfe19814a7613cfd14964994cdc9c9a2e26"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -7558,9 +7582,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
@@ -7572,15 +7596,15 @@ dependencies = [
[[package]]
name = "icu_locale_data"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831"
checksum = "d5fdcc9ac77c6d74ff5cf6e65ef3181d6af32003b16fce3a77fb451d2f695993"
[[package]]
name = "icu_normalizer"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -7592,15 +7616,34 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_plurals"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a50023f1d49ad5c4333380328a0d4a19e4b9d6d842ec06639affd5ba47c8103"
dependencies = [
"fixed_decimal",
"icu_locale",
"icu_plurals_data",
"icu_provider",
"zerovec",
]
[[package]]
name = "icu_plurals_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8485497155dc865f901decb93ecc20d3e467df67bfeceb91e3ba34e2b11e8e1d"
[[package]]
name = "icu_properties"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -7612,15 +7655,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -10875,9 +10918,9 @@ dependencies = [
[[package]]
name = "resb"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a067ab3b5ca3b4dc307d0de9cf75f9f5e6ca9717b192b2f28a36c83e5de9e76"
checksum = "22d392791f3c6802a1905a509e9d1a6039cbbcb5e9e00e5a6d3661f7c874f390"
dependencies = [
"potential_utf",
"serde_core",
@@ -11641,6 +11684,16 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_ignored"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde_json"
version = "1.0.149"
@@ -12594,14 +12647,14 @@ dependencies = [
[[package]]
name = "temporal_capi"
version = "0.1.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a151e402c2bdb6a3a2a2f3f225eddaead2e7ce7dd5d3fa2090deb11b17aa4ed8"
checksum = "8a2a1f001e756a9f5f2d175a9965c4c0b3a054f09f30de3a75ab49765f2deb36"
dependencies = [
"diplomat",
"diplomat-runtime",
"icu_calendar",
"icu_locale",
"icu_locale_core",
"num-traits",
"temporal_rs",
"timezone_provider",
@@ -12611,13 +12664,14 @@ dependencies = [
[[package]]
name = "temporal_rs"
version = "0.1.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88afde3bd75d2fc68d77a914bece426aa08aa7649ffd0cdd4a11c3d4d33474d1"
checksum = "9a902a45282e5175186b21d355efc92564601efe6e2d92818dc9e333d50bd4de"
dependencies = [
"calendrical_calculations",
"core_maths",
"icu_calendar",
"icu_locale",
"icu_locale_core",
"ixdtf",
"num-traits",
"timezone_provider",
@@ -12834,9 +12888,9 @@ dependencies = [
[[package]]
name = "timezone_provider"
version = "0.1.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9ba0000e9e73862f3e7ca1ff159e2ddf915c9d8bb11e38a7874760f445d993"
checksum = "c48f9b04628a2b813051e4dfe97c65281e49625eabd09ec343190e31e399a8c2"
dependencies = [
"tinystr",
"zerotrie",
@@ -12867,9 +12921,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"serde_core",
@@ -13683,9 +13737,9 @@ dependencies = [
[[package]]
name = "v8"
version = "146.4.0"
version = "147.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97bcac5cdc5a195a4813f1855a6bc658f240452aac36caa12fd6c6f16026ab1"
checksum = "2df8fffd507fb18ed000673a83d937f58e60fb07f3306b2274284125b15137cd"
dependencies = [
"bindgen",
"bitflags 2.10.0",
@@ -14835,9 +14889,9 @@ dependencies = [
[[package]]
name = "yoke"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -14846,9 +14900,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
@@ -14981,20 +15035,21 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "zerovec"
version = "0.11.5"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"serde",
"yoke",
@@ -15004,9 +15059,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.2"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
@@ -15077,9 +15132,9 @@ checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
[[package]]
name = "zoneinfo64"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2e5597efbe7c421da8a7fd396b20b571704e787c21a272eecf35dfe9d386f0"
checksum = "ed6eb2607e906160c457fd573e9297e65029669906b9ac8fb1b5cd5e055f0705"
dependencies = [
"calendrical_calculations",
"icu_locale_core",
+5 -4
View File
@@ -45,8 +45,8 @@ members = [
"execpolicy",
"execpolicy-legacy",
"ext/extension-api",
"ext/goal",
"ext/guardian",
"ext/git-attribution",
"ext/memories",
"external-agent-migration",
"external-agent-sessions",
@@ -163,8 +163,8 @@ codex-file-system = { path = "file-system" }
codex-exec-server = { path = "exec-server" }
codex-execpolicy = { path = "execpolicy" }
codex-extension-api = { path = "ext/extension-api" }
codex-goal-extension = { path = "ext/goal" }
codex-guardian = { path = "ext/guardian" }
codex-git-attribution = { path = "ext/git-attribution" }
codex-external-agent-migration = { path = "external-agent-migration" }
codex-external-agent-sessions = { path = "external-agent-sessions" }
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
@@ -352,6 +352,7 @@ seccompiler = "0.5.0"
semver = "1.0"
sentry = "0.46.0"
serde = "1"
serde_ignored = "0.1.14"
serde_json = "1"
serde_path_to_error = "0.1.20"
serde_with = "3.17"
@@ -412,7 +413,7 @@ unicode-width = "0.2"
url = "2"
urlencoding = "2.1"
uuid = "1"
v8 = "=146.4.0"
v8 = "=147.4.0"
vt100 = "0.16.2"
walkdir = "2.5.0"
webbrowser = "1.0"
@@ -471,7 +472,7 @@ unwrap_used = "deny"
[workspace.metadata.cargo-shear]
ignored = [
"codex-agent-graph-store",
"codex-memories-extension",
"codex-goal-extension",
"icu_provider",
"openssl-sys",
"codex-v8-poc",
@@ -138,7 +138,6 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::HookEventName;
use codex_protocol::protocol::HookRunStatus;
use codex_protocol::protocol::HookSource;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadSource;
@@ -201,11 +200,11 @@ fn sample_thread_start_response(
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
runtime_workspace_roots: Vec::new(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: None,
active_permission_profile: None,
reasoning_effort: None,
})
@@ -257,11 +256,11 @@ fn sample_thread_resume_response_with_source(
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
runtime_workspace_roots: Vec::new(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: None,
active_permission_profile: None,
reasoning_effort: None,
})
@@ -279,6 +278,7 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest
},
UserInput::Image {
url: "https://example.com/a.png".to_string(),
detail: None,
},
],
..Default::default()
@@ -366,9 +366,7 @@ fn sample_turn_resolved_config(thread_id: &str, turn_id: &str) -> TurnResolvedCo
session_source: SessionSource::Exec,
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
permission_profile: CorePermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::new_read_only_policy(),
),
permission_profile: CorePermissionProfile::read_only(),
permission_profile_cwd: PathBuf::from("/tmp"),
reasoning_effort: None,
reasoning_summary: None,
@@ -399,6 +397,7 @@ fn sample_turn_steer_request(
},
UserInput::LocalImage {
path: "/tmp/a.png".into(),
detail: None,
},
],
responsesapi_client_metadata: None,
+3 -9
View File
@@ -12,7 +12,6 @@ use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy;
use codex_app_server_protocol::SessionSource as AppServerSessionSource;
@@ -29,7 +28,6 @@ use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStatus as AppServerTurnStatus;
use codex_app_server_protocol::TurnSteerParams;
use codex_app_server_protocol::TurnSteerResponse;
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use std::collections::HashSet;
@@ -142,10 +140,6 @@ fn sample_thread(thread_id: &str) -> Thread {
}
}
fn sample_permission_profile() -> AppServerPermissionProfile {
CorePermissionProfile::Disabled.into()
}
fn sample_thread_start_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadStart(ThreadStartResponse {
thread: sample_thread("thread-1"),
@@ -153,11 +147,11 @@ fn sample_thread_start_response() -> ClientResponsePayload {
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
runtime_workspace_roots: Vec::new(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
@@ -170,11 +164,11 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
runtime_workspace_roots: Vec::new(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
@@ -187,11 +181,11 @@ fn sample_thread_fork_response() -> ClientResponsePayload {
model_provider: "openai".to_string(),
service_tier: None,
cwd: test_path_buf("/tmp").abs(),
runtime_workspace_roots: Vec::new(),
instruction_sources: Vec::new(),
approval_policy: AppServerAskForApproval::OnFailure,
approvals_reviewer: AppServerApprovalsReviewer::User,
sandbox: AppServerSandboxPolicy::DangerFullAccess,
permission_profile: Some(sample_permission_profile()),
active_permission_profile: None,
reasoning_effort: None,
})
+1
View File
@@ -990,6 +990,7 @@ fn analytics_hook_event_name(event_name: HookEventName) -> &'static str {
HookEventName::PostCompact => "PostCompact",
HookEventName::SessionStart => "SessionStart",
HookEventName::UserPromptSubmit => "UserPromptSubmit",
HookEventName::SubagentStart => "SubagentStart",
HookEventName::Stop => "Stop",
}
}
+13 -5
View File
@@ -336,6 +336,8 @@ pub struct InProcessClientStartArgs {
pub cli_overrides: Vec<(String, TomlValue)>,
/// Loader override knobs used by config API paths.
pub loader_overrides: LoaderOverrides,
/// Whether config API paths should reject unknown config fields.
pub strict_config: bool,
/// Preloaded cloud requirements provider.
pub cloud_requirements: CloudRequirementsLoader,
/// Feedback sink used by app-server/core telemetry and logs.
@@ -402,6 +404,7 @@ impl InProcessClientStartArgs {
config: self.config,
cli_overrides: self.cli_overrides,
loader_overrides: self.loader_overrides,
strict_config: self.strict_config,
cloud_requirements: self.cloud_requirements,
thread_config_loader,
feedback: self.feedback,
@@ -1030,6 +1033,7 @@ mod tests {
config,
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -2174,11 +2178,13 @@ mod tests {
let environment_manager = Arc::new(
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths"),
Some(
ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths"),
),
)
.await,
);
@@ -2188,6 +2194,7 @@ mod tests {
config: config.clone(),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -2228,6 +2235,7 @@ mod tests {
config: Arc::new(config),
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
strict_config: false,
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
@@ -1,5 +1,6 @@
mod pid;
use std::path::Path;
use std::path::PathBuf;
use serde::Serialize;
@@ -31,3 +32,15 @@ pub(crate) fn pid_backend(paths: BackendPaths) -> PidBackend {
pub(crate) fn pid_update_loop_backend(paths: BackendPaths) -> PidBackend {
PidBackend::new_update_loop(paths.codex_bin, paths.update_pid_file)
}
pub(crate) async fn append_stderr_log_tail_context(pid_file: &Path, context: &mut String) {
match pid::read_stderr_log_tail(pid_file).await {
Ok(Some(tail)) => tail.append_to_context(context),
Ok(None) => {}
Err(err) => {
context.push_str(&format!(
"\n\nFailed to read managed app-server stderr log: {err:#}"
));
}
}
}
+102 -2
View File
@@ -1,3 +1,4 @@
use std::io::SeekFrom;
use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
@@ -10,6 +11,8 @@ use anyhow::bail;
use serde::Deserialize;
use serde::Serialize;
use tokio::fs;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncSeekExt;
#[cfg(unix)]
use tokio::process::Command;
use tokio::time::sleep;
@@ -18,6 +21,7 @@ const STOP_POLL_INTERVAL: Duration = Duration::from_millis(50);
const STOP_GRACE_PERIOD: Duration = Duration::from_secs(60);
const STOP_TIMEOUT: Duration = Duration::from_secs(70);
const START_TIMEOUT: Duration = Duration::from_secs(10);
const STDERR_LOG_TAIL_BYTES: u64 = 4096;
#[derive(Debug)]
#[cfg_attr(not(unix), allow(dead_code))]
@@ -35,6 +39,25 @@ struct PidRecord {
process_start_time: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PidLogTail {
pub(crate) path: PathBuf,
pub(crate) contents: String,
}
impl PidLogTail {
pub(crate) fn append_to_context(&self, context: &mut String) {
context.push_str(&format!(
"\n\nManaged app-server stderr ({}):",
self.path.display()
));
for line in self.contents.lines() {
context.push_str("\n ");
context.push_str(line);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PidFileState {
Missing,
@@ -129,11 +152,18 @@ impl PidBackend {
}
};
let mut command = Command::new(&self.codex_bin);
let stderr_log = match self.open_stderr_log().await {
Ok(stderr_log) => stderr_log,
Err(err) => {
let _ = fs::remove_file(&self.pid_file).await;
return Err(err);
}
};
command
.args(self.command_args())
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
.stderr(Stdio::from(stderr_log.into_std().await));
#[cfg(unix)]
{
@@ -169,8 +199,11 @@ impl PidBackend {
},
Err(err) => {
let _ = self.terminate_process(pid);
let mut context =
format!("failed to record pid-managed app-server process {pid} startup");
super::append_stderr_log_tail_context(&self.pid_file, &mut context).await;
let _ = fs::remove_file(&self.pid_file).await;
return Err(err);
return Err(err).context(context);
}
};
let contents = serde_json::to_vec(&record).context("failed to serialize pid record")?;
@@ -344,6 +377,23 @@ impl PidBackend {
Ok(reservation_lock)
}
#[cfg(unix)]
async fn open_stderr_log(&self) -> Result<fs::File> {
let stderr_log_file = stderr_log_file_for_pid_file(&self.pid_file);
fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&stderr_log_file)
.await
.with_context(|| {
format!(
"failed to open stderr log for pid-managed app server {}",
stderr_log_file.display()
)
})
}
#[cfg(unix)]
fn command_args(&self) -> Vec<&'static str> {
match self.command_kind {
@@ -376,6 +426,56 @@ impl PidBackend {
}
}
pub(crate) async fn read_stderr_log_tail(pid_file: &Path) -> Result<Option<PidLogTail>> {
let path = stderr_log_file_for_pid_file(pid_file);
let Some(contents) = read_log_tail(&path, STDERR_LOG_TAIL_BYTES).await? else {
return Ok(None);
};
Ok(Some(PidLogTail { path, contents }))
}
fn stderr_log_file_for_pid_file(pid_file: &Path) -> PathBuf {
pid_file.with_extension("stderr.log")
}
async fn read_log_tail(path: &Path, byte_limit: u64) -> Result<Option<String>> {
let mut file = match fs::File::open(path).await {
Ok(file) => file,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => {
return Err(err)
.with_context(|| format!("failed to open stderr log {}", path.display()));
}
};
let len = file
.metadata()
.await
.with_context(|| format!("failed to inspect stderr log {}", path.display()))?
.len();
if len == 0 {
return Ok(None);
}
let start = len.saturating_sub(byte_limit);
file.seek(SeekFrom::Start(start))
.await
.with_context(|| format!("failed to seek stderr log {}", path.display()))?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)
.await
.with_context(|| format!("failed to read stderr log {}", path.display()))?;
if start > 0
&& let Some(newline_index) = bytes.iter().position(|byte| *byte == b'\n')
{
bytes.drain(..=newline_index);
}
let contents = String::from_utf8_lossy(&bytes).trim_end().to_string();
if contents.is_empty() {
return Ok(None);
}
Ok(Some(contents))
}
#[cfg(unix)]
fn process_exists(pid: u32) -> bool {
let Ok(pid) = libc::pid_t::try_from(pid) else {
@@ -6,7 +6,10 @@ use tempfile::TempDir;
use super::PidBackend;
use super::PidCommandKind;
use super::PidFileState;
use super::PidLogTail;
use super::PidRecord;
use super::read_stderr_log_tail;
use super::stderr_log_file_for_pid_file;
use super::try_lock_file;
#[tokio::test]
@@ -156,3 +159,38 @@ fn update_loop_uses_hidden_app_server_subcommand() {
vec!["app-server", "daemon", "pid-update-loop"]
);
}
#[test]
fn app_server_remote_control_uses_runtime_flag() {
let backend = PidBackend::new(
"codex".into(),
"app-server.pid".into(),
/*remote_control_enabled*/ true,
);
assert_eq!(
backend.command_args(),
vec!["app-server", "--remote-control", "--listen", "unix://"]
);
}
#[tokio::test]
async fn read_stderr_log_tail_returns_recent_complete_lines() {
let temp_dir = TempDir::new().expect("temp dir");
let pid_file = temp_dir.path().join("app-server.pid");
let log_file = stderr_log_file_for_pid_file(&pid_file);
let contents = format!("{}\nrecent error\nusage", "x".repeat(4100));
tokio::fs::write(&log_file, contents)
.await
.expect("write stderr log");
assert_eq!(
read_stderr_log_tail(&pid_file)
.await
.expect("read stderr log"),
Some(PidLogTail {
path: log_file,
contents: "recent error\nusage".to_string(),
})
);
}
+95 -45
View File
@@ -5,6 +5,7 @@ use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::JSONRPCMessage;
@@ -14,12 +15,16 @@ use codex_app_server_protocol::RequestId;
use codex_uds::UnixStream;
use futures::SinkExt;
use futures::StreamExt;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::time::timeout;
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::client_async;
use tokio_tungstenite::tungstenite::Message;
const PROBE_TIMEOUT: Duration = Duration::from_secs(2);
pub(crate) const CONTROL_SOCKET_RESPONSE_TIMEOUT: Duration = Duration::from_secs(2);
const CLIENT_NAME: &str = "codex_app_server_daemon";
const INITIALIZE_REQUEST_ID: RequestId = RequestId::Integer(1);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ProbeInfo {
@@ -27,7 +32,7 @@ pub(crate) struct ProbeInfo {
}
pub(crate) async fn probe(socket_path: &Path) -> Result<ProbeInfo> {
timeout(PROBE_TIMEOUT, probe_inner(socket_path))
timeout(CONTROL_SOCKET_RESPONSE_TIMEOUT, probe_inner(socket_path))
.await
.with_context(|| {
format!(
@@ -38,54 +43,14 @@ pub(crate) async fn probe(socket_path: &Path) -> Result<ProbeInfo> {
}
async fn probe_inner(socket_path: &Path) -> Result<ProbeInfo> {
let stream = UnixStream::connect(socket_path)
.await
.with_context(|| format!("failed to connect to {}", socket_path.display()))?;
let (mut websocket, _response) = client_async("ws://localhost/", stream)
.await
.with_context(|| format!("failed to upgrade {}", socket_path.display()))?;
let initialize = JSONRPCMessage::Request(JSONRPCRequest {
id: RequestId::Integer(1),
method: "initialize".to_string(),
params: Some(serde_json::to_value(InitializeParams {
client_info: ClientInfo {
name: CLIENT_NAME.to_string(),
title: Some("Codex App Server Daemon".to_string()),
version: env!("CARGO_PKG_VERSION").to_string(),
},
capabilities: None,
})?),
trace: None,
});
websocket
.send(Message::Text(serde_json::to_string(&initialize)?.into()))
.await
.context("failed to send initialize request")?;
let response = loop {
let frame = websocket
.next()
.await
.ok_or_else(|| anyhow!("app-server closed before initialize response"))??;
let Message::Text(payload) = frame else {
continue;
};
let message = serde_json::from_str::<JSONRPCMessage>(&payload)?;
if let JSONRPCMessage::Response(response) = message
&& response.id == RequestId::Integer(1)
{
break response;
}
};
let initialize_response = serde_json::from_value::<InitializeResponse>(response.result)?;
let mut websocket = connect(socket_path).await?;
let initialize_response = initialize(&mut websocket, /*experimental_api*/ false).await?;
let initialized = JSONRPCMessage::Notification(JSONRPCNotification {
method: "initialized".to_string(),
params: None,
});
websocket
.send(Message::Text(serde_json::to_string(&initialized)?.into()))
send_message(&mut websocket, &initialized)
.await
.context("failed to send initialized notification")?;
websocket.close(None).await.ok();
@@ -95,6 +60,91 @@ async fn probe_inner(socket_path: &Path) -> Result<ProbeInfo> {
})
}
pub(crate) async fn connect(socket_path: &Path) -> Result<WebSocketStream<UnixStream>> {
let stream = UnixStream::connect(socket_path)
.await
.with_context(|| format!("failed to connect to {}", socket_path.display()))?;
let (websocket, _response) = client_async("ws://localhost/", stream)
.await
.with_context(|| format!("failed to upgrade {}", socket_path.display()))?;
Ok(websocket)
}
pub(crate) async fn initialize<S>(
websocket: &mut WebSocketStream<S>,
experimental_api: bool,
) -> Result<InitializeResponse>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let initialize = JSONRPCMessage::Request(JSONRPCRequest {
id: INITIALIZE_REQUEST_ID,
method: "initialize".to_string(),
params: Some(serde_json::to_value(InitializeParams {
client_info: ClientInfo {
name: CLIENT_NAME.to_string(),
title: Some("Codex App Server Daemon".to_string()),
version: env!("CARGO_PKG_VERSION").to_string(),
},
capabilities: if experimental_api {
Some(InitializeCapabilities {
experimental_api: true,
..Default::default()
})
} else {
None
},
})?),
trace: None,
});
send_message(websocket, &initialize)
.await
.context("failed to send initialize request")?;
let response = loop {
let message = timeout(CONTROL_SOCKET_RESPONSE_TIMEOUT, read_message(websocket))
.await
.context("timed out waiting for initialize response")??;
if let JSONRPCMessage::Response(response) = message
&& response.id == INITIALIZE_REQUEST_ID
{
break response;
}
};
serde_json::from_value::<InitializeResponse>(response.result)
.context("failed to parse initialize response")
}
pub(crate) async fn send_message<S>(
websocket: &mut WebSocketStream<S>,
message: &JSONRPCMessage,
) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin,
{
websocket
.send(Message::Text(serde_json::to_string(message)?.into()))
.await?;
Ok(())
}
pub(crate) async fn read_message<S>(websocket: &mut WebSocketStream<S>) -> Result<JSONRPCMessage>
where
S: AsyncRead + AsyncWrite + Unpin,
{
loop {
let frame = websocket
.next()
.await
.ok_or_else(|| anyhow!("app-server closed the control socket"))??;
let Message::Text(payload) = frame else {
continue;
};
return serde_json::from_str::<JSONRPCMessage>(&payload)
.context("failed to parse app-server JSON-RPC message");
}
}
fn parse_version_from_user_agent(user_agent: &str) -> Result<String> {
let (_originator, rest) = user_agent
.split_once('/')
+211 -67
View File
@@ -1,6 +1,7 @@
mod backend;
mod client;
mod managed_install;
mod remote_control_client;
mod settings;
mod update_loop;
@@ -13,6 +14,7 @@ use anyhow::Result;
use anyhow::anyhow;
pub use backend::BackendKind;
use backend::BackendPaths;
use codex_app_server_protocol::RemoteControlConnectionStatus;
use codex_app_server_transport::app_server_control_socket_path;
use codex_utils_home_dir::find_codex_home;
use managed_install::managed_codex_bin;
@@ -58,6 +60,8 @@ pub struct LifecycleOutput {
pub backend: Option<BackendKind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
pub managed_codex_path: PathBuf,
pub managed_codex_version: Option<String>,
pub socket_path: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub cli_version: Option<String>,
@@ -84,6 +88,7 @@ pub struct BootstrapOutput {
pub auto_update_enabled: bool,
pub remote_control_enabled: bool,
pub managed_codex_path: PathBuf,
pub managed_codex_version: Option<String>,
pub socket_path: PathBuf,
pub cli_version: String,
pub app_server_version: String,
@@ -96,6 +101,20 @@ pub enum RemoteControlStartOutput {
Start(LifecycleOutput),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoteControlReadyStatus {
pub status: RemoteControlConnectionStatus,
pub server_name: String,
pub environment_id: Option<String>,
pub timed_out: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoteControlReadyOutput {
pub daemon: RemoteControlStartOutput,
pub remote_control: RemoteControlReadyStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteControlMode {
Enabled,
@@ -179,6 +198,27 @@ pub async fn ensure_remote_control_started() -> Result<RemoteControlStartOutput>
.await
}
pub async fn ensure_remote_control_ready() -> Result<RemoteControlReadyOutput> {
ensure_supported_platform()?;
Daemon::from_environment()?
.ensure_remote_control_ready()
.await
}
pub async fn enable_remote_control_on_socket(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<RemoteControlReadyStatus> {
ensure_supported_platform()?;
remote_control_client::enable_remote_control_with_connect_retry(
socket_path,
connect_timeout,
connect_retry_delay,
)
.await
}
pub async fn set_remote_control(mode: RemoteControlMode) -> Result<RemoteControlOutput> {
ensure_supported_platform()?;
Daemon::from_environment()?.set_remote_control(mode).await
@@ -248,33 +288,39 @@ impl Daemon {
async fn start(&self) -> Result<LifecycleOutput> {
let settings = self.load_settings().await?;
if let Ok(info) = client::probe(&self.socket_path).await {
return Ok(self.output(
LifecycleStatus::AlreadyRunning,
self.running_backend(&settings).await?,
/*pid*/ None,
Some(info.app_server_version),
));
return Ok(self
.output(
LifecycleStatus::AlreadyRunning,
self.running_backend(&settings).await?,
/*pid*/ None,
Some(info.app_server_version),
)
.await);
}
if self.running_backend_instance(&settings).await?.is_some() {
let info = self.wait_until_ready().await?;
return Ok(self.output(
LifecycleStatus::AlreadyRunning,
Some(BackendKind::Pid),
/*pid*/ None,
Some(info.app_server_version),
));
return Ok(self
.output(
LifecycleStatus::AlreadyRunning,
Some(BackendKind::Pid),
/*pid*/ None,
Some(info.app_server_version),
)
.await);
}
self.ensure_managed_codex_bin()?;
let pid = self.start_managed_backend(&settings).await?;
let info = self.wait_until_ready().await?;
Ok(self.output(
LifecycleStatus::Started,
Some(BackendKind::Pid),
pid,
Some(info.app_server_version),
))
Ok(self
.output(
LifecycleStatus::Started,
Some(BackendKind::Pid),
pid,
Some(info.app_server_version),
)
.await)
}
async fn restart(&self) -> Result<LifecycleOutput> {
@@ -294,12 +340,14 @@ impl Daemon {
let pid = self.start_managed_backend(&settings).await?;
let info = self.wait_until_ready().await?;
Ok(self.output(
LifecycleStatus::Restarted,
Some(BackendKind::Pid),
pid,
Some(info.app_server_version),
))
Ok(self
.output(
LifecycleStatus::Restarted,
Some(BackendKind::Pid),
pid,
Some(info.app_server_version),
)
.await)
}
#[cfg(unix)]
@@ -352,12 +400,14 @@ impl Daemon {
let settings = self.load_settings().await?;
if let Some(backend) = self.running_backend_instance(&settings).await? {
backend.stop().await?;
return Ok(self.output(
LifecycleStatus::Stopped,
Some(BackendKind::Pid),
/*pid*/ None,
/*app_server_version*/ None,
));
return Ok(self
.output(
LifecycleStatus::Stopped,
Some(BackendKind::Pid),
/*pid*/ None,
/*app_server_version*/ None,
)
.await);
}
if client::probe(&self.socket_path).await.is_ok() {
@@ -366,23 +416,27 @@ impl Daemon {
));
}
Ok(self.output(
LifecycleStatus::NotRunning,
/*backend*/ None,
/*pid*/ None,
/*app_server_version*/ None,
))
Ok(self
.output(
LifecycleStatus::NotRunning,
/*backend*/ None,
/*pid*/ None,
/*app_server_version*/ None,
)
.await)
}
async fn version(&self) -> Result<LifecycleOutput> {
let settings = self.load_settings().await?;
let info = client::probe(&self.socket_path).await?;
Ok(self.output(
LifecycleStatus::Running,
self.running_backend(&settings).await?,
/*pid*/ None,
Some(info.app_server_version),
))
Ok(self
.output(
LifecycleStatus::Running,
self.running_backend(&settings).await?,
/*pid*/ None,
Some(info.app_server_version),
)
.await)
}
async fn wait_until_ready(&self) -> Result<client::ProbeInfo> {
@@ -395,17 +449,34 @@ impl Daemon {
sleep(START_POLL_INTERVAL).await;
}
Err(err) => {
return Err(err).with_context(|| {
format!(
"app server did not become ready on {}",
self.socket_path.display()
)
});
let context = self.app_server_not_ready_context().await;
return Err(err).context(context);
}
}
}
}
async fn app_server_not_ready_context(&self) -> String {
let mut context = format!(
"app server did not become ready on {}",
self.socket_path.display()
);
self.append_daemon_app_server_context(&mut context).await;
backend::append_stderr_log_tail_context(&self.pid_file, &mut context).await;
context
}
async fn append_daemon_app_server_context(&self, context: &mut String) {
let managed_codex_version = self
.managed_codex_version_best_effort()
.await
.unwrap_or_else(|| "unknown".to_string());
context.push_str(&format!(
"\n\nDaemon used app-server:\n path: {}\n version: {managed_codex_version}",
self.managed_codex_bin.display()
));
}
async fn bootstrap(&self, options: BootstrapOptions) -> Result<BootstrapOutput> {
let _operation_lock = self.acquire_operation_lock().await?;
self.bootstrap_locked(options).await
@@ -430,6 +501,16 @@ impl Daemon {
Ok(RemoteControlStartOutput::Bootstrap(output))
}
async fn ensure_remote_control_ready(&self) -> Result<RemoteControlReadyOutput> {
let daemon = self.ensure_remote_control_started().await?;
let remote_control =
remote_control_client::enable_remote_control(&self.socket_path).await?;
Ok(RemoteControlReadyOutput {
daemon,
remote_control,
})
}
async fn set_remote_control(&self, mode: RemoteControlMode) -> Result<RemoteControlOutput> {
let _operation_lock = self.acquire_operation_lock().await?;
self.set_remote_control_locked(mode).await
@@ -512,12 +593,14 @@ impl Daemon {
updater.start().await?;
let info = self.wait_until_ready().await?;
let managed_codex_version = self.managed_codex_version_best_effort().await;
Ok(BootstrapOutput {
status: BootstrapStatus::Bootstrapped,
backend: BackendKind::Pid,
auto_update_enabled: true,
remote_control_enabled: settings.remote_control_enabled,
managed_codex_path: self.managed_codex_bin.clone(),
managed_codex_version,
socket_path: self.socket_path.clone(),
cli_version: env!("CARGO_PKG_VERSION").to_string(),
app_server_version: info.app_server_version,
@@ -567,12 +650,26 @@ impl Daemon {
return Ok(());
}
let managed_codex_path = self.managed_codex_bin.display();
Err(anyhow!(
"managed standalone Codex install not found at {}; install Codex first",
self.managed_codex_bin.display()
"managed standalone Codex install not found at {managed_codex_path}\n\n\
This command requires the standalone install managed by the Codex installer, because \
the daemon starts and updates app-server from that fixed path.\n\n\
Install it with:\n curl -fsSL https://chatgpt.com/codex/install.sh | sh\n\n\
Then rerun the command you just tried."
))
}
#[cfg(unix)]
async fn managed_codex_version_best_effort(&self) -> Option<String> {
managed_codex_version(&self.managed_codex_bin).await.ok()
}
#[cfg(not(unix))]
async fn managed_codex_version_best_effort(&self) -> Option<String> {
None
}
fn backend_paths(&self, settings: &DaemonSettings) -> BackendPaths {
self.backend_paths_with_bin(settings, &self.managed_codex_bin)
}
@@ -632,17 +729,20 @@ impl Daemon {
})
}
fn output(
async fn output(
&self,
status: LifecycleStatus,
backend: Option<BackendKind>,
pid: Option<u32>,
app_server_version: Option<String>,
) -> LifecycleOutput {
let managed_codex_version = self.managed_codex_version_best_effort().await;
LifecycleOutput {
status,
backend,
pid,
managed_codex_path: self.managed_codex_bin.clone(),
managed_codex_version,
socket_path: self.socket_path.clone(),
cli_version: Some(env!("CARGO_PKG_VERSION").to_string()),
app_server_version,
@@ -731,10 +831,12 @@ fn try_lock_file(_file: &tokio::fs::File) -> Result<bool> {
#[cfg(all(test, unix))]
mod tests {
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use super::BackendKind;
use super::BootstrapOutput;
use super::BootstrapStatus;
use super::Daemon;
use super::LifecycleOutput;
use super::LifecycleStatus;
use super::RemoteControlStartOutput;
@@ -747,22 +849,6 @@ mod tests {
use super::should_reexec_updater;
use crate::client::ProbeInfo;
#[test]
fn lifecycle_status_uses_camel_case_json() {
assert_eq!(
serde_json::to_string(&LifecycleStatus::AlreadyRunning).expect("serialize"),
"\"alreadyRunning\""
);
}
#[test]
fn bootstrap_status_uses_camel_case_json() {
assert_eq!(
serde_json::to_string(&BootstrapStatus::Bootstrapped).expect("serialize"),
"\"bootstrapped\""
);
}
#[test]
fn remote_control_status_uses_camel_case_json() {
assert_eq!(
@@ -843,12 +929,26 @@ mod tests {
status: LifecycleStatus::AlreadyRunning,
backend: Some(BackendKind::Pid),
pid: None,
managed_codex_path: "codex".into(),
managed_codex_version: Some("1.2.3".to_string()),
socket_path: "codex.sock".into(),
cli_version: Some("1.2.3".to_string()),
app_server_version: Some("1.2.4".to_string()),
};
let output = RemoteControlStartOutput::Start(lifecycle_output.clone());
assert_eq!(
serde_json::to_value(&lifecycle_output).expect("serialize"),
serde_json::json!({
"status": "alreadyRunning",
"backend": "pid",
"managedCodexPath": "codex",
"managedCodexVersion": "1.2.3",
"socketPath": "codex.sock",
"cliVersion": "1.2.3",
"appServerVersion": "1.2.4",
})
);
assert_eq!(
serde_json::to_value(output).expect("serialize"),
serde_json::to_value(lifecycle_output).expect("serialize")
@@ -860,15 +960,59 @@ mod tests {
auto_update_enabled: true,
remote_control_enabled: true,
managed_codex_path: "codex".into(),
managed_codex_version: Some("1.2.3".to_string()),
socket_path: "codex.sock".into(),
cli_version: "1.2.3".to_string(),
app_server_version: "1.2.4".to_string(),
};
let output = RemoteControlStartOutput::Bootstrap(bootstrap_output.clone());
assert_eq!(
serde_json::to_value(&bootstrap_output).expect("serialize"),
serde_json::json!({
"status": "bootstrapped",
"backend": "pid",
"autoUpdateEnabled": true,
"remoteControlEnabled": true,
"managedCodexPath": "codex",
"managedCodexVersion": "1.2.3",
"socketPath": "codex.sock",
"cliVersion": "1.2.3",
"appServerVersion": "1.2.4",
})
);
assert_eq!(
serde_json::to_value(output).expect("serialize"),
serde_json::to_value(bootstrap_output).expect("serialize")
);
}
#[tokio::test]
async fn not_ready_context_reports_daemon_app_server_before_stderr() {
let temp_dir = TempDir::new().expect("temp dir");
let daemon = Daemon {
socket_path: temp_dir.path().join("app-server-control.sock"),
pid_file: temp_dir.path().join("app-server.pid"),
update_pid_file: temp_dir.path().join("app-server-updater.pid"),
operation_lock_file: temp_dir.path().join("daemon.lock"),
settings_file: temp_dir.path().join("settings.json"),
managed_codex_bin: temp_dir.path().join("missing-codex"),
};
let stderr_log = daemon.pid_file.with_extension("stderr.log");
tokio::fs::write(&stderr_log, "unexpected argument")
.await
.expect("write stderr log");
assert_eq!(
daemon.app_server_not_ready_context().await,
format!(
"app server did not become ready on {}\n\n\
Daemon used app-server:\n path: {}\n version: unknown\n\n\
Managed app-server stderr ({}):\n unexpected argument",
daemon.socket_path.display(),
daemon.managed_codex_bin.display(),
stderr_log.display()
)
);
}
}
@@ -0,0 +1,459 @@
use std::path::Path;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::RemoteControlConnectionStatus;
use codex_app_server_protocol::RemoteControlEnableResponse;
use codex_app_server_protocol::RemoteControlStatusChangedNotification;
use codex_app_server_protocol::RequestId;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::time::Instant;
use tokio::time::sleep;
use tokio::time::timeout;
use tokio_tungstenite::WebSocketStream;
use crate::RemoteControlReadyStatus;
use crate::client;
const REMOTE_CONTROL_READY_TIMEOUT: Duration = Duration::from_secs(10);
const REMOTE_CONTROL_ENABLE_REQUEST_ID: RequestId = RequestId::Integer(2);
pub(crate) async fn enable_remote_control(socket_path: &Path) -> Result<RemoteControlReadyStatus> {
let mut websocket = client::connect(socket_path).await?;
enable_remote_control_with_timeout(&mut websocket, REMOTE_CONTROL_READY_TIMEOUT).await
}
pub(crate) async fn enable_remote_control_with_connect_retry(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<RemoteControlReadyStatus> {
let mut websocket =
connect_with_retry(socket_path, connect_timeout, connect_retry_delay).await?;
enable_remote_control_with_timeout(&mut websocket, REMOTE_CONTROL_READY_TIMEOUT).await
}
async fn enable_remote_control_with_timeout<S>(
websocket: &mut WebSocketStream<S>,
ready_timeout: Duration,
) -> Result<RemoteControlReadyStatus>
where
S: AsyncRead + AsyncWrite + Unpin,
{
client::initialize(websocket, /*experimental_api*/ true).await?;
let initialized = JSONRPCMessage::Notification(JSONRPCNotification {
method: "initialized".to_string(),
params: None,
});
client::send_message(websocket, &initialized)
.await
.context("failed to send initialized notification")?;
let enable = JSONRPCMessage::Request(JSONRPCRequest {
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
method: "remoteControl/enable".to_string(),
params: None,
trace: None,
});
client::send_message(websocket, &enable)
.await
.context("failed to send remoteControl/enable request")?;
let mut latest = read_enable_response(websocket).await?;
if latest.status == RemoteControlConnectionStatus::Connecting {
latest = wait_for_remote_control_status(websocket, latest, ready_timeout).await?;
}
websocket.close(None).await.ok();
Ok(latest)
}
async fn connect_with_retry(
socket_path: &Path,
connect_timeout: Duration,
connect_retry_delay: Duration,
) -> Result<WebSocketStream<codex_uds::UnixStream>> {
let deadline = Instant::now() + connect_timeout;
loop {
match client::connect(socket_path).await {
Ok(websocket) => return Ok(websocket),
Err(_) if Instant::now() < deadline => {
sleep(connect_retry_delay).await;
}
Err(error) => {
return Err(error).with_context(|| {
format!(
"app server did not become ready on {}",
socket_path.display()
)
});
}
}
}
}
async fn read_enable_response<S>(
websocket: &mut WebSocketStream<S>,
) -> Result<RemoteControlReadyStatus>
where
S: AsyncRead + AsyncWrite + Unpin,
{
loop {
let message = timeout(
client::CONTROL_SOCKET_RESPONSE_TIMEOUT,
client::read_message(websocket),
)
.await
.context("timed out waiting for remoteControl/enable response")??;
match message {
JSONRPCMessage::Response(response)
if response.id == REMOTE_CONTROL_ENABLE_REQUEST_ID =>
{
let response =
serde_json::from_value::<RemoteControlEnableResponse>(response.result)
.context("failed to parse remoteControl/enable response")?;
return Ok(RemoteControlReadyStatus::from(response));
}
JSONRPCMessage::Error(err) if err.id == REMOTE_CONTROL_ENABLE_REQUEST_ID => {
return Err(anyhow!(
"remoteControl/enable failed: {}",
err.error.message
));
}
JSONRPCMessage::Notification(notification)
if remote_control_status_notification(&notification).is_some() =>
{
continue;
}
_ => {}
}
}
}
async fn wait_for_remote_control_status<S>(
websocket: &mut WebSocketStream<S>,
mut latest: RemoteControlReadyStatus,
ready_timeout: Duration,
) -> Result<RemoteControlReadyStatus>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let deadline = tokio::time::Instant::now() + ready_timeout;
while tokio::time::Instant::now() < deadline {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
let message = match timeout(remaining, client::read_message(websocket)).await {
Ok(Ok(message)) => message,
Ok(Err(err)) => return Err(err),
Err(_) => {
latest.timed_out = true;
return Ok(latest);
}
};
let JSONRPCMessage::Notification(notification) = message else {
continue;
};
let Some(status) = remote_control_status_notification(&notification) else {
continue;
};
latest = RemoteControlReadyStatus::from(status);
if latest.status != RemoteControlConnectionStatus::Connecting {
return Ok(latest);
}
}
latest.timed_out = true;
Ok(latest)
}
fn remote_control_status_notification(
notification: &JSONRPCNotification,
) -> Option<RemoteControlStatusChangedNotification> {
if notification.method != "remoteControl/status/changed" {
return None;
}
let params = notification.params.clone()?;
serde_json::from_value(params).ok()
}
impl From<RemoteControlEnableResponse> for RemoteControlReadyStatus {
fn from(response: RemoteControlEnableResponse) -> Self {
let RemoteControlEnableResponse {
status,
server_name,
installation_id: _,
environment_id,
} = response;
Self {
status,
server_name,
environment_id,
timed_out: false,
}
}
}
impl From<RemoteControlStatusChangedNotification> for RemoteControlReadyStatus {
fn from(notification: RemoteControlStatusChangedNotification) -> Self {
let RemoteControlStatusChangedNotification {
status,
server_name,
installation_id: _,
environment_id,
} = notification;
Self {
status,
server_name,
environment_id,
timed_out: false,
}
}
}
#[cfg(all(test, unix))]
mod tests {
use anyhow::Result;
use codex_app_server_protocol::JSONRPCResponse;
use codex_uds::UnixListener;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio_tungstenite::accept_async;
use super::*;
const INITIALIZE_REQUEST_ID: RequestId = RequestId::Integer(1);
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
const TEST_SERVER_NAME: &str = "owen-mbp";
const TEST_CODEX_HOME: &str = "/tmp/codex-home";
#[tokio::test]
async fn enable_remote_control_uses_connected_enable_response_without_later_notification()
-> Result<()> {
let status = run_enable_remote_control_scenario(EnableScenario {
initial_notification: Some(remote_control_status(
RemoteControlConnectionStatus::Connected,
Some("env_test"),
)),
enable_response: remote_control_status(
RemoteControlConnectionStatus::Connected,
Some("env_test"),
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
})
.await?;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Connected,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: Some("env_test".to_string()),
timed_out: false,
}
);
Ok(())
}
#[tokio::test]
async fn enable_remote_control_waits_for_connected_notification() -> Result<()> {
let status = run_enable_remote_control_scenario(EnableScenario {
initial_notification: None,
enable_response: remote_control_status(
RemoteControlConnectionStatus::Connecting,
/*environment_id*/ None,
),
after_enable_notification: Some(remote_control_status(
RemoteControlConnectionStatus::Connected,
Some("env_test"),
)),
ready_timeout: Duration::from_secs(1),
})
.await?;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Connected,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: Some("env_test".to_string()),
timed_out: false,
}
);
Ok(())
}
#[tokio::test]
async fn enable_remote_control_reports_connecting_after_timeout() -> Result<()> {
let status = run_enable_remote_control_scenario(EnableScenario {
initial_notification: None,
enable_response: remote_control_status(
RemoteControlConnectionStatus::Connecting,
/*environment_id*/ None,
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
})
.await?;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Connecting,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: None,
timed_out: true,
}
);
Ok(())
}
#[tokio::test]
async fn enable_remote_control_returns_errored_enable_response() -> Result<()> {
let status = run_enable_remote_control_scenario(EnableScenario {
initial_notification: None,
enable_response: remote_control_status(
RemoteControlConnectionStatus::Errored,
/*environment_id*/ None,
),
after_enable_notification: None,
ready_timeout: Duration::from_millis(20),
})
.await?;
assert_eq!(
status,
RemoteControlReadyStatus {
status: RemoteControlConnectionStatus::Errored,
server_name: TEST_SERVER_NAME.to_string(),
environment_id: None,
timed_out: false,
}
);
Ok(())
}
struct EnableScenario {
initial_notification: Option<RemoteControlStatusChangedNotification>,
enable_response: RemoteControlStatusChangedNotification,
after_enable_notification: Option<RemoteControlStatusChangedNotification>,
ready_timeout: Duration,
}
async fn run_enable_remote_control_scenario(
scenario: EnableScenario,
) -> Result<RemoteControlReadyStatus> {
let dir = TempDir::new()?;
let socket_path = dir.path().join("app-server.sock");
let listener = UnixListener::bind(&socket_path).await?;
let ready_timeout = scenario.ready_timeout;
let server_task = tokio::spawn(serve_enable_remote_control_scenario(listener, scenario));
let mut websocket = client::connect(&socket_path).await?;
let status = enable_remote_control_with_timeout(&mut websocket, ready_timeout).await?;
server_task.await??;
Ok(status)
}
async fn serve_enable_remote_control_scenario(
mut listener: UnixListener,
scenario: EnableScenario,
) -> Result<()> {
let stream = listener.accept().await?;
let mut websocket = accept_async(stream).await?;
let initialize = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(initialize) = initialize else {
panic!("expected initialize request");
};
assert_eq!(initialize.id, INITIALIZE_REQUEST_ID);
assert_eq!(initialize.method, "initialize");
let Some(initialize_params) = initialize.params else {
panic!("expected initialize params");
};
assert_eq!(
initialize_params["capabilities"]["experimentalApi"],
serde_json::Value::Bool(true)
);
client::send_message(
&mut websocket,
&JSONRPCMessage::Response(JSONRPCResponse {
id: INITIALIZE_REQUEST_ID,
result: serde_json::json!({
"userAgent": "codex_app_server/1.2.3",
"codexHome": TEST_CODEX_HOME,
"platformFamily": "unix",
"platformOs": "macos",
}),
}),
)
.await?;
let initialized = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Notification(initialized) = initialized else {
panic!("expected initialized notification");
};
assert_eq!(initialized.method, "initialized");
if let Some(status) = scenario.initial_notification {
send_remote_control_status(&mut websocket, status).await?;
}
let enable = client::read_message(&mut websocket).await?;
let JSONRPCMessage::Request(enable) = enable else {
panic!("expected remoteControl/enable request");
};
assert_eq!(enable.id, REMOTE_CONTROL_ENABLE_REQUEST_ID);
assert_eq!(enable.method, "remoteControl/enable");
client::send_message(
&mut websocket,
&JSONRPCMessage::Response(JSONRPCResponse {
id: REMOTE_CONTROL_ENABLE_REQUEST_ID,
result: serde_json::to_value(RemoteControlEnableResponse::from(
scenario.enable_response,
))?,
}),
)
.await?;
if let Some(status) = scenario.after_enable_notification {
send_remote_control_status(&mut websocket, status).await?;
} else {
tokio::time::sleep(Duration::from_millis(50)).await;
}
Ok(())
}
async fn send_remote_control_status<S>(
websocket: &mut WebSocketStream<S>,
status: RemoteControlStatusChangedNotification,
) -> Result<()>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
client::send_message(
websocket,
&JSONRPCMessage::Notification(JSONRPCNotification {
method: "remoteControl/status/changed".to_string(),
params: Some(serde_json::to_value(status)?),
}),
)
.await
}
fn remote_control_status(
status: RemoteControlConnectionStatus,
environment_id: Option<&str>,
) -> RemoteControlStatusChangedNotification {
RemoteControlStatusChangedNotification {
status,
server_name: TEST_SERVER_NAME.to_string(),
installation_id: TEST_INSTALLATION_ID.to_string(),
environment_id: environment_id.map(str::to_string),
}
}
}
+98 -388
View File
@@ -591,6 +591,13 @@
"integer",
"null"
]
},
"threadId": {
"description": "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"type": [
"string",
"null"
]
}
},
"type": "object"
@@ -719,202 +726,6 @@
],
"type": "object"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"FsCopyParams": {
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
@@ -1235,8 +1046,6 @@
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
@@ -1732,194 +1541,6 @@
],
"type": "string"
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -1955,6 +1576,31 @@
],
"type": "object"
},
"PluginInstalledParams": {
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
},
"installSuggestionPluginNames": {
"description": "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"type": "object"
},
"PluginListMarketplaceKind": {
"enum": [
"local",
@@ -2788,6 +2434,22 @@
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"compaction_trigger"
],
"title": "CompactionTriggerResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "CompactionTriggerResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -3345,7 +3007,7 @@
"type": "object"
},
"ThreadForkParams": {
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -3449,6 +3111,8 @@
"enum": [
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -3749,7 +3413,7 @@
]
},
"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.",
"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\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -4293,6 +3957,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -4313,6 +3988,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -4966,6 +4652,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/installed"
],
"title": "Plugin/installedRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginInstalledParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/installedRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -277,7 +277,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -1085,7 +1085,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -1740,6 +1740,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -1947,6 +1948,13 @@
],
"type": "string"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"ItemCompletedNotification": {
"properties": {
"completedAtMs": {
@@ -2763,12 +2771,16 @@
"installationId": {
"type": "string"
},
"serverName": {
"type": "string"
},
"status": {
"$ref": "#/definitions/RemoteControlConnectionStatus"
}
},
"required": [
"installationId",
"serverName",
"status"
],
"type": "object"
@@ -3261,6 +3273,8 @@
"enum": [
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -4604,6 +4618,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -4624,6 +4649,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
+1 -1
View File
@@ -631,7 +631,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -757,6 +757,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"plugin/installed"
],
"title": "Plugin/installedRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/PluginInstalledParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/installedRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -5607,14 +5631,6 @@
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/v2/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
@@ -5622,31 +5638,6 @@
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AddCreditsNudgeCreditType": {
"enum": [
"credits",
@@ -6323,6 +6314,25 @@
}
]
},
"AutoCompactTokenLimitScope": {
"description": "Selects which part of the active context is charged against `model_auto_compact_token_limit`.",
"oneOf": [
{
"description": "Count the full active context against the limit.",
"enum": [
"total"
],
"type": "string"
},
{
"description": "Count sampled output and later growth after the carried window prefix.",
"enum": [
"body_after_prefix"
],
"type": "string"
}
]
},
"AutoReviewDecisionSource": {
"description": "[UNSTABLE] Source that produced a terminal approval auto-review decision.",
"enum": [
@@ -7119,6 +7129,13 @@
"null"
]
},
"desktop": {
"additionalProperties": true,
"type": [
"object",
"null"
]
},
"developer_instructions": {
"type": [
"string",
@@ -7126,9 +7143,13 @@
]
},
"forced_chatgpt_workspace_id": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/v2/ForcedChatgptWorkspaceIds"
},
{
"type": "null"
}
]
},
"forced_login_method": {
@@ -7160,6 +7181,16 @@
"null"
]
},
"model_auto_compact_token_limit_scope": {
"anyOf": [
{
"$ref": "#/definitions/v2/AutoCompactTokenLimitScope"
},
{
"type": "null"
}
]
},
"model_context_window": {
"format": "int64",
"type": [
@@ -7423,6 +7454,13 @@
],
"description": "This is the path to the user's config.toml file, though it is not guaranteed to exist."
},
"profile": {
"description": "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"user"
@@ -8185,6 +8223,13 @@
"integer",
"null"
]
},
"threadId": {
"description": "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"type": [
"string",
"null"
]
}
},
"title": "ExperimentalFeatureListParams",
@@ -8479,7 +8524,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -8690,6 +8735,20 @@
],
"type": "object"
},
"ForcedChatgptWorkspaceIds": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
],
"description": "Backward-compatible API shape for ChatGPT workspace login restrictions."
},
"ForcedLoginMethod": {
"enum": [
"chatgpt",
@@ -9565,6 +9624,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -9936,8 +9996,6 @@
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
@@ -10472,6 +10530,12 @@
},
"type": "array"
},
"SubagentStart": {
"items": {
"$ref": "#/definitions/v2/ConfiguredHookMatcherGroup"
},
"type": "array"
},
"UserPromptSubmit": {
"items": {
"$ref": "#/definitions/v2/ConfiguredHookMatcherGroup"
@@ -10499,6 +10563,7 @@
"PreToolUse",
"SessionStart",
"Stop",
"SubagentStart",
"UserPromptSubmit"
],
"type": "object"
@@ -11637,194 +11702,6 @@
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/v2/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/v2/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/v2/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/v2/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/v2/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -12031,6 +11908,56 @@
"title": "PluginInstallResponse",
"type": "object"
},
"PluginInstalledParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces.",
"items": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
},
"installSuggestionPluginNames": {
"description": "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginInstalledParams",
"type": "object"
},
"PluginInstalledResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplaceLoadErrors": {
"default": [],
"items": {
"$ref": "#/definitions/v2/MarketplaceLoadErrorInfo"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/v2/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginInstalledResponse",
"type": "object"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -13521,12 +13448,16 @@
"installationId": {
"type": "string"
},
"serverName": {
"type": "string"
},
"status": {
"$ref": "#/definitions/v2/RemoteControlConnectionStatus"
}
},
"required": [
"installationId",
"serverName",
"status"
],
"title": "RemoteControlStatusChangedNotification",
@@ -14140,6 +14071,22 @@
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"compaction_trigger"
],
"title": "CompactionTriggerResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "CompactionTriggerResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -15438,7 +15385,7 @@
},
"ThreadForkParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -15586,7 +15533,7 @@
"$ref": "#/definitions/v2/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -15673,6 +15620,8 @@
"enum": [
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -16934,7 +16883,7 @@
},
"ThreadResumeParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"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.",
"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\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -17078,7 +17027,7 @@
"$ref": "#/definitions/v2/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -17386,7 +17335,7 @@
"$ref": "#/definitions/v2/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -17708,12 +17657,6 @@
},
"ToolsV2": {
"properties": {
"view_image": {
"type": [
"boolean",
"null"
]
},
"web_search": {
"anyOf": [
{
@@ -18195,6 +18138,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/v2/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -18215,6 +18169,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/v2/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -143,14 +143,6 @@
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
@@ -158,31 +150,6 @@
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AddCreditsNudgeCreditType": {
"enum": [
"credits",
@@ -859,6 +826,25 @@
}
]
},
"AutoCompactTokenLimitScope": {
"description": "Selects which part of the active context is charged against `model_auto_compact_token_limit`.",
"oneOf": [
{
"description": "Count the full active context against the limit.",
"enum": [
"total"
],
"type": "string"
},
{
"description": "Count sampled output and later growth after the carried window prefix.",
"enum": [
"body_after_prefix"
],
"type": "string"
}
]
},
"AutoReviewDecisionSource": {
"description": "[UNSTABLE] Source that produced a terminal approval auto-review decision.",
"enum": [
@@ -1497,6 +1483,30 @@
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/installed"
],
"title": "Plugin/installedRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginInstalledParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/installedRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -3508,6 +3518,13 @@
"null"
]
},
"desktop": {
"additionalProperties": true,
"type": [
"object",
"null"
]
},
"developer_instructions": {
"type": [
"string",
@@ -3515,9 +3532,13 @@
]
},
"forced_chatgpt_workspace_id": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/ForcedChatgptWorkspaceIds"
},
{
"type": "null"
}
]
},
"forced_login_method": {
@@ -3549,6 +3570,16 @@
"null"
]
},
"model_auto_compact_token_limit_scope": {
"anyOf": [
{
"$ref": "#/definitions/AutoCompactTokenLimitScope"
},
{
"type": "null"
}
]
},
"model_context_window": {
"format": "int64",
"type": [
@@ -3812,6 +3843,13 @@
],
"description": "This is the path to the user's config.toml file, though it is not guaranteed to exist."
},
"profile": {
"description": "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"user"
@@ -4574,6 +4612,13 @@
"integer",
"null"
]
},
"threadId": {
"description": "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"type": [
"string",
"null"
]
}
},
"title": "ExperimentalFeatureListParams",
@@ -4868,7 +4913,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -5079,6 +5124,20 @@
],
"type": "object"
},
"ForcedChatgptWorkspaceIds": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
],
"description": "Backward-compatible API shape for ChatGPT workspace login restrictions."
},
"ForcedLoginMethod": {
"enum": [
"chatgpt",
@@ -6065,6 +6124,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -6436,8 +6496,6 @@
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
@@ -7021,6 +7079,12 @@
},
"type": "array"
},
"SubagentStart": {
"items": {
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
},
"type": "array"
},
"UserPromptSubmit": {
"items": {
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
@@ -7048,6 +7112,7 @@
"PreToolUse",
"SessionStart",
"Stop",
"SubagentStart",
"UserPromptSubmit"
],
"type": "object"
@@ -8186,194 +8251,6 @@
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -8580,6 +8457,56 @@
"title": "PluginInstallResponse",
"type": "object"
},
"PluginInstalledParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
},
"installSuggestionPluginNames": {
"description": "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginInstalledParams",
"type": "object"
},
"PluginInstalledResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplaceLoadErrors": {
"default": [],
"items": {
"$ref": "#/definitions/MarketplaceLoadErrorInfo"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginInstalledResponse",
"type": "object"
},
"PluginInterface": {
"properties": {
"brandColor": {
@@ -10070,12 +9997,16 @@
"installationId": {
"type": "string"
},
"serverName": {
"type": "string"
},
"status": {
"$ref": "#/definitions/RemoteControlConnectionStatus"
}
},
"required": [
"installationId",
"serverName",
"status"
],
"title": "RemoteControlStatusChangedNotification",
@@ -10689,6 +10620,22 @@
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"compaction_trigger"
],
"title": "CompactionTriggerResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "CompactionTriggerResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -13262,7 +13209,7 @@
},
"ThreadForkParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -13410,7 +13357,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -13497,6 +13444,8 @@
"enum": [
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -14758,7 +14707,7 @@
},
"ThreadResumeParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"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.",
"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\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -14902,7 +14851,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -15210,7 +15159,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -15532,12 +15481,6 @@
},
"ToolsV2": {
"properties": {
"view_image": {
"type": [
"boolean",
"null"
]
},
"web_search": {
"anyOf": [
{
@@ -16019,6 +15962,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -16039,6 +15993,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -27,202 +27,6 @@
],
"type": "object"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
"restricted",
@@ -230,135 +34,6 @@
],
"type": "string"
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"SandboxPolicy": {
"oneOf": [
{
@@ -188,6 +188,25 @@
}
]
},
"AutoCompactTokenLimitScope": {
"description": "Selects which part of the active context is charged against `model_auto_compact_token_limit`.",
"oneOf": [
{
"description": "Count the full active context against the limit.",
"enum": [
"total"
],
"type": "string"
},
{
"description": "Count sampled output and later growth after the carried window prefix.",
"enum": [
"body_after_prefix"
],
"type": "string"
}
]
},
"Config": {
"additionalProperties": true,
"properties": {
@@ -228,6 +247,13 @@
"null"
]
},
"desktop": {
"additionalProperties": true,
"type": [
"object",
"null"
]
},
"developer_instructions": {
"type": [
"string",
@@ -235,9 +261,13 @@
]
},
"forced_chatgpt_workspace_id": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/ForcedChatgptWorkspaceIds"
},
{
"type": "null"
}
]
},
"forced_login_method": {
@@ -269,6 +299,16 @@
"null"
]
},
"model_auto_compact_token_limit_scope": {
"anyOf": [
{
"$ref": "#/definitions/AutoCompactTokenLimitScope"
},
{
"type": "null"
}
]
},
"model_context_window": {
"format": "int64",
"type": [
@@ -482,6 +522,13 @@
],
"description": "This is the path to the user's config.toml file, though it is not guaranteed to exist."
},
"profile": {
"description": "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"user"
@@ -574,6 +621,20 @@
}
]
},
"ForcedChatgptWorkspaceIds": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
],
"description": "Backward-compatible API shape for ChatGPT workspace login restrictions."
},
"ForcedLoginMethod": {
"enum": [
"chatgpt",
@@ -748,12 +809,6 @@
},
"ToolsV2": {
"properties": {
"view_image": {
"type": [
"boolean",
"null"
]
},
"web_search": {
"anyOf": [
{
@@ -261,6 +261,12 @@
},
"type": "array"
},
"SubagentStart": {
"items": {
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
},
"type": "array"
},
"UserPromptSubmit": {
"items": {
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
@@ -288,6 +294,7 @@
"PreToolUse",
"SessionStart",
"Stop",
"SubagentStart",
"UserPromptSubmit"
],
"type": "object"
@@ -84,6 +84,13 @@
],
"description": "This is the path to the user's config.toml file, though it is not guaranteed to exist."
},
"profile": {
"description": "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"user"
@@ -16,6 +16,13 @@
"integer",
"null"
]
},
"threadId": {
"description": "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"type": [
"string",
"null"
]
}
},
"title": "ExperimentalFeatureListParams",
@@ -14,6 +14,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -14,6 +14,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -29,6 +29,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -285,6 +285,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1179,6 +1186,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1199,6 +1217,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -69,7 +69,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -62,7 +62,7 @@
"enum": [
"read",
"write",
"none"
"deny"
],
"type": "string"
},
@@ -285,6 +285,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1179,6 +1186,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1199,6 +1217,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -0,0 +1,33 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
}
},
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
},
"installSuggestionPluginNames": {
"description": "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginInstalledParams",
"type": "object"
}
@@ -0,0 +1,525 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"MarketplaceInterface": {
"properties": {
"displayName": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"MarketplaceLoadErrorInfo": {
"properties": {
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"message": {
"type": "string"
}
},
"required": [
"marketplacePath",
"message"
],
"type": "object"
},
"PluginAuthPolicy": {
"enum": [
"ON_INSTALL",
"ON_USE"
],
"type": "string"
},
"PluginAvailability": {
"oneOf": [
{
"enum": [
"DISABLED_BY_ADMIN"
],
"type": "string"
},
{
"description": "Plugin-service currently sends `\"ENABLED\"` for available remote plugins. Codex app-server exposes `\"AVAILABLE\"` in its API; the alias keeps decoding compatible with that upstream response.",
"enum": [
"AVAILABLE"
],
"type": "string"
}
]
},
"PluginInstallPolicy": {
"enum": [
"NOT_AVAILABLE",
"AVAILABLE",
"INSTALLED_BY_DEFAULT"
],
"type": "string"
},
"PluginInterface": {
"properties": {
"brandColor": {
"type": [
"string",
"null"
]
},
"capabilities": {
"items": {
"type": "string"
},
"type": "array"
},
"category": {
"type": [
"string",
"null"
]
},
"composerIcon": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
],
"description": "Local composer icon path, resolved from the installed plugin package."
},
"composerIconUrl": {
"description": "Remote composer icon URL from the plugin catalog.",
"type": [
"string",
"null"
]
},
"defaultPrompt": {
"description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"developerName": {
"type": [
"string",
"null"
]
},
"displayName": {
"type": [
"string",
"null"
]
},
"logo": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
],
"description": "Local logo path, resolved from the installed plugin package."
},
"logoUrl": {
"description": "Remote logo URL from the plugin catalog.",
"type": [
"string",
"null"
]
},
"longDescription": {
"type": [
"string",
"null"
]
},
"privacyPolicyUrl": {
"type": [
"string",
"null"
]
},
"screenshotUrls": {
"description": "Remote screenshot URLs from the plugin catalog.",
"items": {
"type": "string"
},
"type": "array"
},
"screenshots": {
"description": "Local screenshot paths, resolved from the installed plugin package.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"shortDescription": {
"type": [
"string",
"null"
]
},
"termsOfServiceUrl": {
"type": [
"string",
"null"
]
},
"websiteUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
"capabilities",
"screenshotUrls",
"screenshots"
],
"type": "object"
},
"PluginMarketplaceEntry": {
"properties": {
"interface": {
"anyOf": [
{
"$ref": "#/definitions/MarketplaceInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"path": {
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
],
"description": "Local marketplace file path when the marketplace is backed by a local file. Remote-only catalog marketplaces do not have a local path."
},
"plugins": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"plugins"
],
"type": "object"
},
"PluginShareContext": {
"properties": {
"creatorAccountUserId": {
"type": [
"string",
"null"
]
},
"creatorName": {
"type": [
"string",
"null"
]
},
"discoverability": {
"anyOf": [
{
"$ref": "#/definitions/PluginShareDiscoverability"
},
{
"type": "null"
}
]
},
"remotePluginId": {
"type": "string"
},
"remoteVersion": {
"default": null,
"description": "Version of the remote shared plugin release when available.",
"type": [
"string",
"null"
]
},
"sharePrincipals": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": [
"array",
"null"
]
},
"shareUrl": {
"type": [
"string",
"null"
]
}
},
"required": [
"remotePluginId"
],
"type": "object"
},
"PluginShareDiscoverability": {
"enum": [
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type": "string"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
},
"role": {
"$ref": "#/definitions/PluginSharePrincipalRole"
}
},
"required": [
"name",
"principalId",
"principalType",
"role"
],
"type": "object"
},
"PluginSharePrincipalRole": {
"enum": [
"reader",
"editor",
"owner"
],
"type": "string"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
},
{
"properties": {
"path": {
"type": [
"string",
"null"
]
},
"refName": {
"type": [
"string",
"null"
]
},
"sha": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"git"
],
"title": "GitPluginSourceType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "GitPluginSource",
"type": "object"
},
{
"description": "The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.",
"properties": {
"type": {
"enum": [
"remote"
],
"title": "RemotePluginSourceType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RemotePluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"authPolicy": {
"$ref": "#/definitions/PluginAuthPolicy"
},
"availability": {
"allOf": [
{
"$ref": "#/definitions/PluginAvailability"
}
],
"default": "AVAILABLE",
"description": "Availability state for installing and using the plugin."
},
"enabled": {
"type": "boolean"
},
"id": {
"type": "string"
},
"installPolicy": {
"$ref": "#/definitions/PluginInstallPolicy"
},
"installed": {
"type": "boolean"
},
"interface": {
"anyOf": [
{
"$ref": "#/definitions/PluginInterface"
},
{
"type": "null"
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"localVersion": {
"default": null,
"description": "Version of the locally materialized plugin package when available.",
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"remotePluginId": {
"description": "Backend remote plugin identifier when available.",
"type": [
"string",
"null"
]
},
"shareContext": {
"anyOf": [
{
"$ref": "#/definitions/PluginShareContext"
},
{
"type": "null"
}
],
"description": "Remote sharing context associated with this plugin when available."
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"authPolicy",
"enabled",
"id",
"installPolicy",
"installed",
"name",
"source"
],
"type": "object"
}
},
"properties": {
"marketplaceLoadErrors": {
"default": [],
"items": {
"$ref": "#/definitions/MarketplaceLoadErrorInfo"
},
"type": "array"
},
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginInstalledResponse",
"type": "object"
}
@@ -46,6 +46,7 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"stop"
],
"type": "string"
@@ -145,8 +145,6 @@
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
@@ -732,6 +730,22 @@
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"compaction_trigger"
],
"title": "CompactionTriggerResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "CompactionTriggerResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -22,12 +22,16 @@
"installationId": {
"type": "string"
},
"serverName": {
"type": "string"
},
"status": {
"$ref": "#/definitions/RemoteControlConnectionStatus"
}
},
"required": [
"installationId",
"serverName",
"status"
],
"title": "RemoteControlStatusChangedNotification",
@@ -422,6 +422,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1452,6 +1459,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1472,6 +1490,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -1,10 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [
@@ -64,65 +60,6 @@
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"SandboxMode": {
"enum": [
"read-only",
@@ -140,7 +77,7 @@
"type": "string"
}
},
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -18,14 +18,6 @@
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
@@ -33,31 +25,6 @@
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -503,202 +470,6 @@
],
"type": "string"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -756,6 +527,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -937,135 +715,6 @@
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -2370,6 +2019,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -2390,6 +2050,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -2605,7 +2276,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -51,6 +51,8 @@
"enum": [
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -1,10 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [
@@ -208,8 +204,6 @@
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
@@ -298,65 +292,6 @@
}
]
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -862,6 +797,22 @@
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"compaction_trigger"
],
"title": "CompactionTriggerResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "CompactionTriggerResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
@@ -1012,7 +963,7 @@
"type": "string"
}
},
"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.",
"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\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties": {
"approvalPolicy": {
"anyOf": [
@@ -18,14 +18,6 @@
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
@@ -33,31 +25,6 @@
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -503,202 +470,6 @@
],
"type": "string"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -756,6 +527,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -937,135 +715,6 @@
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -2370,6 +2019,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -2390,6 +2050,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -2605,7 +2276,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -90,65 +90,6 @@
],
"type": "object"
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -18,14 +18,6 @@
"id": {
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type": "string"
},
"modifications": {
"default": [],
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
"items": {
"$ref": "#/definitions/ActivePermissionProfileModification"
},
"type": "array"
}
},
"required": [
@@ -33,31 +25,6 @@
],
"type": "object"
},
"ActivePermissionProfileModification": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootActivePermissionProfileModification",
"type": "object"
}
]
},
"AgentPath": {
"type": "string"
},
@@ -503,202 +470,6 @@
],
"type": "string"
},
"FileSystemAccessMode": {
"enum": [
"read",
"write",
"none"
],
"type": "string"
},
"FileSystemPath": {
"oneOf": [
{
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"path"
],
"title": "PathFileSystemPathType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "PathFileSystemPath",
"type": "object"
},
{
"properties": {
"pattern": {
"type": "string"
},
"type": {
"enum": [
"glob_pattern"
],
"title": "GlobPatternFileSystemPathType",
"type": "string"
}
},
"required": [
"pattern",
"type"
],
"title": "GlobPatternFileSystemPath",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"special"
],
"title": "SpecialFileSystemPathType",
"type": "string"
},
"value": {
"$ref": "#/definitions/FileSystemSpecialPath"
}
},
"required": [
"type",
"value"
],
"title": "SpecialFileSystemPath",
"type": "object"
}
]
},
"FileSystemSandboxEntry": {
"properties": {
"access": {
"$ref": "#/definitions/FileSystemAccessMode"
},
"path": {
"$ref": "#/definitions/FileSystemPath"
}
},
"required": [
"access",
"path"
],
"type": "object"
},
"FileSystemSpecialPath": {
"oneOf": [
{
"properties": {
"kind": {
"enum": [
"root"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "RootFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"minimal"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"project_roots"
],
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind"
],
"title": "KindFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"tmpdir"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "TmpdirFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"slash_tmp"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "SlashTmpFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"unknown"
],
"type": "string"
},
"path": {
"type": "string"
},
"subpath": {
"type": [
"string",
"null"
]
}
},
"required": [
"kind",
"path"
],
"type": "object"
}
]
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -756,6 +527,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -937,135 +715,6 @@
}
]
},
"PermissionProfile": {
"oneOf": [
{
"description": "Codex owns sandbox construction for this profile.",
"properties": {
"fileSystem": {
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"managed"
],
"title": "ManagedPermissionProfileType",
"type": "string"
}
},
"required": [
"fileSystem",
"network",
"type"
],
"title": "ManagedPermissionProfile",
"type": "object"
},
{
"description": "Do not apply an outer sandbox.",
"properties": {
"type": {
"enum": [
"disabled"
],
"title": "DisabledPermissionProfileType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DisabledPermissionProfile",
"type": "object"
},
{
"description": "Filesystem isolation is enforced by an external caller.",
"properties": {
"network": {
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
},
"type": {
"enum": [
"external"
],
"title": "ExternalPermissionProfileType",
"type": "string"
}
},
"required": [
"network",
"type"
],
"title": "ExternalPermissionProfile",
"type": "object"
}
]
},
"PermissionProfileFileSystemPermissions": {
"oneOf": [
{
"properties": {
"entries": {
"items": {
"$ref": "#/definitions/FileSystemSandboxEntry"
},
"type": "array"
},
"globScanMaxDepth": {
"format": "uint",
"minimum": 1.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"entries",
"type"
],
"title": "RestrictedPermissionProfileFileSystemPermissions",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"unrestricted"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
"type": "object"
}
]
},
"PermissionProfileNetworkPermissions": {
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -2370,6 +2019,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -2390,6 +2050,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -2605,7 +2276,7 @@
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance."
},
"serviceTier": {
"type": [
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -448,6 +448,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1827,6 +1834,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1847,6 +1865,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -422,6 +422,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1452,6 +1459,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1472,6 +1490,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -99,6 +99,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"ModeKind": {
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
@@ -114,65 +121,6 @@
],
"type": "string"
},
"PermissionProfileModificationParams": {
"oneOf": [
{
"description": "Additional concrete directory that should be writable.",
"properties": {
"path": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": {
"enum": [
"additionalWritableRoot"
],
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "AdditionalWritableRootPermissionProfileModificationParams",
"type": "object"
}
]
},
"PermissionProfileSelectionParams": {
"oneOf": [
{
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
"properties": {
"id": {
"type": "string"
},
"modifications": {
"items": {
"$ref": "#/definitions/PermissionProfileModificationParams"
},
"type": [
"array",
"null"
]
},
"type": {
"enum": [
"profile"
],
"title": "ProfilePermissionProfileSelectionParamsType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ProfilePermissionProfileSelectionParams",
"type": "object"
}
]
},
"Personality": {
"enum": [
"none",
@@ -410,6 +358,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -430,6 +389,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -422,6 +422,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1452,6 +1459,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1472,6 +1490,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -422,6 +422,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1452,6 +1459,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -1472,6 +1490,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -20,6 +20,13 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"high",
"original"
],
"type": "string"
},
"TextElement": {
"properties": {
"byteRange": {
@@ -75,6 +82,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"image"
@@ -95,6 +113,17 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
],
"default": null
},
"path": {
"type": "string"
},
@@ -0,0 +1,9 @@
// 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.
/**
* Selects which part of the active context is charged against
* `model_auto_compact_token_limit`.
*/
export type AutoCompactTokenLimitScope = "total" | "body_after_prefix";
File diff suppressed because one or more lines are too long
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ImageDetail = "auto" | "low" | "high" | "original";
export type ImageDetail = "high" | "original";
@@ -14,4 +14,4 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
/**
* Set when using the Responses API.
*/
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "compaction", encrypted_content: string, } | { "type": "context_compaction", encrypted_content?: string, } | { "type": "other" };
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, namespace?: string, arguments: string, call_id: string, } | { "type": "tool_search_call", call_id: string | null, status?: string, execution: string, arguments: unknown, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputBody, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, name?: string, output: FunctionCallOutputBody, } | { "type": "tool_search_output", call_id: string | null, status: string, execution: string, tools: unknown[], } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "compaction", encrypted_content: string, } | { "type": "compaction_trigger" } | { "type": "context_compaction", encrypted_content?: string, } | { "type": "other" };
+1
View File
@@ -5,6 +5,7 @@ export type { AgentPath } from "./AgentPath";
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse";
export type { AuthMode } from "./AuthMode";
export type { AutoCompactTokenLimitScope } from "./AutoCompactTokenLimitScope";
export type { ClientInfo } from "./ClientInfo";
export type { ClientNotification } from "./ClientNotification";
export type { ClientRequest } from "./ClientRequest";
@@ -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 { ActivePermissionProfileModification } from "./ActivePermissionProfileModification";
export type ActivePermissionProfile = {
/**
@@ -13,9 +12,4 @@ id: string,
* Parent profile identifier once permissions profiles support
* inheritance. This is currently always `null`.
*/
extends: string | null,
/**
* Bounded user-requested modifications applied on top of the named
* profile, if any.
*/
modifications: Array<ActivePermissionProfileModification>, };
extends: string | null, };
@@ -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 { AbsolutePathBuf } from "../AbsolutePathBuf";
export type ActivePermissionProfileModification = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
+4 -2
View File
@@ -1,6 +1,7 @@
// 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 { AutoCompactTokenLimitScope } from "../AutoCompactTokenLimitScope";
import type { ForcedLoginMethod } from "../ForcedLoginMethod";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
@@ -10,13 +11,14 @@ import type { JsonValue } from "../serde_json/JsonValue";
import type { AnalyticsConfig } from "./AnalyticsConfig";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ForcedChatgptWorkspaceIds } from "./ForcedChatgptWorkspaceIds";
import type { ProfileV2 } from "./ProfileV2";
import type { SandboxMode } from "./SandboxMode";
import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
import type { ToolsV2 } from "./ToolsV2";
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, /**
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_auto_compact_token_limit_scope: AutoCompactTokenLimitScope | null, model_provider: string | null, approval_policy: AskForApproval | null, /**
* [UNSTABLE] Optional default for where approval requests are routed for
* review.
*/
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: string | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: ForcedChatgptWorkspaceIds | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: string | null, analytics: AnalyticsConfig | null, desktop: { [key in string]?: JsonValue } | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
@@ -13,4 +13,9 @@ file: AbsolutePathBuf, } | { "type": "user",
* This is the path to the user's config.toml file, though it is not
* guaranteed to exist.
*/
file: AbsolutePathBuf, } | { "type": "project", dotCodexFolder: AbsolutePathBuf, } | { "type": "sessionFlags" } | { "type": "legacyManagedConfigTomlFromFile", file: AbsolutePathBuf, } | { "type": "legacyManagedConfigTomlFromMdm" };
file: AbsolutePathBuf,
/**
* Name of the selected profile-v2 config layered on top of the base
* user config, when this layer represents one.
*/
profile: string | null, } | { "type": "project", dotCodexFolder: AbsolutePathBuf, } | { "type": "sessionFlags" } | { "type": "legacyManagedConfigTomlFromFile", file: AbsolutePathBuf, } | { "type": "legacyManagedConfigTomlFromMdm" };
@@ -10,4 +10,10 @@ cursor?: string | null,
/**
* Optional page size; defaults to a reasonable server-side value.
*/
limit?: number | null, };
limit?: number | null,
/**
* Optional loaded thread id. Pass this when showing feature state for an
* existing thread so enablement is computed from that thread's refreshed
* config, including project-local config for the thread's cwd.
*/
threadId?: string | null, };
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FileSystemAccessMode = "read" | "write" | "none";
export type FileSystemAccessMode = "read" | "write" | "deny";
@@ -2,4 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PermissionProfileNetworkPermissions = { enabled: boolean, };
/**
* Backward-compatible API shape for ChatGPT workspace login restrictions.
*/
export type ForcedChatgptWorkspaceIds = string | Array<string>;
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HookEventName = "preToolUse" | "permissionRequest" | "postToolUse" | "preCompact" | "postCompact" | "sessionStart" | "userPromptSubmit" | "stop";
export type HookEventName = "preToolUse" | "permissionRequest" | "postToolUse" | "preCompact" | "postCompact" | "sessionStart" | "userPromptSubmit" | "subagentStart" | "stop";
@@ -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 { ConfiguredHookMatcherGroup } from "./ConfiguredHookMatcherGroup";
export type ManagedHooksRequirements = { managedDir: string | null, windowsManagedDir: string | null, PreToolUse: Array<ConfiguredHookMatcherGroup>, PermissionRequest: Array<ConfiguredHookMatcherGroup>, PostToolUse: Array<ConfiguredHookMatcherGroup>, PreCompact: Array<ConfiguredHookMatcherGroup>, PostCompact: Array<ConfiguredHookMatcherGroup>, SessionStart: Array<ConfiguredHookMatcherGroup>, UserPromptSubmit: Array<ConfiguredHookMatcherGroup>, Stop: Array<ConfiguredHookMatcherGroup>, };
export type ManagedHooksRequirements = { managedDir: string | null, windowsManagedDir: string | null, PreToolUse: Array<ConfiguredHookMatcherGroup>, PermissionRequest: Array<ConfiguredHookMatcherGroup>, PostToolUse: Array<ConfiguredHookMatcherGroup>, PreCompact: Array<ConfiguredHookMatcherGroup>, PostCompact: Array<ConfiguredHookMatcherGroup>, SessionStart: Array<ConfiguredHookMatcherGroup>, UserPromptSubmit: Array<ConfiguredHookMatcherGroup>, SubagentStart: Array<ConfiguredHookMatcherGroup>, Stop: Array<ConfiguredHookMatcherGroup>, };
@@ -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 { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions";
import type { PermissionProfileNetworkPermissions } from "./PermissionProfileNetworkPermissions";
export type PermissionProfile = { "type": "managed", network: PermissionProfileNetworkPermissions, fileSystem: PermissionProfileFileSystemPermissions, } | { "type": "disabled" } | { "type": "external", network: PermissionProfileNetworkPermissions, };
@@ -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 { FileSystemSandboxEntry } from "./FileSystemSandboxEntry";
export type PermissionProfileFileSystemPermissions = { "type": "restricted", entries: Array<FileSystemSandboxEntry>, globScanMaxDepth?: number, } | { "type": "unrestricted" };
@@ -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 { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PermissionProfileModificationParams = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
@@ -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 { PermissionProfileModificationParams } from "./PermissionProfileModificationParams";
export type PermissionProfileSelectionParams = { "type": "profile", id: string, modifications?: Array<PermissionProfileModificationParams> | null, };
@@ -0,0 +1,15 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginInstalledParams = {
/**
* Optional working directories used to discover repo marketplaces.
*/
cwds?: Array<AbsolutePathBuf> | null,
/**
* Additional uninstalled plugin names that should be returned when present locally.
* This is used by mention surfaces that intentionally expose install entrypoints.
*/
installSuggestionPluginNames?: Array<string> | null, };
@@ -0,0 +1,7 @@
// 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 { MarketplaceLoadErrorInfo } from "./MarketplaceLoadErrorInfo";
import type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type PluginInstalledResponse = { marketplaces: Array<PluginMarketplaceEntry>, marketplaceLoadErrors: Array<MarketplaceLoadErrorInfo>, };
@@ -6,4 +6,4 @@ import type { RemoteControlConnectionStatus } from "./RemoteControlConnectionSta
/**
* Current remote-control connection status and remote identity exposed to clients.
*/
export type RemoteControlStatusChangedNotification = { status: RemoteControlConnectionStatus, installationId: string, environmentId: string | null, };
export type RemoteControlStatusChangedNotification = { status: RemoteControlConnectionStatus, serverName: string, installationId: string, environmentId: string | null, };
@@ -12,7 +12,8 @@ import type { ThreadSource } from "./ThreadSource";
* 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread.
* 2. By path: load the thread from disk by path and fork it into a new thread.
*
* If using path, the thread_id param will be ignored.
* If using a non-empty path, the thread_id param will be ignored.
* Empty string path values are treated as absent.
*
* Prefer using thread_id whenever possible.
*/
@@ -16,7 +16,6 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
*/
approvalsReviewer: ApprovalsReviewer, /**
* Legacy sandbox policy retained for compatibility. Experimental clients
* should prefer `permissionProfile` when they need exact runtime
* permissions.
* should prefer `activePermissionProfile` for profile provenance.
*/
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};

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