mirror of
https://github.com/openai/codex.git
synced 2026-06-03 20:02:10 +00:00
Compare commits
1 Commits
won-fix-ap
...
codex/cca-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2e9b3b04 |
5
.bazelrc
5
.bazelrc
@@ -193,10 +193,5 @@ 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
|
||||
|
||||
@@ -8,4 +8,4 @@ script = ""
|
||||
[[actions]]
|
||||
name = "Run"
|
||||
icon = "run"
|
||||
command = "cargo +1.95.0 run --manifest-path=codex-rs/Cargo.toml --bin codex -- -c mcp_oauth_credentials_store=file"
|
||||
command = "cargo +1.93.0 run --manifest-path=codex-rs/Cargo.toml --bin codex -- -c mcp_oauth_credentials_store=file"
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,4 +0,0 @@
|
||||
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."
|
||||
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04
|
||||
ARG TZ
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG NODE_MAJOR=22
|
||||
ARG RUST_TOOLCHAIN=1.95.0
|
||||
ARG RUST_TOOLCHAIN=1.92.0
|
||||
# Keep this in sync with .devcontainer/codex-install/package.json and pnpm-lock.yaml.
|
||||
ARG CODEX_NPM_VERSION=0.121.0
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"args": {
|
||||
"TZ": "${localEnv:TZ:UTC}",
|
||||
"NODE_MAJOR": "22",
|
||||
"RUST_TOOLCHAIN": "1.95.0",
|
||||
"RUST_TOOLCHAIN": "1.92.0",
|
||||
"CODEX_NPM_VERSION": "0.121.0"
|
||||
}
|
||||
},
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1,7 +1,6 @@
|
||||
# Core crate ownership.
|
||||
/codex-rs/core/ @openai/codex-core-agent-team
|
||||
/codex-rs/ext/extension-api/ @openai/codex-core-agent-team
|
||||
/codex-rs/prompts/ @openai/codex-core-agent-team
|
||||
|
||||
# Keep ownership changes reviewed by the same team.
|
||||
/.github/CODEOWNERS @openai/codex-core-agent-team
|
||||
|
||||
17
.github/actions/setup-msvc-env/action.yml
vendored
17
.github/actions/setup-msvc-env/action.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: setup-msvc-env
|
||||
description: Expose an MSVC developer environment for the requested Windows target.
|
||||
inputs:
|
||||
target:
|
||||
description: Rust target triple that will be built on this Windows runner.
|
||||
required: true
|
||||
host-arch:
|
||||
description: Optional Visual Studio host architecture override.
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Expose MSVC SDK environment
|
||||
shell: pwsh
|
||||
run: '& "$env:GITHUB_ACTION_PATH/setup-msvc-env.ps1" -Target "${{ inputs.target }}" -HostArch "${{ inputs.host-arch }}"'
|
||||
257
.github/actions/setup-msvc-env/setup-msvc-env.ps1
vendored
257
.github/actions/setup-msvc-env/setup-msvc-env.ps1
vendored
@@ -1,257 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Target,
|
||||
|
||||
[string]$HostArch = ""
|
||||
)
|
||||
|
||||
# Cargo can cross-compile the Rust code for Windows ARM64 on a Windows x64
|
||||
# runner, but rustup alone does not expose the matching MSVC/UCRT include and
|
||||
# library paths. Ask Visual Studio for the target-specific developer
|
||||
# environment, then persist the relevant variables through GITHUB_ENV so the
|
||||
# later Cargo step sees the same environment as a normal VsDevCmd shell.
|
||||
switch ($Target) {
|
||||
"x86_64-pc-windows-msvc" {
|
||||
$TargetArch = "x64"
|
||||
$RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
|
||||
}
|
||||
"aarch64-pc-windows-msvc" {
|
||||
$TargetArch = "arm64"
|
||||
$RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"
|
||||
}
|
||||
default {
|
||||
throw "Unsupported Windows MSVC target: $Target"
|
||||
}
|
||||
}
|
||||
|
||||
# VsDevCmd needs both sides of the cross compile: the architecture of the
|
||||
# machine running the tools and the architecture of the binaries being linked.
|
||||
# Infer the host from the runner unless a caller needs to override it.
|
||||
if (-not $HostArch) {
|
||||
$HostArch = if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
|
||||
"arm64"
|
||||
} else {
|
||||
"x64"
|
||||
}
|
||||
}
|
||||
|
||||
$VsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $VsWhere)) {
|
||||
throw "vswhere.exe not found"
|
||||
}
|
||||
|
||||
# Require the target VC tools component, not merely any Visual Studio install,
|
||||
# so an x64 archive producer cannot silently link ARM64 tests with the wrong
|
||||
# SDK/toolchain layout.
|
||||
$InstallPath = & $VsWhere -latest -products * -requires $RequiredComponent -property installationPath 2>$null
|
||||
if (-not $InstallPath) {
|
||||
throw "Could not locate a Visual Studio installation with component $RequiredComponent"
|
||||
}
|
||||
|
||||
$VsDevCmd = Join-Path $InstallPath "Common7\Tools\VsDevCmd.bat"
|
||||
if (-not (Test-Path $VsDevCmd)) {
|
||||
throw "VsDevCmd.bat not found at $VsDevCmd"
|
||||
}
|
||||
|
||||
$VarsToExport = @(
|
||||
"INCLUDE",
|
||||
"LIB",
|
||||
"LIBPATH",
|
||||
"PATH",
|
||||
"UCRTVersion",
|
||||
"UniversalCRTSdkDir",
|
||||
"VCINSTALLDIR",
|
||||
"VCToolsInstallDir",
|
||||
"WindowsLibPath",
|
||||
"WindowsSdkBinPath",
|
||||
"WindowsSdkDir",
|
||||
"WindowsSDKLibVersion",
|
||||
"WindowsSDKVersion"
|
||||
)
|
||||
|
||||
# Run VsDevCmd inside cmd.exe because it is a batch file, then copy just the
|
||||
# variables Cargo/rustc need into the GitHub Actions environment file. PowerShell
|
||||
# cannot mutate the parent composite-action environment directly.
|
||||
$EnvLines = & cmd.exe /c ('"{0}" -no_logo -arch={1} -host_arch={2} >nul && set' -f $VsDevCmd, $TargetArch, $HostArch)
|
||||
$VcToolsInstallDir = $null
|
||||
foreach ($Line in $EnvLines) {
|
||||
if ($Line -notmatch "^(.*?)=(.*)$") {
|
||||
continue
|
||||
}
|
||||
|
||||
$Name = $Matches[1]
|
||||
$Value = $Matches[2]
|
||||
if ($VarsToExport -contains $Name) {
|
||||
if ($Name -ieq "Path") {
|
||||
$Name = "PATH"
|
||||
}
|
||||
if ($Name -eq "VCToolsInstallDir") {
|
||||
$VcToolsInstallDir = $Value
|
||||
}
|
||||
"$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $VcToolsInstallDir) {
|
||||
throw "VCToolsInstallDir was not exported by VsDevCmd.bat"
|
||||
}
|
||||
|
||||
# Prefer Rust's bundled linker when rustup provides one, then Visual Studio's
|
||||
# LLVM linker, and finally MSVC link.exe. This keeps the cross-compile path close
|
||||
# to Rust's normal Windows MSVC behavior while still working on runner images
|
||||
# where one of those linkers is absent.
|
||||
$Linker = $null
|
||||
$Rustc = Get-Command rustc -ErrorAction SilentlyContinue
|
||||
if ($Rustc) {
|
||||
$Sysroot = (& rustc --print sysroot 2>$null).Trim()
|
||||
$RustHost = & rustc -vV 2>$null | Select-String "^host: " | ForEach-Object { $_.Line.Substring(6) }
|
||||
if ($RustHost) {
|
||||
$RustHost = $RustHost.Trim()
|
||||
}
|
||||
if ($Sysroot -and $RustHost) {
|
||||
$RustLld = Join-Path $Sysroot "lib\rustlib\$RustHost\bin\rust-lld.exe"
|
||||
if (Test-Path $RustLld) {
|
||||
$Linker = $RustLld
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $Linker) {
|
||||
$Linker = Join-Path $InstallPath "VC\Tools\Llvm\x64\bin\lld-link.exe"
|
||||
}
|
||||
if (-not (Test-Path $Linker)) {
|
||||
$Linker = Join-Path $VcToolsInstallDir "bin\Host${HostArch}\${TargetArch}\link.exe"
|
||||
}
|
||||
if (-not (Test-Path $Linker)) {
|
||||
throw "Windows linker not found at $Linker"
|
||||
}
|
||||
|
||||
# rustc passes `/arm64hazardfree` for ARM64 MSVC links. The lld variants on our
|
||||
# Windows x64 archive producers reject that flag, including when rustc places it
|
||||
# inside a response file. Compile a tiny forwarding wrapper that strips only
|
||||
# that unsupported flag, then delegate every other argument to the real linker.
|
||||
if ($TargetArch -eq "arm64" -and (Split-Path -Leaf $Linker) -match "lld") {
|
||||
$WrapperDir = Join-Path $env:RUNNER_TEMP "msvc-lld-wrapper"
|
||||
New-Item -Path $WrapperDir -ItemType Directory -Force | Out-Null
|
||||
$WrapperPath = Join-Path $WrapperDir "lld-link-wrapper.exe"
|
||||
$WrapperSource = @'
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
var linker = Environment.GetEnvironmentVariable("MSVC_REAL_LINKER");
|
||||
if (string.IsNullOrEmpty(linker))
|
||||
{
|
||||
Console.Error.WriteLine("MSVC_REAL_LINKER is not set");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(linker)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
};
|
||||
var filteredArgs = new List<string> { "-flavor", "link", "/defaultlib:ucrt", "/nodefaultlib:libucrt" };
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (!string.Equals(arg, "/arm64hazardfree", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filteredArgs.Add(QuoteArgument(FilterResponseFile(arg)));
|
||||
}
|
||||
}
|
||||
startInfo.Arguments = string.Join(" ", filteredArgs);
|
||||
|
||||
using var process = Process.Start(startInfo);
|
||||
if (process is null)
|
||||
{
|
||||
Console.Error.WriteLine($"Failed to start linker: {linker}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
|
||||
private static string FilterResponseFile(string argument)
|
||||
{
|
||||
if (argument.Length < 2 || argument[0] != '@')
|
||||
{
|
||||
return argument;
|
||||
}
|
||||
|
||||
var responsePath = argument.Substring(1);
|
||||
if (!File.Exists(responsePath))
|
||||
{
|
||||
return argument;
|
||||
}
|
||||
|
||||
var filteredResponsePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".rsp");
|
||||
var responseContents = Regex.Replace(
|
||||
File.ReadAllText(responsePath),
|
||||
"/arm64hazardfree",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase);
|
||||
File.WriteAllText(filteredResponsePath, responseContents);
|
||||
return "@" + filteredResponsePath;
|
||||
}
|
||||
|
||||
private static string QuoteArgument(string argument)
|
||||
{
|
||||
if (argument.Length == 0)
|
||||
{
|
||||
return "\"\"";
|
||||
}
|
||||
if (argument.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
|
||||
{
|
||||
return argument;
|
||||
}
|
||||
|
||||
var quoted = new StringBuilder("\"");
|
||||
var backslashes = 0;
|
||||
foreach (var character in argument)
|
||||
{
|
||||
if (character == '\\')
|
||||
{
|
||||
backslashes++;
|
||||
continue;
|
||||
}
|
||||
if (character == '"')
|
||||
{
|
||||
quoted.Append('\\', (backslashes * 2) + 1);
|
||||
quoted.Append(character);
|
||||
backslashes = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
quoted.Append('\\', backslashes);
|
||||
backslashes = 0;
|
||||
quoted.Append(character);
|
||||
}
|
||||
quoted.Append('\\', backslashes * 2);
|
||||
quoted.Append('"');
|
||||
return quoted.ToString();
|
||||
}
|
||||
}
|
||||
'@
|
||||
$WrapperSourcePath = Join-Path $WrapperDir "lld-link-wrapper.cs"
|
||||
$WrapperSource | Out-File -FilePath $WrapperSourcePath -Encoding utf8
|
||||
$Csc = Join-Path $InstallPath "MSBuild\Current\Bin\Roslyn\csc.exe"
|
||||
if (-not (Test-Path $Csc)) {
|
||||
throw "csc.exe not found at $Csc"
|
||||
}
|
||||
& $Csc /nologo /target:exe /out:$WrapperPath $WrapperSourcePath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to compile lld-link wrapper"
|
||||
}
|
||||
"MSVC_REAL_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
$Linker = $WrapperPath
|
||||
}
|
||||
|
||||
Write-Output "Using Windows linker: $Linker"
|
||||
$CargoTarget = $Target.ToUpperInvariant().Replace("-", "_")
|
||||
"CARGO_TARGET_${CargoTarget}_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
@@ -1,20 +1,29 @@
|
||||
name: setup-rusty-v8
|
||||
description: Download and verify Codex-built rusty_v8 artifacts for Cargo builds.
|
||||
name: setup-rusty-v8-musl
|
||||
description: Download and verify musl rusty_v8 artifacts for Cargo builds.
|
||||
inputs:
|
||||
target:
|
||||
description: Rust target triple with Codex-built V8 release artifacts.
|
||||
description: Rust musl target triple.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure rusty_v8 artifact overrides and verify checksums
|
||||
- name: Configure musl rusty_v8 artifact overrides and verify checksums
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ inputs.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
case "${TARGET}" in
|
||||
x86_64-unknown-linux-musl|aarch64-unknown-linux-musl)
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported musl rusty_v8 target: ${TARGET}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)"
|
||||
release_tag="rusty-v8-v${version}"
|
||||
base_url="https://github.com/openai/codex/releases/download/${release_tag}"
|
||||
@@ -22,21 +31,19 @@ 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}"
|
||||
curl -fsSL "${base_url}/rusty_v8_release_${TARGET}.sha256" -o "${checksums_path}"
|
||||
grep -E " (librusty_v8_release_${TARGET}[.]a[.]gz|src_binding_release_${TARGET}[.]rs)$" \
|
||||
"${checksums_source}" > "${checksums_path}"
|
||||
|
||||
if [[ "$(wc -l < "${checksums_path}")" -ne 2 ]]; then
|
||||
echo "Expected exactly two checksums for ${TARGET} in ${checksums_path}" >&2
|
||||
echo "Expected exactly two checksums for ${TARGET} in ${checksums_source}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(cd "${binding_dir}" && sha256sum -c "${checksums_path}")
|
||||
else
|
||||
(cd "${binding_dir}" && shasum -a 256 -c "${checksums_path}")
|
||||
fi
|
||||
(cd "${binding_dir}" && sha256sum -c "${checksums_path}")
|
||||
echo "RUSTY_V8_ARCHIVE=${archive_path}" >> "${GITHUB_ENV}"
|
||||
echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "${GITHUB_ENV}"
|
||||
48
.github/dotslash-config.json
vendored
48
.github/dotslash-config.json
vendored
@@ -3,56 +3,56 @@
|
||||
"codex": {
|
||||
"platforms": {
|
||||
"macos-aarch64": {
|
||||
"regex": "^codex-package-aarch64-apple-darwin\\.tar\\.zst$",
|
||||
"path": "bin/codex"
|
||||
"regex": "^codex-aarch64-apple-darwin\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"macos-x86_64": {
|
||||
"regex": "^codex-package-x86_64-apple-darwin\\.tar\\.zst$",
|
||||
"path": "bin/codex"
|
||||
"regex": "^codex-x86_64-apple-darwin\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"regex": "^codex-package-x86_64-unknown-linux-musl\\.tar\\.zst$",
|
||||
"path": "bin/codex"
|
||||
"regex": "^codex-x86_64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^codex-package-aarch64-unknown-linux-musl\\.tar\\.zst$",
|
||||
"path": "bin/codex"
|
||||
"regex": "^codex-aarch64-unknown-linux-musl-bundle\\.tar\\.zst$",
|
||||
"path": "codex"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"regex": "^codex-package-x86_64-pc-windows-msvc\\.tar\\.zst$",
|
||||
"path": "bin/codex.exe"
|
||||
"regex": "^codex-x86_64-pc-windows-msvc\\.exe\\.zst$",
|
||||
"path": "codex.exe"
|
||||
},
|
||||
"windows-aarch64": {
|
||||
"regex": "^codex-package-aarch64-pc-windows-msvc\\.tar\\.zst$",
|
||||
"path": "bin/codex.exe"
|
||||
"regex": "^codex-aarch64-pc-windows-msvc\\.exe\\.zst$",
|
||||
"path": "codex.exe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codex-app-server": {
|
||||
"platforms": {
|
||||
"macos-aarch64": {
|
||||
"regex": "^codex-app-server-package-aarch64-apple-darwin\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server"
|
||||
"regex": "^codex-app-server-aarch64-apple-darwin\\.zst$",
|
||||
"path": "codex-app-server"
|
||||
},
|
||||
"macos-x86_64": {
|
||||
"regex": "^codex-app-server-package-x86_64-apple-darwin\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server"
|
||||
"regex": "^codex-app-server-x86_64-apple-darwin\\.zst$",
|
||||
"path": "codex-app-server"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"regex": "^codex-app-server-package-x86_64-unknown-linux-musl\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server"
|
||||
"regex": "^codex-app-server-x86_64-unknown-linux-musl\\.zst$",
|
||||
"path": "codex-app-server"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"regex": "^codex-app-server-package-aarch64-unknown-linux-musl\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server"
|
||||
"regex": "^codex-app-server-aarch64-unknown-linux-musl\\.zst$",
|
||||
"path": "codex-app-server"
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"regex": "^codex-app-server-package-x86_64-pc-windows-msvc\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server.exe"
|
||||
"regex": "^codex-app-server-x86_64-pc-windows-msvc\\.exe\\.zst$",
|
||||
"path": "codex-app-server.exe"
|
||||
},
|
||||
"windows-aarch64": {
|
||||
"regex": "^codex-app-server-package-aarch64-pc-windows-msvc\\.tar\\.zst$",
|
||||
"path": "bin/codex-app-server.exe"
|
||||
"regex": "^codex-app-server-aarch64-pc-windows-msvc\\.exe\\.zst$",
|
||||
"path": "codex-app-server.exe"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
5
.github/dotslash-zsh-config.json
vendored
5
.github/dotslash-zsh-config.json
vendored
@@ -7,11 +7,6 @@
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh"
|
||||
},
|
||||
"macos-x86_64": {
|
||||
"name": "codex-zsh-x86_64-apple-darwin.tar.gz",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"name": "codex-zsh-x86_64-unknown-linux-musl.tar.gz",
|
||||
"format": "tar.gz",
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: archive-release-symbols-and-strip-binaries.sh \
|
||||
--target <rust-target> \
|
||||
--artifact-name <artifact-name> \
|
||||
--release-dir <dir> \
|
||||
--archive-dir <dir> \
|
||||
--binaries "<space-delimited binary basenames>"
|
||||
EOF
|
||||
}
|
||||
|
||||
target=""
|
||||
artifact_name=""
|
||||
release_dir=""
|
||||
archive_dir=""
|
||||
binaries=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
target="${2:?--target requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--artifact-name)
|
||||
artifact_name="${2:?--artifact-name requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--release-dir)
|
||||
release_dir="${2:?--release-dir requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--archive-dir)
|
||||
archive_dir="${2:?--archive-dir requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--binaries)
|
||||
binaries="${2:?--binaries requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$target" || -z "$artifact_name" || -z "$release_dir" || -z "$archive_dir" || -z "$binaries" ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
symbols_root="${RUNNER_TEMP:-/tmp}/codex-symbols-${artifact_name}"
|
||||
symbols_dir="${symbols_root}/codex-symbols-${artifact_name}"
|
||||
archive_path="${archive_dir%/}/codex-symbols-${artifact_name}.tar.gz"
|
||||
rm -rf "$symbols_root"
|
||||
mkdir -p "$symbols_dir" "$archive_dir"
|
||||
read -r -a binary_names <<< "$binaries"
|
||||
|
||||
case "$target" in
|
||||
*apple-darwin)
|
||||
for binary in "${binary_names[@]}"; do
|
||||
binary_path="${release_dir%/}/${binary}"
|
||||
dsym_path="${binary_path}.dSYM"
|
||||
if [[ ! -f "$binary_path" ]]; then
|
||||
echo "Binary $binary_path not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -d "$dsym_path" ]]; then
|
||||
echo "dSYM $dsym_path not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -RL "$dsym_path" "${symbols_dir}/${binary}.dSYM"
|
||||
strip -S -x "$binary_path"
|
||||
done
|
||||
;;
|
||||
*linux*)
|
||||
objcopy_bin="${OBJCOPY:-objcopy}"
|
||||
strip_bin="${STRIP:-strip}"
|
||||
for binary in "${binary_names[@]}"; do
|
||||
binary_path="${release_dir%/}/${binary}"
|
||||
debug_path="${symbols_dir}/${binary}.debug"
|
||||
if [[ ! -f "$binary_path" ]]; then
|
||||
echo "Binary $binary_path not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$objcopy_bin" --only-keep-debug "$binary_path" "$debug_path"
|
||||
"$strip_bin" --strip-debug --strip-unneeded "$binary_path"
|
||||
"$objcopy_bin" --add-gnu-debuglink="$debug_path" "$binary_path"
|
||||
done
|
||||
;;
|
||||
*windows*)
|
||||
for binary in "${binary_names[@]}"; do
|
||||
pdb_path="${release_dir%/}/${binary}.pdb"
|
||||
if [[ ! -f "$pdb_path" ]]; then
|
||||
echo "PDB $pdb_path not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$pdb_path" "${symbols_dir}/${binary}.pdb"
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "No symbols packaging support for target: $target" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f "$archive_path"
|
||||
tar -C "$symbols_root" -czf "$archive_path" "codex-symbols-${artifact_name}"
|
||||
172
.github/scripts/build-codex-package-archive.sh
vendored
172
.github/scripts/build-codex-package-archive.sh
vendored
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build-codex-package-archive.sh \
|
||||
--target <rust-target> \
|
||||
--bundle <primary|app-server> \
|
||||
--entrypoint-dir <dir> \
|
||||
--archive-dir <dir> \
|
||||
[--bwrap-bin <path>] \
|
||||
[--codex-command-runner-bin <path>] \
|
||||
[--codex-windows-sandbox-setup-bin <path>] \
|
||||
[--target-suffixed-entrypoint]
|
||||
EOF
|
||||
}
|
||||
|
||||
target=""
|
||||
bundle=""
|
||||
entrypoint_dir=""
|
||||
archive_dir=""
|
||||
target_suffixed_entrypoint="false"
|
||||
resource_args=()
|
||||
bwrap_bin_provided="false"
|
||||
command_runner_bin_provided="false"
|
||||
sandbox_setup_bin_provided="false"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
target="${2:?--target requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--bundle)
|
||||
bundle="${2:?--bundle requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--entrypoint-dir)
|
||||
entrypoint_dir="${2:?--entrypoint-dir requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--archive-dir)
|
||||
archive_dir="${2:?--archive-dir requires a value}"
|
||||
shift 2
|
||||
;;
|
||||
--bwrap-bin)
|
||||
resource_args+=(--bwrap-bin "${2:?--bwrap-bin requires a value}")
|
||||
bwrap_bin_provided="true"
|
||||
shift 2
|
||||
;;
|
||||
--codex-command-runner-bin)
|
||||
resource_args+=(
|
||||
--codex-command-runner-bin
|
||||
"${2:?--codex-command-runner-bin requires a value}"
|
||||
)
|
||||
command_runner_bin_provided="true"
|
||||
shift 2
|
||||
;;
|
||||
--codex-windows-sandbox-setup-bin)
|
||||
resource_args+=(
|
||||
--codex-windows-sandbox-setup-bin
|
||||
"${2:?--codex-windows-sandbox-setup-bin requires a value}"
|
||||
)
|
||||
sandbox_setup_bin_provided="true"
|
||||
shift 2
|
||||
;;
|
||||
--target-suffixed-entrypoint)
|
||||
target_suffixed_entrypoint="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$target" || -z "$bundle" || -z "$entrypoint_dir" || -z "$archive_dir" ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$bundle" in
|
||||
primary)
|
||||
variant="codex"
|
||||
entrypoint="codex"
|
||||
archive_stem="codex-package"
|
||||
;;
|
||||
app-server)
|
||||
variant="codex-app-server"
|
||||
entrypoint="codex-app-server"
|
||||
archive_stem="codex-app-server-package"
|
||||
;;
|
||||
*)
|
||||
echo "No Codex package variant for bundle: $bundle" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exe_suffix=""
|
||||
case "$target" in
|
||||
*windows*)
|
||||
exe_suffix=".exe"
|
||||
;;
|
||||
esac
|
||||
|
||||
entrypoint_name="$entrypoint"
|
||||
if [[ "$target_suffixed_entrypoint" == "true" ]]; then
|
||||
entrypoint_name="${entrypoint_name}-${target}"
|
||||
fi
|
||||
|
||||
case "$target" in
|
||||
*linux*)
|
||||
bwrap_bin="${entrypoint_dir%/}/bwrap"
|
||||
if [[ "$bwrap_bin_provided" == "false" && -f "$bwrap_bin" ]]; then
|
||||
resource_args+=(--bwrap-bin "$bwrap_bin")
|
||||
fi
|
||||
;;
|
||||
*windows*)
|
||||
command_runner_bin="${entrypoint_dir%/}/codex-command-runner.exe"
|
||||
sandbox_setup_bin="${entrypoint_dir%/}/codex-windows-sandbox-setup.exe"
|
||||
if [[ "$command_runner_bin_provided" == "false" && -f "$command_runner_bin" ]]; then
|
||||
resource_args+=(--codex-command-runner-bin "$command_runner_bin")
|
||||
fi
|
||||
if [[ "$sandbox_setup_bin_provided" == "false" && -f "$sandbox_setup_bin" ]]; then
|
||||
resource_args+=(--codex-windows-sandbox-setup-bin "$sandbox_setup_bin")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
repo_root="${GITHUB_WORKSPACE:-}"
|
||||
if [[ -z "$repo_root" ]]; then
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python_bin="python3"
|
||||
else
|
||||
python_bin="python"
|
||||
fi
|
||||
|
||||
if ! command -v zstd >/dev/null 2>&1 && [[ -x "${repo_root}/.github/workflows/zstd" ]]; then
|
||||
export PATH="${repo_root}/.github/workflows:${PATH}"
|
||||
fi
|
||||
|
||||
mkdir -p "$archive_dir"
|
||||
package_dir="${RUNNER_TEMP:-/tmp}/${archive_stem}-${target}"
|
||||
gzip_archive_path="${archive_dir}/${archive_stem}-${target}.tar.gz"
|
||||
zstd_archive_path="${archive_dir}/${archive_stem}-${target}.tar.zst"
|
||||
rm -rf "$package_dir"
|
||||
|
||||
python_args=(
|
||||
"${repo_root}/scripts/build_codex_package.py"
|
||||
--target "$target"
|
||||
--variant "$variant"
|
||||
--entrypoint-bin "${entrypoint_dir%/}/${entrypoint_name}${exe_suffix}"
|
||||
--cargo-profile release
|
||||
--package-dir "$package_dir"
|
||||
--archive-output "$gzip_archive_path"
|
||||
--archive-output "$zstd_archive_path"
|
||||
)
|
||||
if ((${#resource_args[@]} > 0)); then
|
||||
python_args+=("${resource_args[@]}")
|
||||
fi
|
||||
python_args+=(--force)
|
||||
|
||||
"$python_bin" "${python_args[@]}"
|
||||
13
.github/scripts/install-musl-build-tools.sh
vendored
13
.github/scripts/install-musl-build-tools.sh
vendored
@@ -150,9 +150,7 @@ for arg in "\$@"; do
|
||||
args+=("\${arg}")
|
||||
done
|
||||
|
||||
# Zig enables UBSan for debug C builds by default. Rust links these objects
|
||||
# without Zig's sanitizer runtime, so keep native dependencies uninstrumented.
|
||||
exec "${zig_bin}" cc -target "${zig_target}" "\${args[@]}" -fno-sanitize=undefined
|
||||
exec "${zig_bin}" cc -target "${zig_target}" "\${args[@]}"
|
||||
EOF
|
||||
cat >"${cxx}" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
@@ -209,9 +207,7 @@ for arg in "\$@"; do
|
||||
args+=("\${arg}")
|
||||
done
|
||||
|
||||
# Zig enables UBSan for debug C++ builds by default. Rust links these objects
|
||||
# without Zig's sanitizer runtime, so keep native dependencies uninstrumented.
|
||||
exec "${zig_bin}" c++ -target "${zig_target}" "\${args[@]}" -fno-sanitize=undefined
|
||||
exec "${zig_bin}" c++ -target "${zig_target}" "\${args[@]}"
|
||||
EOF
|
||||
chmod +x "${cc}" "${cxx}"
|
||||
|
||||
@@ -274,11 +270,6 @@ echo "PKG_CONFIG_PATH=${pkg_config_path}" >> "$GITHUB_ENV"
|
||||
pkg_config_path_var="PKG_CONFIG_PATH_${TARGET}"
|
||||
pkg_config_path_var="${pkg_config_path_var//-/_}"
|
||||
echo "${pkg_config_path_var}=${libcap_pkgconfig_dir}" >> "$GITHUB_ENV"
|
||||
pkg_config_libdir_var="PKG_CONFIG_LIBDIR_${TARGET}"
|
||||
pkg_config_libdir_var="${pkg_config_libdir_var//-/_}"
|
||||
# Do not let musl cross-builds resolve native libraries from the host glibc
|
||||
# pkg-config directories. libcap is the only target package provided here.
|
||||
echo "${pkg_config_libdir_var}=${libcap_pkgconfig_dir}" >> "$GITHUB_ENV"
|
||||
|
||||
if [[ -n "${sysroot}" && "${sysroot}" != "/" ]]; then
|
||||
echo "PKG_CONFIG_SYSROOT_DIR=${sysroot}" >> "$GITHUB_ENV"
|
||||
|
||||
231
.github/scripts/rusty_v8_bazel.py
vendored
231
.github/scripts/rusty_v8_bazel.py
vendored
@@ -5,18 +5,17 @@ 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,
|
||||
)
|
||||
|
||||
@@ -24,16 +23,12 @@ 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"
|
||||
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}"]
|
||||
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"
|
||||
|
||||
|
||||
def bazel_execroot() -> Path:
|
||||
@@ -80,7 +75,6 @@ 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,
|
||||
],
|
||||
@@ -97,10 +91,8 @@ 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",
|
||||
@@ -109,8 +101,6 @@ 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,
|
||||
@@ -124,15 +114,11 @@ def ensure_bazel_output_files(
|
||||
compilation_mode: str = "fastbuild",
|
||||
bazel_configs: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
# 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)
|
||||
if all(path.exists() for path in outputs):
|
||||
return outputs
|
||||
|
||||
bazel_build(platform, labels, compilation_mode, bazel_configs)
|
||||
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
|
||||
missing = [str(path) for path in outputs if not path.exists()]
|
||||
if missing:
|
||||
@@ -140,18 +126,9 @@ def ensure_bazel_output_files(
|
||||
return outputs
|
||||
|
||||
|
||||
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:
|
||||
def release_pair_label(target: str) -> str:
|
||||
target_suffix = target.replace("-", "_")
|
||||
pair_kind = "sandbox_release_pair" if sandbox else "release_pair"
|
||||
return f"//third_party/v8:rusty_v8_{pair_kind}_{target_suffix}"
|
||||
return f"//third_party/v8:rusty_v8_release_pair_{target_suffix}"
|
||||
|
||||
|
||||
def resolved_v8_crate_version() -> str:
|
||||
@@ -192,16 +169,6 @@ 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()
|
||||
|
||||
|
||||
@@ -213,76 +180,66 @@ def command_manifest_path(manifest: Path | None, version: str) -> Path:
|
||||
return ROOT / manifest
|
||||
|
||||
|
||||
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 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_binding_name(target: str, artifact_profile: str) -> str:
|
||||
return f"src_binding_{artifact_profile}_{target}.rs"
|
||||
def is_musl_archive_target(target: str, source_path: Path) -> bool:
|
||||
return target.endswith("-unknown-linux-musl") and source_path.suffix == ".a"
|
||||
|
||||
|
||||
def staged_checksums_name(target: str, artifact_profile: str) -> str:
|
||||
return f"rusty_v8_{artifact_profile}_{target}.sha256"
|
||||
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 stage_artifacts(
|
||||
target: str,
|
||||
def merged_musl_archive(
|
||||
platform: str,
|
||||
lib_path: Path,
|
||||
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}")
|
||||
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
|
||||
]
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
def stage_release_pair(
|
||||
@@ -291,12 +248,10 @@ 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, sandbox)],
|
||||
[release_pair_label(target)],
|
||||
compilation_mode,
|
||||
bazel_configs,
|
||||
)
|
||||
@@ -311,7 +266,39 @@ def stage_release_pair(
|
||||
except StopIteration as exc:
|
||||
raise SystemExit(f"missing Rust binding output for {target}") from exc
|
||||
|
||||
stage_artifacts(target, lib_path, binding_path, output_dir, sandbox)
|
||||
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)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
@@ -322,7 +309,6 @@ 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",
|
||||
@@ -335,14 +321,6 @@ 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")
|
||||
@@ -375,15 +353,6 @@ 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
.github/scripts/rusty_v8_module_bazel.py
vendored
13
.github/scripts/rusty_v8_module_bazel.py
vendored
@@ -9,7 +9,6 @@ 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):
|
||||
@@ -96,18 +95,6 @@ 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],
|
||||
|
||||
62
.github/scripts/setup-dev-drive.ps1
vendored
62
.github/scripts/setup-dev-drive.ps1
vendored
@@ -1,62 +0,0 @@
|
||||
# Configure a fast drive for Windows CI jobs.
|
||||
#
|
||||
# GitHub-hosted Windows runners do not always expose a secondary D: volume. When
|
||||
# they do not, try to create a Dev Drive VHD and fall back to C: if the runner
|
||||
# image does not allow that provisioning path.
|
||||
|
||||
function Use-FallbackDrive {
|
||||
param([string]$Reason)
|
||||
|
||||
Write-Warning "$Reason Falling back to C:"
|
||||
return "C:"
|
||||
}
|
||||
|
||||
function Invoke-BestEffort {
|
||||
param([scriptblock]$Script, [string]$Description)
|
||||
|
||||
try {
|
||||
& $Script
|
||||
} catch {
|
||||
Write-Warning "$Description failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path "D:\") {
|
||||
Write-Output "Using existing drive at D:"
|
||||
$Drive = "D:"
|
||||
} else {
|
||||
try {
|
||||
$VhdPath = Join-Path $env:RUNNER_TEMP "codex-dev-drive.vhdx"
|
||||
$SizeBytes = 64GB
|
||||
|
||||
if (Test-Path $VhdPath) {
|
||||
Remove-Item -Path $VhdPath -Force
|
||||
}
|
||||
|
||||
New-VHD -Path $VhdPath -SizeBytes $SizeBytes -Dynamic -ErrorAction Stop | Out-Null
|
||||
$Mounted = Mount-VHD -Path $VhdPath -Passthru -ErrorAction Stop
|
||||
$Disk = $Mounted | Get-Disk -ErrorAction Stop
|
||||
$Disk | Initialize-Disk -PartitionStyle GPT -ErrorAction Stop
|
||||
$Partition = $Disk | New-Partition -AssignDriveLetter -UseMaximumSize -ErrorAction Stop
|
||||
$Volume = $Partition | Format-Volume -FileSystem ReFS -NewFileSystemLabel "CodexDevDrive" -DevDrive -Confirm:$false -Force -ErrorAction Stop
|
||||
|
||||
$Drive = "$($Volume.DriveLetter):"
|
||||
|
||||
Invoke-BestEffort { fsutil devdrv trust $Drive } "Trusting Dev Drive $Drive"
|
||||
Invoke-BestEffort { fsutil devdrv enable /disallowAv } "Disabling AV filter attachment for Dev Drives"
|
||||
Invoke-BestEffort { fsutil devdrv query $Drive } "Querying Dev Drive $Drive"
|
||||
|
||||
Write-Output "Using Dev Drive at $Drive"
|
||||
} catch {
|
||||
$Drive = Use-FallbackDrive "Failed to create Dev Drive: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$Tmp = "$Drive\codex-tmp"
|
||||
New-Item -Path $Tmp -ItemType Directory -Force | Out-Null
|
||||
|
||||
@(
|
||||
"DEV_DRIVE=$Drive"
|
||||
"TMP=$Tmp"
|
||||
"TEMP=$Tmp"
|
||||
) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
287
.github/scripts/test_rusty_v8_bazel.py
vendored
287
.github/scripts/test_rusty_v8_bazel.py
vendored
@@ -4,270 +4,11 @@ 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(
|
||||
"""\
|
||||
@@ -380,34 +121,6 @@ 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()
|
||||
|
||||
3
.github/workflows/README.md
vendored
3
.github/workflows/README.md
vendored
@@ -21,8 +21,7 @@ The workflows in this directory are split so that pull requests get fast, review
|
||||
- `rust-ci-full.yml` is the full Cargo-native verification workflow.
|
||||
It keeps the heavier checks off the PR path while still validating them after merge:
|
||||
- the full Cargo `clippy` matrix
|
||||
- the full Cargo `nextest` matrix via per-platform archive-backed shards
|
||||
- Windows ARM64 nextest archives cross-compiled on Windows x64, then replayed on native Windows ARM64 shards
|
||||
- the full Cargo `nextest` matrix
|
||||
- release-profile Cargo builds
|
||||
- cross-platform `argument-comment-lint`
|
||||
- Linux remote-env tests
|
||||
|
||||
18
.github/workflows/bazel.yml
vendored
18
.github/workflows/bazel.yml
vendored
@@ -141,9 +141,7 @@ jobs:
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
runs-on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
runs-on: windows-latest
|
||||
name: Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard ${{ matrix.shard }}/4
|
||||
|
||||
steps:
|
||||
@@ -248,9 +246,7 @@ jobs:
|
||||
# it a larger timeout.
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
timeout-minutes: 40
|
||||
runs-on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
runs-on: windows-latest
|
||||
name: Bazel test on windows-latest for x86_64-pc-windows-gnullvm (native main)
|
||||
|
||||
steps:
|
||||
@@ -336,10 +332,7 @@ jobs:
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
runs-on: ${{ matrix.runs_on || matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Bazel clippy on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
@@ -429,10 +422,7 @@ jobs:
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
runs-on: ${{ matrix.runs_on || matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Verify release build on ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
||||
steps:
|
||||
|
||||
9
.github/workflows/cargo-deny.yml
vendored
9
.github/workflows/cargo-deny.yml
vendored
@@ -6,11 +6,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI across every Cargo invocation.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,10 +20,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
|
||||
- name: Run cargo-deny
|
||||
uses: EmbarkStudios/cargo-deny-action@82eb9f621fbc699dd0918f3ea06864c14cc84246 # v2
|
||||
with:
|
||||
rust-version: 1.95.0
|
||||
rust-version: 1.93.0
|
||||
manifest-path: ./codex-rs/Cargo.toml
|
||||
|
||||
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -26,9 +26,6 @@ jobs:
|
||||
- name: Verify Bazel clippy flags match Cargo workspace lints
|
||||
run: python3 .github/scripts/verify_bazel_clippy_lints.py
|
||||
|
||||
- name: Test Codex package builder
|
||||
run: python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5
|
||||
with:
|
||||
@@ -42,6 +39,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# stage_npm_packages.py requires DotSlash when staging releases.
|
||||
- uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- name: Stage npm package
|
||||
id: stage_npm_package
|
||||
env:
|
||||
@@ -52,13 +52,15 @@ jobs:
|
||||
# cross-platform native payload required by the npm package layout.
|
||||
# Passing the workflow URL directly avoids relying on old rust-v*
|
||||
# branches remaining discoverable via `gh run list --branch ...`.
|
||||
CODEX_VERSION=0.133.0-alpha.4
|
||||
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/26201494185"
|
||||
CODEX_VERSION=0.125.0
|
||||
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/24901475298"
|
||||
OUTPUT_DIR="${RUNNER_TEMP}"
|
||||
# This reused workflow predates the standalone bwrap artifact.
|
||||
python3 ./scripts/stage_npm_packages.py \
|
||||
--release-version "$CODEX_VERSION" \
|
||||
--workflow-url "$WORKFLOW_URL" \
|
||||
--package codex \
|
||||
--allow-missing-native-component bwrap \
|
||||
--output-dir "$OUTPUT_DIR"
|
||||
PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz"
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
@@ -74,15 +76,5 @@ jobs:
|
||||
- name: Check root README ToC
|
||||
run: python3 scripts/readme_toc.py README.md
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: just@1.51.0
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
with:
|
||||
version: "0.11.3"
|
||||
- name: Check formatting (run `just fmt` to fix)
|
||||
run: just fmt-check
|
||||
|
||||
- name: Prettier (run `pnpm run format:fix` to fix)
|
||||
run: pnpm run format
|
||||
|
||||
2
.github/workflows/issue-deduplicator.yml
vendored
2
.github/workflows/issue-deduplicator.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
|
||||
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-deduplicate'))
|
||||
runs-on: ubuntu-latest
|
||||
environment: issue-triage
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
@@ -158,7 +157,6 @@ jobs:
|
||||
needs: normalize-duplicates-all
|
||||
if: ${{ needs.normalize-duplicates-all.result == 'success' && needs.normalize-duplicates-all.outputs.has_matches != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: issue-triage
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
|
||||
1
.github/workflows/issue-labeler.yml
vendored
1
.github/workflows/issue-labeler.yml
vendored
@@ -12,7 +12,6 @@ jobs:
|
||||
# Prevent runs on forks (requires OpenAI API key, wastes Actions minutes)
|
||||
if: github.repository == 'openai/codex' && (github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'codex-label'))
|
||||
runs-on: ubuntu-latest
|
||||
environment: issue-triage
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
|
||||
91
.github/workflows/python-sdk-release.yml
vendored
91
.github/workflows/python-sdk-release.yml
vendored
@@ -1,91 +0,0 @@
|
||||
name: python-sdk-release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "python-v*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-python-sdk:
|
||||
if: github.repository == 'openai/codex'
|
||||
name: build-python-sdk
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate tag and build Python SDK package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
sdk_version="${GITHUB_REF_NAME#python-v}"
|
||||
if [[ ! "${sdk_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$ ]]; then
|
||||
echo "Python SDK release tags must identify a beta release, for example python-v0.1.0b1."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build in a glibc Linux image so release type generation installs
|
||||
# the pinned manylinux runtime wheel.
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-e HOME=/tmp/codex-python-sdk-home \
|
||||
-e UV_LINK_MODE=copy \
|
||||
-e SDK_VERSION="${sdk_version}" \
|
||||
-e SDK_STAGE_DIR="${RUNNER_TEMP}/openai-codex" \
|
||||
-e SDK_DIST_DIR="${GITHUB_WORKSPACE}/dist/python-sdk" \
|
||||
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
|
||||
-v "${RUNNER_TEMP}:${RUNNER_TEMP}" \
|
||||
-w "${GITHUB_WORKSPACE}/sdk/python" \
|
||||
python:3.12-slim \
|
||||
sh -euxc '
|
||||
python -m venv /tmp/release-tools
|
||||
/tmp/release-tools/bin/python -m pip install build twine uv==0.11.3
|
||||
/tmp/release-tools/bin/uv sync --extra dev --frozen
|
||||
/tmp/release-tools/bin/uv run --extra dev --frozen python scripts/update_sdk_artifacts.py \
|
||||
stage-sdk "${SDK_STAGE_DIR}" \
|
||||
--sdk-version "${SDK_VERSION}"
|
||||
/tmp/release-tools/bin/python -m build \
|
||||
--wheel \
|
||||
--sdist \
|
||||
--outdir "${SDK_DIST_DIR}" \
|
||||
"${SDK_STAGE_DIR}"
|
||||
/tmp/release-tools/bin/python -m twine check --strict "${SDK_DIST_DIR}/"*
|
||||
'
|
||||
|
||||
- name: Upload Python SDK package
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-sdk-package
|
||||
path: dist/python-sdk/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish-python-sdk:
|
||||
name: publish-python-sdk
|
||||
needs: build-python-sdk
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # Required for PyPI trusted publishing.
|
||||
|
||||
steps:
|
||||
- name: Download Python SDK package
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-sdk-package
|
||||
path: dist/python-sdk
|
||||
|
||||
- name: Publish Python SDK to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
packages-dir: dist/python-sdk
|
||||
464
.github/workflows/rust-ci-full-nextest-platform.yml
vendored
464
.github/workflows/rust-ci-full-nextest-platform.yml
vendored
@@ -1,464 +0,0 @@
|
||||
name: rust-ci-full nextest platform
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runner:
|
||||
required: true
|
||||
type: string
|
||||
runner_group:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
runner_labels:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
archive_runner:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
archive_runner_group:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
archive_runner_labels:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
profile:
|
||||
required: true
|
||||
type: string
|
||||
artifact_id:
|
||||
required: true
|
||||
type: string
|
||||
remote_env:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
test_threads:
|
||||
required: false
|
||||
default: 0
|
||||
type: number
|
||||
use_sccache:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
# Caller workflow-level env does not flow through workflow_call, so keep the
|
||||
# Cargo git transport hardening on the archive and shard jobs directly here.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
jobs:
|
||||
archive:
|
||||
name: Build nextest archive
|
||||
runs-on: ${{ inputs.archive_runner_group != '' && fromJSON(format('{{"group":"{0}","labels":"{1}"}}', inputs.archive_runner_group, inputs.archive_runner_labels)) || inputs.archive_runner != '' && inputs.archive_runner || inputs.runner_group != '' && fromJSON(format('{{"group":"{0}","labels":"{1}"}}', inputs.runner_group, inputs.runner_labels)) || inputs.runner }}
|
||||
timeout-minutes: 60
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Windows ARM64 archives are built on Windows x64, while their shards run
|
||||
# on native Windows ARM64. Key producer-side caches by the archive runner
|
||||
# so the cross-compile build reuses the Windows x64 cache lineage.
|
||||
ARCHIVE_CACHE_RUNNER: ${{ inputs.archive_runner != '' && inputs.archive_runner || inputs.runner }}
|
||||
USE_SCCACHE: ${{ inputs.use_sccache && 'true' || 'false' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
NEXTEST_ARCHIVE_FILE: nextest-${{ inputs.artifact_id }}.tar.zst
|
||||
TEST_HELPERS_ARTIFACT: nextest-test-helpers-${{ inputs.artifact_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure Dev Drive (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: pwsh
|
||||
run: ../.github/scripts/setup-dev-drive.ps1
|
||||
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev bubblewrap
|
||||
fi
|
||||
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: ${{ inputs.target }}
|
||||
|
||||
- name: Expose MSVC SDK environment (Windows)
|
||||
if: ${{ runner.os == 'Windows' && inputs.target == 'aarch64-pc-windows-msvc' }}
|
||||
uses: ./.github/actions/setup-msvc-env
|
||||
with:
|
||||
target: ${{ inputs.target }}
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-
|
||||
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
if [[ -n "${DEV_DRIVE:-}" ]]; then
|
||||
echo "SCCACHE_DIR=${DEV_DRIVE}\\.sccache" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
fi
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
wrapper="$(command -v sccache)"
|
||||
if [[ "${RUNNER_OS}" == "Windows" ]] && command -v cygpath >/dev/null 2>&1; then
|
||||
wrapper="$(cygpath -w "${wrapper}")"
|
||||
fi
|
||||
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
echo "CARGO_BUILD_RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ${{ env.SCCACHE_DIR }}
|
||||
key: sccache-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
|
||||
- name: Enable unprivileged user namespaces (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
|
||||
- name: Build nextest archive
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
archive_dir="${RUNNER_TEMP}/nextest-archive"
|
||||
mkdir -p "${archive_dir}"
|
||||
cargo nextest archive \
|
||||
--target ${{ inputs.target }} \
|
||||
--cargo-profile ${{ inputs.profile }} \
|
||||
--timings \
|
||||
--archive-file "${archive_dir}/${NEXTEST_ARCHIVE_FILE}"
|
||||
|
||||
- name: Build runtime test helpers
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'Windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
helper_dir="${RUNNER_TEMP}/${TEST_HELPERS_ARTIFACT}"
|
||||
mkdir -p "${helper_dir}"
|
||||
|
||||
if [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
cargo build \
|
||||
--target ${{ inputs.target }} \
|
||||
--profile ${{ inputs.profile }} \
|
||||
-p codex-linux-sandbox \
|
||||
--bin codex-linux-sandbox
|
||||
cp "target/${{ inputs.target }}/${{ inputs.profile }}/codex-linux-sandbox" "${helper_dir}/"
|
||||
else
|
||||
cargo build \
|
||||
--target ${{ inputs.target }} \
|
||||
--profile ${{ inputs.profile }} \
|
||||
-p codex-windows-sandbox \
|
||||
--bin codex-windows-sandbox-setup \
|
||||
--bin codex-command-runner
|
||||
cp "target/${{ inputs.target }}/${{ inputs.profile }}/codex-windows-sandbox-setup.exe" "${helper_dir}/"
|
||||
cp "target/${{ inputs.target }}/${{ inputs.profile }}/codex-command-runner.exe" "${helper_dir}/"
|
||||
fi
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ inputs.target }}-${{ inputs.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Upload nextest archive
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: nextest-archive-${{ inputs.artifact_id }}
|
||||
path: ${{ runner.temp }}/nextest-archive/${{ env.NEXTEST_ARCHIVE_FILE }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload runtime test helpers
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'Windows' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ env.TEST_HELPERS_ARTIFACT }}
|
||||
path: ${{ runner.temp }}/${{ env.TEST_HELPERS_ARTIFACT }}/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ${{ env.SCCACHE_DIR }}
|
||||
key: sccache-${{ env.ARCHIVE_CACHE_RUNNER }}-${{ inputs.target }}-${{ inputs.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ inputs.target }} (tests)";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
shard:
|
||||
name: Tests shard ${{ matrix.shard }}/4
|
||||
needs: archive
|
||||
runs-on: ${{ inputs.runner_group != '' && fromJSON(format('{{"group":"{0}","labels":"{1}"}}', inputs.runner_group, inputs.runner_labels)) || inputs.runner }}
|
||||
timeout-minutes: 60
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
NEXTEST_ARCHIVE_FILE: nextest-${{ inputs.artifact_id }}.tar.zst
|
||||
TEST_HELPERS_ARTIFACT: nextest-test-helpers-${{ inputs.artifact_id }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev bubblewrap
|
||||
fi
|
||||
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: ${{ inputs.target }}
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
|
||||
- name: Enable unprivileged user namespaces (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
|
||||
- name: Set up remote test env (Docker)
|
||||
if: ${{ runner.os == 'Linux' && inputs.remote_env }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME="codex-remote-test-env-${{ github.run_id }}-${{ matrix.shard }}"
|
||||
source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh"
|
||||
echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV"
|
||||
echo "CODEX_TEST_REMOTE_EXEC_SERVER_URL=${CODEX_TEST_REMOTE_EXEC_SERVER_URL}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download nextest archive
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nextest-archive-${{ inputs.artifact_id }}
|
||||
path: ${{ runner.temp }}/nextest-archive
|
||||
|
||||
- name: Download runtime test helpers
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'Windows' }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ env.TEST_HELPERS_ARTIFACT }}
|
||||
path: ${{ runner.temp }}/${{ env.TEST_HELPERS_ARTIFACT }}
|
||||
|
||||
- name: tests
|
||||
id: test
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
archive_file="${RUNNER_TEMP}/nextest-archive/${NEXTEST_ARCHIVE_FILE}"
|
||||
workspace_root="$(pwd)"
|
||||
|
||||
if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
archive_file="$(cygpath -w "${archive_file}")"
|
||||
workspace_root="$(cygpath -w "${workspace_root}")"
|
||||
fi
|
||||
|
||||
if [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
helper_dir="${RUNNER_TEMP}/${TEST_HELPERS_ARTIFACT}"
|
||||
helper_target_dir="$(pwd)/target/${{ inputs.target }}/${{ inputs.profile }}"
|
||||
mkdir -p "${helper_target_dir}"
|
||||
cp "${helper_dir}/codex-linux-sandbox" "${helper_target_dir}/"
|
||||
chmod +x "${helper_target_dir}/codex-linux-sandbox"
|
||||
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
helper_dir="${RUNNER_TEMP}/${TEST_HELPERS_ARTIFACT}"
|
||||
helper_target_dir="$(pwd)/target/${{ inputs.target }}/${{ inputs.profile }}"
|
||||
mkdir -p "${helper_target_dir}"
|
||||
cp "${helper_dir}/codex-windows-sandbox-setup.exe" "${helper_target_dir}/"
|
||||
cp "${helper_dir}/codex-command-runner.exe" "${helper_target_dir}/"
|
||||
fi
|
||||
|
||||
nextest_args=(
|
||||
run
|
||||
--no-fail-fast
|
||||
--archive-file "${archive_file}"
|
||||
--workspace-remap "${workspace_root}"
|
||||
--partition "hash:${{ matrix.shard }}/4"
|
||||
)
|
||||
if [[ "${{ inputs.test_threads }}" != "0" ]]; then
|
||||
nextest_args+=(--test-threads "${{ inputs.test_threads }}")
|
||||
fi
|
||||
|
||||
test_command=(cargo nextest "${nextest_args[@]}")
|
||||
if [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
sandbox_helper="${helper_target_dir}/codex-linux-sandbox"
|
||||
test_command=(
|
||||
env
|
||||
"CARGO_BIN_EXE_codex-linux-sandbox=${sandbox_helper}"
|
||||
"CARGO_BIN_EXE_codex_linux_sandbox=${sandbox_helper}"
|
||||
cargo nextest "${nextest_args[@]}"
|
||||
)
|
||||
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
setup_helper="$(cygpath -w "${helper_target_dir}/codex-windows-sandbox-setup.exe")"
|
||||
command_runner="$(cygpath -w "${helper_target_dir}/codex-command-runner.exe")"
|
||||
test_command=(
|
||||
env
|
||||
"CARGO_BIN_EXE_codex_windows_sandbox_setup=${setup_helper}"
|
||||
"CARGO_BIN_EXE_codex_command_runner=${command_runner}"
|
||||
cargo nextest "${nextest_args[@]}"
|
||||
)
|
||||
fi
|
||||
|
||||
"${test_command[@]}"
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
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-${{ inputs.artifact_id }}-shard-${{ matrix.shard }}
|
||||
path: codex-rs/target/nextest/default/junit.xml
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Tear down remote test env
|
||||
if: ${{ always() && runner.os == 'Linux' && inputs.remote_env }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
if [[ "${STEPS_TEST_OUTCOME}" != "success" ]]; then
|
||||
docker logs "${CODEX_TEST_REMOTE_ENV}" || true
|
||||
fi
|
||||
docker rm -f "${CODEX_TEST_REMOTE_ENV}" >/dev/null 2>&1 || true
|
||||
env:
|
||||
STEPS_TEST_OUTCOME: ${{ steps.test.outcome }}
|
||||
|
||||
- name: verify tests passed
|
||||
if: steps.test.outcome == 'failure'
|
||||
run: |
|
||||
echo "Tests failed. See logs for details."
|
||||
exit 1
|
||||
|
||||
result:
|
||||
name: Platform result
|
||||
needs: shard
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Confirm test shards passed
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ needs.shard.result }}" != "success" ]]; then
|
||||
echo "Nextest shards finished with result: ${{ needs.shard.result }}" >&2
|
||||
exit 1
|
||||
fi
|
||||
391
.github/workflows/rust-ci-full.yml
vendored
391
.github/workflows/rust-ci-full.yml
vendored
@@ -25,16 +25,11 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: just
|
||||
- name: cargo fmt
|
||||
run: cargo fmt -- --config imports_granularity=Item --check
|
||||
- name: Rust benchmark smoke test
|
||||
run: just bench-smoke
|
||||
|
||||
cargo_shear:
|
||||
name: cargo shear
|
||||
@@ -46,7 +41,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: cargo-shear@1.11.2
|
||||
@@ -63,7 +58,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
components: llvm-tools-preview, rustc-dev, rust-src
|
||||
@@ -260,9 +255,13 @@ jobs:
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
packages=(pkg-config libcap-dev)
|
||||
if [[ "${{ matrix.target }}" == 'x86_64-unknown-linux-musl' || "${{ matrix.target }}" == 'aarch64-unknown-linux-musl' ]]; then
|
||||
packages+=(libubsan1)
|
||||
fi
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
|
||||
fi
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
components: clippy
|
||||
@@ -344,6 +343,14 @@ jobs:
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Disable sccache wrapper (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "RUSTC_WRAPPER=" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Prepare APT cache directories (musl)
|
||||
shell: bash
|
||||
@@ -377,9 +384,61 @@ jobs:
|
||||
shell: bash
|
||||
run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh"
|
||||
|
||||
- if: ${{ !contains(matrix.target, 'windows') }}
|
||||
name: Configure rusty_v8 artifact overrides and verify checksums
|
||||
uses: ./.github/actions/setup-rusty-v8
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Configure rustc UBSan wrapper (musl host)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ubsan=""
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')"
|
||||
fi
|
||||
wrapper_root="${RUNNER_TEMP:-/tmp}"
|
||||
wrapper="${wrapper_root}/rustc-ubsan-wrapper"
|
||||
cat > "${wrapper}" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "${ubsan}" ]]; then
|
||||
export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}"
|
||||
fi
|
||||
exec "\$1" "\${@:2}"
|
||||
EOF
|
||||
chmod +x "${wrapper}"
|
||||
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Clear sanitizer flags (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Clear global Rust flags so host/proc-macro builds don't pull in UBSan.
|
||||
echo "RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV"
|
||||
# Override any runner-level Cargo config rustflags as well.
|
||||
echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
|
||||
sanitize_flags() {
|
||||
local input="$1"
|
||||
input="${input//-fsanitize=undefined/}"
|
||||
input="${input//-fno-sanitize-recover=undefined/}"
|
||||
input="${input//-fno-sanitize-trap=undefined/}"
|
||||
echo "$input"
|
||||
}
|
||||
|
||||
cflags="$(sanitize_flags "${CFLAGS-}")"
|
||||
cxxflags="$(sanitize_flags "${CXXFLAGS-}")"
|
||||
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
||||
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
name: Configure musl rusty_v8 artifact overrides and verify checksums
|
||||
uses: ./.github/actions/setup-rusty-v8-musl
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
@@ -462,73 +521,235 @@ jobs:
|
||||
/var/cache/apt
|
||||
key: apt-${{ matrix.runner }}-${{ matrix.target }}-v1
|
||||
|
||||
tests_macos_aarch64:
|
||||
name: Tests — macos-15-xlarge - aarch64-apple-darwin
|
||||
uses: ./.github/workflows/rust-ci-full-nextest-platform.yml
|
||||
with:
|
||||
runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: ci-test
|
||||
artifact_id: macos-aarch64
|
||||
use_sccache: true
|
||||
secrets: inherit
|
||||
tests:
|
||||
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }}
|
||||
runs-on: ${{ matrix.runs_on || matrix.runner }}
|
||||
# Perhaps we can bring this back down to 30m once we finish the cutover
|
||||
# from tui_app_server/ to tui/. Incidentally, windows-arm64 was the main
|
||||
# offender for exceeding the timeout.
|
||||
timeout-minutes: 45
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# Speed up repeated builds across CI runs by caching compiled objects, except on
|
||||
# arm64 macOS runners cross-targeting x86_64 where ring/cc-rs can produce
|
||||
# mixed-architecture archives under sccache.
|
||||
USE_SCCACHE: ${{ (startsWith(matrix.runner, 'windows') || (matrix.runner == 'macos-15-xlarge' && matrix.target == 'x86_64-apple-darwin')) && 'false' || 'true' }}
|
||||
CARGO_INCREMENTAL: "0"
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
|
||||
tests_linux_x64_remote:
|
||||
name: Tests — ubuntu-24.04 - x86_64-unknown-linux-gnu (remote)
|
||||
uses: ./.github/workflows/rust-ci-full-nextest-platform.yml
|
||||
with:
|
||||
runner: ubuntu-24.04
|
||||
runner_group: codex-runners
|
||||
runner_labels: codex-linux-x64
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: ci-test
|
||||
artifact_id: linux-x64-remote
|
||||
remote_env: true
|
||||
use_sccache: true
|
||||
secrets: inherit
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
profile: dev
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
profile: dev
|
||||
remote_env: "true"
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-linux-arm64
|
||||
- runner: windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-x64
|
||||
- runner: windows-arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: dev
|
||||
runs_on:
|
||||
group: codex-runners
|
||||
labels: codex-windows-arm64
|
||||
|
||||
tests_linux_arm64:
|
||||
name: Tests — ubuntu-24.04-arm - aarch64-unknown-linux-gnu
|
||||
uses: ./.github/workflows/rust-ci-full-nextest-platform.yml
|
||||
with:
|
||||
runner: ubuntu-24.04-arm
|
||||
runner_group: codex-runners
|
||||
runner_labels: codex-linux-arm64
|
||||
target: aarch64-unknown-linux-gnu
|
||||
profile: ci-test
|
||||
artifact_id: linux-arm64
|
||||
use_sccache: true
|
||||
secrets: inherit
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Linux build dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev bubblewrap
|
||||
fi
|
||||
|
||||
tests_windows_x64:
|
||||
name: Tests — windows-x64 - x86_64-pc-windows-msvc
|
||||
uses: ./.github/workflows/rust-ci-full-nextest-platform.yml
|
||||
with:
|
||||
runner: windows-x64
|
||||
runner_group: codex-runners
|
||||
runner_labels: codex-windows-x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
profile: ci-test
|
||||
artifact_id: windows-x64
|
||||
test_threads: 8
|
||||
secrets: inherit
|
||||
# Some integration tests rely on DotSlash being installed.
|
||||
# See https://github.com/openai/codex/pull/7617.
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
tests_windows_arm64:
|
||||
name: Tests — windows-arm64 - aarch64-pc-windows-msvc
|
||||
uses: ./.github/workflows/rust-ci-full-nextest-platform.yml
|
||||
with:
|
||||
runner: windows-arm64
|
||||
runner_group: codex-runners
|
||||
runner_labels: codex-windows-arm64
|
||||
archive_runner: windows-x64
|
||||
archive_runner_group: codex-runners
|
||||
archive_runner_labels: codex-windows-x64
|
||||
target: aarch64-pc-windows-msvc
|
||||
profile: ci-test
|
||||
artifact_id: windows-arm64
|
||||
test_threads: 8
|
||||
use_sccache: true
|
||||
secrets: inherit
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Compute lockfile hash
|
||||
id: lockhash
|
||||
working-directory: codex-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "hash=$(sha256sum Cargo.lock | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
echo "toolchain_hash=$(sha256sum rust-toolchain.toml | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore cargo home cache
|
||||
id: cache_cargo_home_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
restore-keys: |
|
||||
cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- name: Install sccache
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: sccache
|
||||
version: 0.7.5
|
||||
|
||||
- name: Configure sccache backend
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${ACTIONS_CACHE_URL:-}" && -n "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
|
||||
echo "Using sccache GitHub backend"
|
||||
else
|
||||
echo "SCCACHE_GHA_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "SCCACHE_DIR=${{ github.workspace }}/.sccache" >> "$GITHUB_ENV"
|
||||
echo "Using sccache local disk + actions/cache fallback"
|
||||
fi
|
||||
|
||||
- name: Enable sccache wrapper
|
||||
if: ${{ env.USE_SCCACHE == 'true' }}
|
||||
shell: bash
|
||||
run: echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore sccache cache (fallback)
|
||||
if: ${{ env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true' }}
|
||||
id: cache_sccache_restore
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-
|
||||
sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-
|
||||
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: nextest
|
||||
version: 0.9.103
|
||||
|
||||
- name: Enable unprivileged user namespaces (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
# Required for bubblewrap to work on Linux CI runners.
|
||||
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
||||
# Ubuntu 24.04+ can additionally gate unprivileged user namespaces
|
||||
# behind AppArmor.
|
||||
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
|
||||
- name: Set up remote test env (Docker)
|
||||
if: ${{ runner.os == 'Linux' && matrix.remote_env == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export CODEX_TEST_REMOTE_ENV_CONTAINER_NAME=codex-remote-test-env
|
||||
source "${GITHUB_WORKSPACE}/scripts/test-remote-env.sh"
|
||||
echo "CODEX_TEST_REMOTE_ENV=${CODEX_TEST_REMOTE_ENV}" >> "$GITHUB_ENV"
|
||||
echo "CODEX_TEST_REMOTE_EXEC_SERVER_URL=${CODEX_TEST_REMOTE_EXEC_SERVER_URL}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: tests
|
||||
id: test
|
||||
run: cargo nextest run --no-fail-fast --target ${{ matrix.target }} --cargo-profile ci-test --timings
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUST_MIN_STACK: "8388608" # 8 MiB
|
||||
NEXTEST_STATUS_LEVEL: leak
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Save cargo home cache
|
||||
if: always() && !cancelled() && steps.cache_cargo_home_restore.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: cargo-home-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ steps.lockhash.outputs.toolchain_hash }}
|
||||
|
||||
- name: Save sccache cache (fallback)
|
||||
if: always() && !cancelled() && env.USE_SCCACHE == 'true' && env.SCCACHE_GHA_ENABLED != 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ${{ github.workspace }}/.sccache/
|
||||
key: sccache-${{ matrix.runner }}-${{ matrix.target }}-${{ matrix.profile }}-${{ steps.lockhash.outputs.hash }}-${{ github.run_id }}
|
||||
|
||||
- name: sccache stats
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
continue-on-error: true
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: sccache summary
|
||||
if: always() && env.USE_SCCACHE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### sccache stats — ${{ matrix.target }} (tests)";
|
||||
echo;
|
||||
echo '```';
|
||||
sccache --show-stats || true;
|
||||
echo '```';
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Tear down remote test env
|
||||
if: ${{ always() && runner.os == 'Linux' && matrix.remote_env == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
if [[ "${STEPS_TEST_OUTCOME}" != "success" ]]; then
|
||||
docker logs codex-remote-test-env || true
|
||||
fi
|
||||
docker rm -f codex-remote-test-env >/dev/null 2>&1 || true
|
||||
env:
|
||||
STEPS_TEST_OUTCOME: ${{ steps.test.outcome }}
|
||||
|
||||
- name: verify tests passed
|
||||
if: steps.test.outcome == 'failure'
|
||||
run: |
|
||||
echo "Tests failed. See logs for details."
|
||||
exit 1
|
||||
|
||||
# --- Gatherer job for the full post-merge workflow --------------------------
|
||||
results:
|
||||
@@ -540,11 +761,7 @@ jobs:
|
||||
argument_comment_lint_package,
|
||||
argument_comment_lint_prebuilt,
|
||||
lint_build,
|
||||
tests_macos_aarch64,
|
||||
tests_linux_x64_remote,
|
||||
tests_linux_arm64,
|
||||
tests_windows_x64,
|
||||
tests_windows_arm64,
|
||||
tests,
|
||||
]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -557,21 +774,13 @@ jobs:
|
||||
echo "general: ${{ needs.general.result }}"
|
||||
echo "shear : ${{ needs.cargo_shear.result }}"
|
||||
echo "lint : ${{ needs.lint_build.result }}"
|
||||
echo "test macos : ${{ needs.tests_macos_aarch64.result }}"
|
||||
echo "test linux : ${{ needs.tests_linux_x64_remote.result }}"
|
||||
echo "test arm64 : ${{ needs.tests_linux_arm64.result }}"
|
||||
echo "test winx64: ${{ needs.tests_windows_x64.result }}"
|
||||
echo "test winarm: ${{ needs.tests_windows_arm64.result }}"
|
||||
echo "tests : ${{ needs.tests.result }}"
|
||||
[[ '${{ needs.argument_comment_lint_package.result }}' == 'success' ]] || { echo 'argument_comment_lint_package failed'; exit 1; }
|
||||
[[ '${{ needs.argument_comment_lint_prebuilt.result }}' == 'success' ]] || { echo 'argument_comment_lint_prebuilt failed'; exit 1; }
|
||||
[[ '${{ needs.general.result }}' == 'success' ]] || { echo 'general failed'; exit 1; }
|
||||
[[ '${{ needs.cargo_shear.result }}' == 'success' ]] || { echo 'cargo_shear failed'; exit 1; }
|
||||
[[ '${{ needs.lint_build.result }}' == 'success' ]] || { echo 'lint_build failed'; exit 1; }
|
||||
[[ '${{ needs.tests_macos_aarch64.result }}' == 'success' ]] || { echo 'tests_macos_aarch64 failed'; exit 1; }
|
||||
[[ '${{ needs.tests_linux_x64_remote.result }}' == 'success' ]] || { echo 'tests_linux_x64_remote failed'; exit 1; }
|
||||
[[ '${{ needs.tests_linux_arm64.result }}' == 'success' ]] || { echo 'tests_linux_arm64 failed'; exit 1; }
|
||||
[[ '${{ needs.tests_windows_x64.result }}' == 'success' ]] || { echo 'tests_windows_x64 failed'; exit 1; }
|
||||
[[ '${{ needs.tests_windows_arm64.result }}' == 'success' ]] || { echo 'tests_windows_arm64 failed'; exit 1; }
|
||||
[[ '${{ needs.tests.result }}' == 'success' ]] || { echo 'tests failed'; exit 1; }
|
||||
|
||||
- name: sccache summary note
|
||||
if: always()
|
||||
|
||||
16
.github/workflows/rust-ci.yml
vendored
16
.github/workflows/rust-ci.yml
vendored
@@ -3,11 +3,6 @@ on:
|
||||
pull_request: {}
|
||||
workflow_dispatch:
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI across every Cargo invocation.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
jobs:
|
||||
# --- Detect what changed so the fast PR workflow only runs relevant jobs ----
|
||||
changed:
|
||||
@@ -72,16 +67,11 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: just
|
||||
- name: cargo fmt
|
||||
run: cargo fmt -- --config imports_granularity=Item --check
|
||||
- name: Rust benchmark smoke test
|
||||
run: just bench-smoke
|
||||
|
||||
cargo_shear:
|
||||
name: cargo shear
|
||||
@@ -96,7 +86,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
||||
with:
|
||||
tool: cargo-shear@1.11.2
|
||||
@@ -116,7 +106,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Install nightly argument-comment-lint toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -7,11 +7,6 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI across every Cargo invocation.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
jobs:
|
||||
skip:
|
||||
if: ${{ !inputs.publish }}
|
||||
@@ -65,7 +60,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
toolchain: nightly-2025-09-18
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
92
.github/workflows/rust-release-windows.yml
vendored
92
.github/workflows/rust-release-windows.yml
vendored
@@ -6,11 +6,19 @@ on:
|
||||
release-lto:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI across every Cargo invocation.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
secrets:
|
||||
AZURE_TRUSTED_SIGNING_CLIENT_ID:
|
||||
required: true
|
||||
AZURE_TRUSTED_SIGNING_TENANT_ID:
|
||||
required: true
|
||||
AZURE_TRUSTED_SIGNING_SUBSCRIPTION_ID:
|
||||
required: true
|
||||
AZURE_TRUSTED_SIGNING_ENDPOINT:
|
||||
required: true
|
||||
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME:
|
||||
required: true
|
||||
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build-windows-binaries:
|
||||
@@ -26,8 +34,6 @@ jobs:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ inputs.release-lto }}
|
||||
CARGO_PROFILE_RELEASE_DEBUG: full
|
||||
CARGO_PROFILE_RELEASE_STRIP: "false"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -94,22 +100,18 @@ jobs:
|
||||
Write-Host "Total RAM: $ramGiB GiB"
|
||||
Write-Host "Disk usage:"
|
||||
Get-PSDrive -PSProvider FileSystem | Format-Table -AutoSize Name, @{Name='Size(GB)';Expression={[math]::Round(($_.Used + $_.Free) / 1GB, 1)}}, @{Name='Free(GB)';Expression={[math]::Round($_.Free / 1GB, 1)}}
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Cargo build (Windows binaries)
|
||||
shell: bash
|
||||
run: |
|
||||
target="${{ matrix.target }}"
|
||||
if [[ "$target" == "x86_64-pc-windows-msvc" ]]; then
|
||||
export LIBSQLITE3_FLAGS=SQLITE_DISABLE_INTRINSIC
|
||||
fi
|
||||
build_args=()
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
build_args+=(--bin "$binary")
|
||||
done
|
||||
cargo build --target "$target" --release --timings "${build_args[@]}"
|
||||
cargo build --target ${{ matrix.target }} --release --timings "${build_args[@]}"
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
@@ -121,22 +123,10 @@ jobs:
|
||||
- name: Stage Windows binaries
|
||||
shell: bash
|
||||
run: |
|
||||
release_dir="target/${{ matrix.target }}/release"
|
||||
output_dir="$release_dir/staged-${{ matrix.bundle }}"
|
||||
output_dir="target/${{ matrix.target }}/release/staged-${{ matrix.bundle }}"
|
||||
mkdir -p "$output_dir"
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
pdb_name="${binary//-/_}"
|
||||
pdb_path="$release_dir/${pdb_name}.pdb"
|
||||
if [[ ! -f "$pdb_path" ]]; then
|
||||
pdb_path="$release_dir/${binary}.pdb"
|
||||
fi
|
||||
if [[ ! -f "$pdb_path" ]]; then
|
||||
echo "PDB for $binary not found at $release_dir/${pdb_name}.pdb or $release_dir/${binary}.pdb" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$release_dir/${binary}.exe" "$output_dir/${binary}.exe"
|
||||
cp "$pdb_path" "$output_dir/${binary}.pdb"
|
||||
cp "target/${{ matrix.target }}/release/${binary}.exe" "$output_dir/${binary}.exe"
|
||||
done
|
||||
|
||||
- name: Upload Windows binaries
|
||||
@@ -151,9 +141,6 @@ jobs:
|
||||
- build-windows-binaries
|
||||
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
environment:
|
||||
name: azure-artifact-signing
|
||||
deployment: false
|
||||
timeout-minutes: 90
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -222,23 +209,6 @@ jobs:
|
||||
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
|
||||
|
||||
- name: Build symbols archive
|
||||
shell: bash
|
||||
run: |
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/archive-release-symbols-and-strip-binaries.sh" \
|
||||
--target "${{ matrix.target }}" \
|
||||
--artifact-name "${{ matrix.target }}" \
|
||||
--release-dir "target/${{ matrix.target }}/release" \
|
||||
--archive-dir "symbols-dist/${{ matrix.target }}" \
|
||||
--binaries "${WINDOWS_BINARIES}"
|
||||
|
||||
- name: Upload symbols archive
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ matrix.target }}-symbols
|
||||
path: codex-rs/symbols-dist/${{ matrix.target }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stage artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -250,21 +220,6 @@ jobs:
|
||||
"$dest/${binary}-${{ matrix.target }}.exe"
|
||||
done
|
||||
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- name: Build Codex package archives
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for bundle in primary app-server; do
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "${{ matrix.target }}" \
|
||||
--bundle "$bundle" \
|
||||
--entrypoint-dir "target/${{ matrix.target }}/release" \
|
||||
--archive-dir "dist/${{ matrix.target }}"
|
||||
done
|
||||
|
||||
- name: Build Python runtime wheel
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -288,12 +243,16 @@ jobs:
|
||||
|
||||
stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}"
|
||||
wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}"
|
||||
# Keep the helpers next to codex.exe in the runtime wheel so Windows
|
||||
# sandbox/elevation lookup matches the standalone release zip.
|
||||
python "${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
|
||||
stage-runtime \
|
||||
"$stage_dir" \
|
||||
"dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz" \
|
||||
"${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/codex.exe" \
|
||||
--codex-version "${GITHUB_REF_NAME}" \
|
||||
--platform-tag "$platform_tag"
|
||||
--platform-tag "$platform_tag" \
|
||||
--resource-binary "${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/codex-command-runner.exe" \
|
||||
--resource-binary "${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe"
|
||||
"${RUNNER_TEMP}/python-runtime-build-venv/Scripts/python.exe" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
||||
|
||||
- name: Upload Python runtime wheel
|
||||
@@ -303,6 +262,9 @@ jobs:
|
||||
path: python-runtime-dist/${{ matrix.target }}/*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install DotSlash
|
||||
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
||||
|
||||
- name: Compress artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -321,7 +283,7 @@ jobs:
|
||||
base="$(basename "$f")"
|
||||
# Skip files that are already archives (shouldn't happen, but be
|
||||
# safe).
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
4
.github/workflows/rust-release-zsh.yml
vendored
4
.github/workflows/rust-release-zsh.yml
vendored
@@ -69,10 +69,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15-large
|
||||
target: x86_64-apple-darwin
|
||||
variant: macos-15
|
||||
archive_name: codex-zsh-x86_64-apple-darwin.tar.gz
|
||||
- runner: macos-15-xlarge
|
||||
target: aarch64-apple-darwin
|
||||
variant: macos-15
|
||||
|
||||
242
.github/workflows/rust-release.yml
vendored
242
.github/workflows/rust-release.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Validate tag matches Cargo.toml version
|
||||
shell: bash
|
||||
env:
|
||||
@@ -149,14 +149,6 @@ 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' }}
|
||||
CARGO_PROFILE_RELEASE_DEBUG: full
|
||||
CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO: ${{ contains(matrix.target, 'apple-darwin') && 'packed' || 'off' }}
|
||||
CARGO_PROFILE_RELEASE_STRIP: "false"
|
||||
# Use the git CLI instead of Cargo's libgit2 path for git dependencies.
|
||||
# macOS release runners have intermittently failed to fetch nested
|
||||
# submodules through SecureTransport/libgit2, especially libwebrtc's
|
||||
# libyuv submodule from chromium.googlesource.com.
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
SIGN_MACOS: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
|
||||
strategy:
|
||||
@@ -188,25 +180,25 @@ jobs:
|
||||
binaries: "codex-app-server"
|
||||
build_dmg: "false"
|
||||
# Release artifacts intentionally ship MUSL-linked Linux binaries.
|
||||
- runner: codex-linux-x64-xl
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: x86_64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: codex-linux-x64-xl
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
bundle: app-server
|
||||
artifact_name: x86_64-unknown-linux-musl-app-server
|
||||
binaries: "codex-app-server"
|
||||
build_dmg: "false"
|
||||
- runner: codex-linux-arm64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: primary
|
||||
artifact_name: aarch64-unknown-linux-musl
|
||||
binaries: "codex codex-responses-api-proxy bwrap"
|
||||
build_dmg: "false"
|
||||
- runner: codex-linux-arm64
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
bundle: app-server
|
||||
artifact_name: aarch64-unknown-linux-musl-app-server
|
||||
@@ -252,8 +244,17 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends binutils pkg-config libcap-dev
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
||||
- name: Install UBSan runtime (musl)
|
||||
if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1
|
||||
fi
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -282,7 +283,30 @@ jobs:
|
||||
run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Disable aws-lc jitter entropy (musl)
|
||||
name: Configure rustc UBSan wrapper (musl host)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ubsan=""
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')"
|
||||
fi
|
||||
wrapper_root="${RUNNER_TEMP:-/tmp}"
|
||||
wrapper="${wrapper_root}/rustc-ubsan-wrapper"
|
||||
cat > "${wrapper}" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "${ubsan}" ]]; then
|
||||
export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}"
|
||||
fi
|
||||
exec "\$1" "\${@:2}"
|
||||
EOF
|
||||
chmod +x "${wrapper}"
|
||||
echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV"
|
||||
echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
||||
name: Clear sanitizer flags (musl)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -292,12 +316,37 @@ jobs:
|
||||
target_no_jitter="${target_no_jitter//-/_}"
|
||||
echo "${target_no_jitter}=1" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Configure rusty_v8 artifact overrides and verify checksums
|
||||
uses: ./.github/actions/setup-rusty-v8
|
||||
# Clear global Rust flags so host/proc-macro builds don't pull in UBSan.
|
||||
echo "RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV"
|
||||
# Override any runner-level Cargo config rustflags as well.
|
||||
echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV"
|
||||
|
||||
sanitize_flags() {
|
||||
local input="$1"
|
||||
input="${input//-fsanitize=undefined/}"
|
||||
input="${input//-fno-sanitize-recover=undefined/}"
|
||||
input="${input//-fno-sanitize-trap=undefined/}"
|
||||
echo "$input"
|
||||
}
|
||||
|
||||
cflags="$(sanitize_flags "${CFLAGS-}")"
|
||||
cxxflags="$(sanitize_flags "${CXXFLAGS-}")"
|
||||
echo "CFLAGS=${cflags}" >> "$GITHUB_ENV"
|
||||
echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||
name: Configure musl rusty_v8 artifact overrides and verify checksums
|
||||
uses: ./.github/actions/setup-rusty-v8-musl
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- if: ${{ contains(matrix.target, 'linux') }}
|
||||
- if: ${{ contains(matrix.target, 'linux') && matrix.bundle == 'primary' }}
|
||||
name: Build bwrap and export digest
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -311,10 +360,6 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Codex embeds this digest at build time and verifies the bundled
|
||||
# bwrap resource before use. Strip bwrap before hashing so the digest
|
||||
# covers the exact bytes that the release packages.
|
||||
strip --strip-debug --strip-unneeded "$bwrap_path"
|
||||
digest="$(sha256sum "$bwrap_path" | awk '{print $1}')"
|
||||
echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV"
|
||||
echo "Built bwrap ${bwrap_path} with sha256:${digest}"
|
||||
@@ -322,21 +367,12 @@ jobs:
|
||||
- name: Cargo build
|
||||
shell: bash
|
||||
run: |
|
||||
target="${{ matrix.target }}"
|
||||
if [[ "$target" == "x86_64-pc-windows-msvc" ]]; then
|
||||
export LIBSQLITE3_FLAGS=SQLITE_DISABLE_INTRINSIC
|
||||
fi
|
||||
build_args=()
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# bwrap was built, finalized, and hashed before this build so
|
||||
# Codex can embed the digest of the bytes that will be packaged.
|
||||
if [[ "$binary" == "bwrap" ]]; then
|
||||
continue
|
||||
fi
|
||||
build_args+=(--bin "$binary")
|
||||
done
|
||||
echo "CARGO_PROFILE_RELEASE_LTO: ${CARGO_PROFILE_RELEASE_LTO}"
|
||||
cargo build --target "$target" --release --timings "${build_args[@]}"
|
||||
cargo build --target ${{ matrix.target }} --release --timings "${build_args[@]}"
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
@@ -345,32 +381,6 @@ jobs:
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Build symbols archive and strip binaries
|
||||
shell: bash
|
||||
run: |
|
||||
binaries=()
|
||||
for binary in ${{ matrix.binaries }}; do
|
||||
# bwrap is already stripped before hashing. Its symbols are not
|
||||
# useful enough to justify a separate pre-Codex symbols pass.
|
||||
if [[ "$binary" == "bwrap" ]]; then
|
||||
continue
|
||||
fi
|
||||
binaries+=("$binary")
|
||||
done
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/archive-release-symbols-and-strip-binaries.sh" \
|
||||
--target "${{ matrix.target }}" \
|
||||
--artifact-name "${{ matrix.artifact_name }}" \
|
||||
--release-dir "target/${{ matrix.target }}/release" \
|
||||
--archive-dir "symbols-dist/${{ matrix.artifact_name }}" \
|
||||
--binaries "${binaries[*]}"
|
||||
|
||||
- name: Upload symbols archive
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-symbols
|
||||
path: codex-rs/symbols-dist/${{ matrix.artifact_name }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
- if: ${{ runner.os == 'macOS' && env.SIGN_MACOS != 'true' }}
|
||||
name: Stage unsigned macOS artifacts
|
||||
shell: bash
|
||||
@@ -509,20 +519,6 @@ jobs:
|
||||
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
|
||||
fi
|
||||
|
||||
- name: Build Codex package archive
|
||||
if: ${{ runner.os != 'macOS' || env.SIGN_MACOS == 'true' }}
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
BUNDLE: ${{ matrix.bundle }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "$TARGET" \
|
||||
--bundle "$BUNDLE" \
|
||||
--entrypoint-dir "target/${TARGET}/release" \
|
||||
--archive-dir "dist/${TARGET}"
|
||||
|
||||
- name: Build Python runtime wheel
|
||||
if: ${{ matrix.bundle == 'primary' && (runner.os != 'macOS' || env.SIGN_MACOS == 'true') }}
|
||||
shell: bash
|
||||
@@ -537,10 +533,10 @@ jobs:
|
||||
platform_tag="macosx_10_9_x86_64"
|
||||
;;
|
||||
aarch64-unknown-linux-musl)
|
||||
platform_tag="manylinux_2_17_aarch64"
|
||||
platform_tag="musllinux_1_1_aarch64"
|
||||
;;
|
||||
x86_64-unknown-linux-musl)
|
||||
platform_tag="manylinux_2_17_x86_64"
|
||||
platform_tag="musllinux_1_1_x86_64"
|
||||
;;
|
||||
*)
|
||||
echo "No Python runtime wheel platform tag for ${{ matrix.target }}"
|
||||
@@ -559,10 +555,18 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py"
|
||||
stage-runtime
|
||||
"$stage_dir"
|
||||
"dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz"
|
||||
"${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/codex"
|
||||
--codex-version "${GITHUB_REF_NAME}"
|
||||
--platform-tag "$platform_tag"
|
||||
)
|
||||
if [[ "${{ matrix.target }}" == *linux* ]]; then
|
||||
# Keep bwrap in the runtime wheel so Linux sandbox fallback behavior
|
||||
# matches the standalone release bundle on hosts without system bwrap.
|
||||
stage_runtime_args+=(
|
||||
--resource-binary
|
||||
"${GITHUB_WORKSPACE}/codex-rs/target/${{ matrix.target }}/release/bwrap"
|
||||
)
|
||||
fi
|
||||
python3 "${stage_runtime_args[@]}"
|
||||
"${RUNNER_TEMP}/python-runtime-build-venv/bin/python" -m build --wheel --outdir "$wheel_dir" "$stage_dir"
|
||||
|
||||
@@ -782,20 +786,6 @@ jobs:
|
||||
cp "$dmg_source" "$dest/$dmg_name"
|
||||
fi
|
||||
|
||||
- name: Build Codex package archive
|
||||
shell: bash
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
BUNDLE: ${{ matrix.bundle }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \
|
||||
--target "$TARGET" \
|
||||
--bundle "$BUNDLE" \
|
||||
--entrypoint-dir "dist/${TARGET}" \
|
||||
--archive-dir "dist/${TARGET}" \
|
||||
--target-suffixed-entrypoint
|
||||
|
||||
- name: Build Python runtime wheel
|
||||
if: ${{ matrix.bundle == 'primary' }}
|
||||
shell: bash
|
||||
@@ -824,7 +814,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \
|
||||
stage-runtime \
|
||||
"$stage_dir" \
|
||||
"dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz" \
|
||||
"${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"
|
||||
@@ -865,6 +855,7 @@ jobs:
|
||||
uses: ./.github/workflows/rust-release-windows.yml
|
||||
with:
|
||||
release-lto: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
|
||||
secrets: inherit
|
||||
|
||||
argument-comment-lint-release-assets:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.release_mode != 'promote_signed' }}
|
||||
@@ -1068,7 +1059,6 @@ jobs:
|
||||
run: |
|
||||
find dist -mindepth 1 -maxdepth 1 -type d \
|
||||
! -name '*-apple-darwin*-unsigned' \
|
||||
! -name '*-symbols' \
|
||||
! -name 'aarch64-unknown-linux-musl' \
|
||||
! -name 'aarch64-unknown-linux-musl-app-server' \
|
||||
! -name 'x86_64-unknown-linux-musl' \
|
||||
@@ -1093,29 +1083,6 @@ jobs:
|
||||
|
||||
ls -R dist/
|
||||
|
||||
- name: Add Codex package checksum manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
manifest="dist/codex-package_SHA256SUMS"
|
||||
tmp_manifest="$(mktemp)"
|
||||
find dist -type f \
|
||||
\( -name 'codex-package-*.tar.gz' -o -name 'codex-app-server-package-*.tar.gz' \) \
|
||||
-print |
|
||||
sort |
|
||||
while IFS= read -r archive; do
|
||||
sha256sum "$archive" |
|
||||
awk -v name="$(basename "$archive")" '{ print $1 " " name }'
|
||||
done > "$tmp_manifest"
|
||||
|
||||
if [[ ! -s "$tmp_manifest" ]]; then
|
||||
echo "No Codex package archives found for checksum manifest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$tmp_manifest" "$manifest"
|
||||
cat "$manifest"
|
||||
|
||||
- name: Add config schema release asset
|
||||
run: |
|
||||
cp codex-rs/core/config.schema.json dist/config-schema.json
|
||||
@@ -1190,6 +1157,8 @@ jobs:
|
||||
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:
|
||||
@@ -1273,6 +1242,19 @@ jobs:
|
||||
tag: ${{ github.ref_name }}
|
||||
config: .github/dotslash-argument-comment-lint-config.json
|
||||
|
||||
- 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: ${{ 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 }}
|
||||
run: |
|
||||
if ! curl -sS -f -o /dev/null -X POST "$DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL"; then
|
||||
echo "::warning title=developers.openai.com deploy hook failed::Vercel deploy hook POST failed for ${GITHUB_REF_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Publish to npm using OIDC authentication.
|
||||
# July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/
|
||||
# npm docs: https://docs.npmjs.com/trusted-publishers
|
||||
@@ -1485,36 +1467,6 @@ jobs:
|
||||
packages-dir: dist/python-runtime
|
||||
skip-existing: true
|
||||
|
||||
deploy-dev-website:
|
||||
name: Trigger developers.openai.com deploy
|
||||
needs: release
|
||||
# Only trigger the deploy for a stable signed release.
|
||||
# The deploy updates developers.openai.com with the new config schema json file.
|
||||
if: >-
|
||||
${{
|
||||
!cancelled() &&
|
||||
needs.release.result == 'success' &&
|
||||
needs.release.outputs.sign_macos == 'true' &&
|
||||
!contains(needs.release.outputs.version, '-')
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
permissions: {}
|
||||
environment:
|
||||
name: dev-website-vercel-deploy
|
||||
deployment: false
|
||||
|
||||
steps:
|
||||
- name: Trigger developers.openai.com deploy
|
||||
continue-on-error: true
|
||||
env:
|
||||
DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL: ${{ secrets.DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL }}
|
||||
run: |
|
||||
if ! curl -sS -f -o /dev/null -X POST "$DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL"; then
|
||||
echo "::warning title=developers.openai.com deploy hook failed::Vercel deploy hook POST failed for ${GITHUB_REF_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
winget:
|
||||
name: winget
|
||||
needs: release
|
||||
|
||||
183
.github/workflows/rusty-v8-release.yml
vendored
183
.github/workflows/rusty-v8-release.yml
vendored
@@ -5,11 +5,6 @@ on:
|
||||
tags:
|
||||
- "rusty-v8-v*.*.*"
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI for Cargo smoke tests.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
@@ -51,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 expected release tag ${expected_release_tag}." >&2
|
||||
echo "Tag ${release_tag} does not match resolved v8 crate version ${V8_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.variant }} ${{ matrix.target }}
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
@@ -69,77 +64,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
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
|
||||
@@ -156,115 +85,61 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Rust toolchain for Cargo smoke
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
toolchain: "1.95.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_kind="release_pair"
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
pair_kind="sandbox_release_pair"
|
||||
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"
|
||||
)
|
||||
fi
|
||||
pair_target="//third_party/v8:rusty_v8_${pair_kind}_${target_suffix}"
|
||||
|
||||
bazel_args=(
|
||||
build
|
||||
-c
|
||||
opt
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=rusty-v8-upstream-libcxx
|
||||
--config=v8-release-compat
|
||||
"${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=${{ matrix.bazel_config }}" \
|
||||
--config=ci-v8 \
|
||||
"--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
|
||||
|
||||
stage_args=(
|
||||
--platform "${PLATFORM}"
|
||||
--target "${TARGET}"
|
||||
--compilation-mode opt
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--compilation-mode opt \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
--bazel-config "${BAZEL_CONFIG}"
|
||||
)
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
stage_args+=(--sandbox)
|
||||
else
|
||||
stage_args+=(--bazel-config v8-release-compat)
|
||||
fi
|
||||
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
|
||||
|
||||
- name: Smoke test staged artifact with Cargo
|
||||
env:
|
||||
SANDBOX: ${{ matrix.sandbox }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
host_arch="$(uname -m)"
|
||||
case "${TARGET}:${host_arch}" in
|
||||
x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
|
||||
;;
|
||||
*)
|
||||
echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
|
||||
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
|
||||
if [[ -z "${archive}" || -z "${binding}" ]]; then
|
||||
echo "Missing staged archive or binding for ${TARGET}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo_args=(test -p codex-v8-poc)
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
cargo_args+=(--features sandbox)
|
||||
fi
|
||||
|
||||
(
|
||||
cd codex-rs
|
||||
CARGO_TARGET_DIR="${RUNNER_TEMP}/rusty-v8-cargo-smoke-${TARGET}-${SANDBOX}" \
|
||||
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
|
||||
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
|
||||
cargo "${cargo_args[@]}"
|
||||
)
|
||||
|
||||
- name: Upload staged artifacts
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.variant }}-${{ matrix.target }}
|
||||
name: rusty-v8-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
publish-release:
|
||||
@@ -277,8 +152,7 @@ jobs:
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Check whether release already exists
|
||||
id: release
|
||||
- name: Ensure release tag is new
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
|
||||
@@ -287,9 +161,8 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
if gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" > /dev/null 2>&1; then
|
||||
echo "exists=true" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "exists=false" >> "${GITHUB_OUTPUT}"
|
||||
echo "Release tag ${RELEASE_TAG} already exists; musl artifact tags are immutable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
@@ -297,7 +170,6 @@ 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 }}
|
||||
@@ -305,14 +177,3 @@ 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
|
||||
|
||||
6
.github/workflows/sdk.yml
vendored
6
.github/workflows/sdk.yml
vendored
@@ -23,15 +23,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Run inside a glibc Linux image so dependency resolution exercises
|
||||
# the pinned manylinux runtime wheel that users install.
|
||||
# Run inside Alpine so dependency resolution exercises the pinned
|
||||
# runtime wheel on the same Linux wheel family that CI installs.
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-e HOME=/tmp/codex-python-sdk-home \
|
||||
-e UV_LINK_MODE=copy \
|
||||
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
|
||||
-w "${GITHUB_WORKSPACE}/sdk/python" \
|
||||
python:3.12-slim \
|
||||
python:3.12-alpine \
|
||||
sh -euxc '
|
||||
python -m venv /tmp/uv
|
||||
/tmp/uv/bin/python -m pip install uv==0.11.3
|
||||
|
||||
304
.github/workflows/v8-canary.yml
vendored
304
.github/workflows/v8-canary.yml
vendored
@@ -3,45 +3,32 @@ 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:
|
||||
|
||||
# Cargo's libgit2 transport has been flaky when fetching git dependencies with
|
||||
# nested submodules. Prefer the system git CLI for Cargo builds and smoke tests.
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}::${{ github.event.pull_request.number > 0 && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
|
||||
cancel-in-progress: ${{ github.ref_name != 'main' }}
|
||||
@@ -72,7 +59,7 @@ jobs:
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.variant }} ${{ matrix.target }}
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: metadata
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
@@ -83,77 +70,12 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
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:
|
||||
@@ -170,247 +92,53 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Rust toolchain for Cargo smoke
|
||||
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
toolchain: "1.95.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_kind="release_pair"
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
pair_kind="sandbox_release_pair"
|
||||
fi
|
||||
pair_target="//third_party/v8:rusty_v8_${pair_kind}_${target_suffix}"
|
||||
pair_target="//third_party/v8:rusty_v8_release_pair_${target_suffix}"
|
||||
extra_targets=(
|
||||
"@llvm//runtimes/libcxx:libcxx.static"
|
||||
"@llvm//runtimes/libcxx:libcxxabi.static"
|
||||
)
|
||||
|
||||
bazel_args=(
|
||||
build
|
||||
"--platforms=@llvm//platforms:${PLATFORM}"
|
||||
--config=rusty-v8-upstream-libcxx
|
||||
--config=v8-release-compat
|
||||
"${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=${{ matrix.bazel_config }}" \
|
||||
--config=ci-v8 \
|
||||
"--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
|
||||
|
||||
stage_args=(
|
||||
--platform "${PLATFORM}"
|
||||
--target "${TARGET}"
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
|
||||
--platform "${PLATFORM}" \
|
||||
--target "${TARGET}" \
|
||||
--bazel-config v8-release-compat \
|
||||
--output-dir "dist/${TARGET}"
|
||||
--bazel-config "${BAZEL_CONFIG}"
|
||||
)
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
stage_args+=(--sandbox)
|
||||
else
|
||||
stage_args+=(--bazel-config v8-release-compat)
|
||||
fi
|
||||
|
||||
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair "${stage_args[@]}"
|
||||
|
||||
- name: Smoke test staged artifact with Cargo
|
||||
env:
|
||||
SANDBOX: ${{ matrix.sandbox }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
host_arch="$(uname -m)"
|
||||
case "${TARGET}:${host_arch}" in
|
||||
x86_64-apple-darwin:x86_64|aarch64-apple-darwin:arm64|x86_64-unknown-linux-gnu:x86_64|aarch64-unknown-linux-gnu:aarch64)
|
||||
;;
|
||||
*)
|
||||
echo "Skipping non-native Cargo smoke for ${TARGET} on ${host_arch}."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
archive="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'librusty_v8_*.a.gz' -print -quit)"
|
||||
binding="$(find "dist/${TARGET}" -maxdepth 1 -type f -name 'src_binding_*.rs' -print -quit)"
|
||||
if [[ -z "${archive}" || -z "${binding}" ]]; then
|
||||
echo "Missing staged archive or binding for ${TARGET}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo_args=(test -p codex-v8-poc)
|
||||
if [[ "${SANDBOX}" == "true" ]]; then
|
||||
cargo_args+=(--features sandbox)
|
||||
fi
|
||||
|
||||
(
|
||||
cd codex-rs
|
||||
CARGO_TARGET_DIR="${RUNNER_TEMP}/rusty-v8-cargo-smoke-${TARGET}-${SANDBOX}" \
|
||||
RUSTY_V8_ARCHIVE="${GITHUB_WORKSPACE}/${archive}" \
|
||||
RUSTY_V8_SRC_BINDING_PATH="${GITHUB_WORKSPACE}/${binding}" \
|
||||
cargo "${cargo_args[@]}"
|
||||
)
|
||||
|
||||
- name: Upload staged artifacts
|
||||
- name: Upload staged musl artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
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@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
toolchain: "1.95.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.95.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 }}
|
||||
name: v8-canary-${{ needs.metadata.outputs.v8_version }}-${{ matrix.target }}
|
||||
path: dist/${{ matrix.target }}/*
|
||||
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"BazelBuild.vscode-bazel",
|
||||
"rust-lang.rust-analyzer",
|
||||
"charliermarsh.ruff",
|
||||
"tamasfe.even-better-toml",
|
||||
|
||||
79
AGENTS.md
79
AGENTS.md
@@ -30,7 +30,6 @@ In the codex-rs folder where the rust code lives:
|
||||
- Prefer private modules and explicitly exported public crate API.
|
||||
- If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`.
|
||||
- When working with MCP tool calls, prefer using `codex-rs/codex-mcp/src/mcp_connection_manager.rs` to handle mutation of tools and tool calls. Aim to minimize the footprint of changes and leverage existing abstractions rather than plumbing code through multiple levels of function calls.
|
||||
- Do not call `reset_client_session` unnecessarily; let the incremental check logic decide whether to reuse the previous request.
|
||||
- If you change Rust dependencies (`Cargo.toml` or `Cargo.lock`), run `just bazel-lock-update` from the
|
||||
repo root to refresh `MODULE.bazel.lock`, and include that lockfile update in the same change.
|
||||
- After dependency changes, run `just bazel-lock-check` from the repo root so lockfile drift is caught
|
||||
@@ -53,13 +52,12 @@ In the codex-rs folder where the rust code lives:
|
||||
the new implementation so the invariants stay close to the code that owns them.
|
||||
- Avoid adding new standalone methods to `codex-rs/tui/src/chatwidget.rs` unless the change is
|
||||
trivial; prefer new modules/files and keep `chatwidget.rs` focused on orchestration.
|
||||
- When running Rust commands (e.g. `just fix` or `just test`) be patient with the command and never try to kill them using the PID. Rust lock can make the execution slow, this is expected.
|
||||
- When running Rust commands (e.g. `just fix` or `cargo test`) be patient with the command and never try to kill them using the PID. Rust lock can make the execution slow, this is expected.
|
||||
|
||||
Run `just fmt` (in the `codex-rs` directory) automatically after you have finished making code changes anywhere in this repository; do not ask for approval to run it. Additionally, run the tests:
|
||||
Run `just fmt` (in `codex-rs` directory) automatically after you have finished making Rust code changes; do not ask for approval to run it. Additionally, run the tests:
|
||||
|
||||
1. Do not run `cargo test` directly. Use `just test` so test execution follows the repo defaults.
|
||||
2. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `just test -p codex-tui`.
|
||||
3. Once those pass, if any changes were made in common, core, or protocol, run the complete test suite with `just test`. Avoid `--all-features` for routine local runs because it expands the build matrix and can significantly increase `target/` disk usage; use it only when you specifically need full feature coverage. project-specific or individual tests can be run without asking the user, but do ask the user before running the complete test suite.
|
||||
1. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `cargo test -p codex-tui`.
|
||||
2. Once those pass, if any changes were made in common, core, or protocol, run the complete test suite with `cargo test` (or `just test` if `cargo-nextest` is installed). Avoid `--all-features` for routine local runs because it expands the build matrix and can significantly increase `target/` disk usage; use it only when you specifically need full feature coverage. project-specific or individual tests can be run without asking the user, but do ask the user before running the complete test suite.
|
||||
|
||||
Before finalizing a large change to `codex-rs`, run `just fix -p <project>` (in `codex-rs` directory) to fix any linter issues in the code. Prefer scoping with `-p` to avoid slow workspace‑wide Clippy builds; only run `just fix` without `-p` if you changed shared crates. Do not re-run tests after running `fix` or `fmt`.
|
||||
|
||||
@@ -76,49 +74,6 @@ Particularly when introducing a new concept/feature/API, before adding to `codex
|
||||
|
||||
Likewise, when reviewing code, do not hesitate to push back on PRs that would unnecessarily add code to `codex-core`.
|
||||
|
||||
## Code Review Rules
|
||||
|
||||
### Model visible context
|
||||
|
||||
Codex maintains a context (history of messages) that is sent to the model in inference requests.
|
||||
|
||||
1. No history rewrite - the context must be built up incrementally.
|
||||
2. Avoid frequent changes to context that cause cache misses.
|
||||
3. No unbounded items - everything injected in the model context must have a bounded size and a hard cap.
|
||||
4. No items larger than 10K tokens.
|
||||
5. Highlight new individual items that can cross >1k tokens as P0. These need an additional manual review.
|
||||
6. All injected fragments must be defined as structs in `core/context` and implement ContextualUserFragment trait
|
||||
|
||||
### Breaking changes
|
||||
|
||||
Search for breaking changes in external integration surfaces:
|
||||
|
||||
- app-server APIs
|
||||
- CLI parameters
|
||||
- configuration loading
|
||||
- resuming sessions from existing rollouts
|
||||
|
||||
### Test authoring guidance
|
||||
|
||||
For agent changes prefer integration tests over unit tests. Integration tests are under `core/suite` and use `test_codex` to set up a test instance of codex.
|
||||
|
||||
Features that change the agent logic MUST add an integration test:
|
||||
|
||||
- Provide a list of major logic changes and user-facing behaviors that need to be tested.
|
||||
|
||||
If unit tests are needed, put them in a dedicated test file (\*\_tests.rs).
|
||||
Avoid test-only functions in the main implementation.
|
||||
|
||||
Check whether there are existing helpers to make tests more streamlined and readable.
|
||||
|
||||
### Change size guidance (800 lines)
|
||||
|
||||
Unless the change is mechanical the total number of changed lines should not exceed 800 lines.
|
||||
For complex logic changes the size should be under 500 lines.
|
||||
|
||||
If the change is larger, explore whether it can be split into reviewable stages and identify the smallest coherent stage to land first.
|
||||
Base the staging suggestion on the actual diff, dependencies, and affected call sites.
|
||||
|
||||
## TUI style conventions
|
||||
|
||||
See `codex-rs/tui/styles.md`.
|
||||
@@ -153,19 +108,6 @@ See `codex-rs/tui/styles.md`.
|
||||
|
||||
## Tests
|
||||
|
||||
### Test module organization
|
||||
|
||||
- When adding a new test module, define its contents in a separate sibling file rather than inline in the implementation file.
|
||||
- Use an explicit `#[path = "..._tests.rs"]` attribute so the test filename is descriptive and easy to locate:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
#[path = "parser_tests.rs"]
|
||||
mod tests;
|
||||
```
|
||||
|
||||
- This applies only when introducing a new test module. Do not move or rewrite existing inline `#[cfg(test)] mod tests { ... }` modules solely to follow this convention.
|
||||
|
||||
### Snapshot tests
|
||||
|
||||
This repo uses snapshot tests (via `insta`), especially in `codex-rs/tui`, to validate rendered output.
|
||||
@@ -178,7 +120,7 @@ is easy to review and future diffs stay visual.
|
||||
When UI or text output changes intentionally, update the snapshots as follows:
|
||||
|
||||
- Run tests to generate any updated snapshots:
|
||||
- `just test -p codex-tui`
|
||||
- `cargo test -p codex-tui`
|
||||
- Check what’s pending:
|
||||
- `cargo insta pending-snapshots -p codex-tui`
|
||||
- Review changes by reading the generated `*.snap.new` files directly in the repo, or preview a specific file:
|
||||
@@ -272,15 +214,6 @@ These guidelines apply to app-server protocol work in `codex-rs`, especially:
|
||||
- Regenerate schema fixtures when API shapes change:
|
||||
`just write-app-server-schema`
|
||||
(and `just write-app-server-schema --experimental` when experimental API fixtures are affected).
|
||||
- Validate with `just test -p codex-app-server-protocol`.
|
||||
- Validate with `cargo test -p codex-app-server-protocol`.
|
||||
- Avoid boilerplate tests that only assert experimental field markers for individual
|
||||
request fields in `common.rs`; rely on schema generation/tests and behavioral coverage instead.
|
||||
|
||||
## Python Development Best Practices
|
||||
|
||||
### Ignore Python 2 compatibility
|
||||
|
||||
This project uses Python 3+. You should not use the `__future__` module.
|
||||
|
||||
If you need to worry about feature compatibility between different 3.xx point releases, check the
|
||||
closest `pyproject.toml`'s `requires-python` field to see what minimum runtime version is supported.
|
||||
|
||||
138
MODULE.bazel
138
MODULE.bazel
@@ -10,7 +10,6 @@ single_version_override(
|
||||
module_name = "llvm",
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
"//patches:llvm_rusty_v8_custom_libcxx.patch",
|
||||
"//patches:llvm_windows_symlink_extract.patch",
|
||||
],
|
||||
)
|
||||
@@ -78,13 +77,6 @@ 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
|
||||
@@ -163,7 +155,7 @@ use_repo(nightly_rust, "rust_toolchains")
|
||||
toolchains = use_extension("@rules_rs//rs/experimental/toolchains:module_extension.bzl", "toolchains")
|
||||
toolchains.toolchain(
|
||||
edition = "2024",
|
||||
version = "1.95.0",
|
||||
version = "1.93.0",
|
||||
)
|
||||
use_repo(toolchains, "default_rust_toolchains")
|
||||
|
||||
@@ -415,18 +407,18 @@ crate.annotation(
|
||||
|
||||
inject_repo(crate, "alsa_lib")
|
||||
|
||||
bazel_dep(name = "v8", version = "14.7.173.20")
|
||||
bazel_dep(name = "v8", version = "14.6.202.9")
|
||||
archive_override(
|
||||
module_name = "v8",
|
||||
integrity = "sha256-v/x6I4X38a2wckzUIft3Dh0SUdkuOTokwxyF7lzW8Lc=",
|
||||
integrity = "sha256-JphDwLAzsd9KvgRZ7eQvNtPU6qGd3XjFt/a/1QITAJU=",
|
||||
patch_strip = 3,
|
||||
patches = [
|
||||
"//patches:v8_module_deps.patch",
|
||||
"//patches:v8_bazel_rules.patch",
|
||||
"//patches:v8_source_portability.patch",
|
||||
],
|
||||
strip_prefix = "v8-14.7.173.20",
|
||||
urls = ["https://github.com/v8/v8/archive/refs/tags/14.7.173.20.tar.gz"],
|
||||
strip_prefix = "v8-14.6.202.9",
|
||||
urls = ["https://github.com/v8/v8/archive/refs/tags/14.6.202.9.tar.gz"],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@@ -438,53 +430,93 @@ http_archive(
|
||||
urls = ["https://static.crates.io/crates/v8/v8-146.4.0.crate"],
|
||||
)
|
||||
|
||||
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_147_4_0_aarch64_pc_windows_msvc_archive",
|
||||
downloaded_file_path = "rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
|
||||
sha256 = "1fa3f94d9e09cff1f6bcce94c478e5cb072c0755f6a0357abadb9dd3b48d8127",
|
||||
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/v147.4.0/rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_aarch64-apple-darwin.a.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_file(
|
||||
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 = "e2827ff98b1a9d4c0343000fc5124ac30dfab3007bc0129c168c9355fc2fcd7c",
|
||||
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/v147.4.0/rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
|
||||
"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",
|
||||
downloaded_file_path = "rusty_v8_release_aarch64-pc-windows-msvc.lib.gz",
|
||||
sha256 = "ed13363659c6d08583ac8fdc40493445c5767d8b94955a4d5d7bb8d5a81f6bf8",
|
||||
urls = [
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v146.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",
|
||||
downloaded_file_path = "rusty_v8_release_x86_64-pc-windows-msvc.lib.gz",
|
||||
sha256 = "90a9a2346acd3685a355e98df85c24dbe406cb124367d16259a4b5d522621862",
|
||||
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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
334
MODULE.bazel.lock
generated
334
MODULE.bazel.lock
generated
File diff suppressed because one or more lines are too long
15
README.md
15
README.md
@@ -1,3 +1,4 @@
|
||||
<p align="center"><code>npm i -g @openai/codex</code><br />or <code>brew install --cask codex</code></p>
|
||||
<p align="center"><strong>Codex CLI</strong> is a coding agent from OpenAI that runs locally on your computer.
|
||||
<p align="center">
|
||||
<img src="https://github.com/openai/codex/blob/main/.github/codex-cli-splash.png" alt="Codex CLI splash" width="80%" />
|
||||
@@ -13,19 +14,7 @@ If you want Codex in your code editor (VS Code, Cursor, Windsurf), <a href="http
|
||||
|
||||
### Installing and running Codex CLI
|
||||
|
||||
Run the following on Mac or Linux to install Codex CLI:
|
||||
|
||||
```shell
|
||||
curl -fsSL https://chatgpt.com/codex/install.sh | sh
|
||||
```
|
||||
|
||||
Run the following on Windows to install Codex CLI:
|
||||
|
||||
```
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
|
||||
```
|
||||
|
||||
Codex CLI can also be installed via the following package managers:
|
||||
Install globally with your preferred package manager:
|
||||
|
||||
```shell
|
||||
# Install using npm
|
||||
|
||||
@@ -77,43 +77,33 @@ if (!platformPackage) {
|
||||
|
||||
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
||||
const localVendorRoot = path.join(__dirname, "..", "vendor");
|
||||
const packageBinaryPath = (vendorRoot) =>
|
||||
path.join(vendorRoot, targetTriple, "bin", codexBinaryName);
|
||||
const legacyBinaryPath = (vendorRoot) =>
|
||||
path.join(vendorRoot, targetTriple, "codex", codexBinaryName);
|
||||
const localBinaryPath = path.join(
|
||||
localVendorRoot,
|
||||
targetTriple,
|
||||
"codex",
|
||||
codexBinaryName,
|
||||
);
|
||||
|
||||
function resolveNativePackage(vendorRoot) {
|
||||
const packageRoot = path.join(vendorRoot, targetTriple);
|
||||
const binaryPath = packageBinaryPath(vendorRoot);
|
||||
if (existsSync(binaryPath)) {
|
||||
return {
|
||||
binaryPath,
|
||||
pathDir: path.join(packageRoot, "codex-path"),
|
||||
};
|
||||
}
|
||||
|
||||
const legacyPath = legacyBinaryPath(vendorRoot);
|
||||
if (existsSync(legacyPath)) {
|
||||
return {
|
||||
binaryPath: legacyPath,
|
||||
pathDir: path.join(packageRoot, "path"),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let nativePackage;
|
||||
let vendorRoot;
|
||||
try {
|
||||
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
||||
nativePackage = resolveNativePackage(
|
||||
path.join(path.dirname(packageJsonPath), "vendor"),
|
||||
);
|
||||
vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
|
||||
} catch {
|
||||
nativePackage = resolveNativePackage(localVendorRoot);
|
||||
if (existsSync(localBinaryPath)) {
|
||||
vendorRoot = localVendorRoot;
|
||||
} else {
|
||||
const packageManager = detectPackageManager();
|
||||
const updateCommand =
|
||||
packageManager === "bun"
|
||||
? "bun install -g @openai/codex@latest"
|
||||
: "npm install -g @openai/codex@latest";
|
||||
throw new Error(
|
||||
`Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativePackage) {
|
||||
if (!vendorRoot) {
|
||||
const packageManager = detectPackageManager();
|
||||
const updateCommand =
|
||||
packageManager === "bun"
|
||||
@@ -124,7 +114,8 @@ if (!nativePackage) {
|
||||
);
|
||||
}
|
||||
|
||||
const { binaryPath, pathDir } = nativePackage;
|
||||
const archRoot = path.join(vendorRoot, targetTriple);
|
||||
const binaryPath = path.join(archRoot, "codex", codexBinaryName);
|
||||
|
||||
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
||||
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
||||
@@ -168,6 +159,7 @@ function detectPackageManager() {
|
||||
}
|
||||
|
||||
const additionalDirs = [];
|
||||
const pathDir = path.join(archRoot, "path");
|
||||
if (existsSync(pathDir)) {
|
||||
additionalDirs.push(pathDir);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@openai/codex",
|
||||
"version": "0.0.0-dev",
|
||||
"description": "Codex CLI is a coding agent from OpenAI that runs locally on your computer.",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"codex": "bin/codex.js"
|
||||
@@ -11,7 +10,8 @@
|
||||
"node": ">=16"
|
||||
},
|
||||
"files": [
|
||||
"bin/codex.js"
|
||||
"bin",
|
||||
"vendor"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -11,13 +11,13 @@ example, to stage the CLI, responses proxy, and SDK packages for version `0.6.0`
|
||||
--package codex-sdk
|
||||
```
|
||||
|
||||
This downloads the required native package archive artifacts, hydrates `vendor/` for
|
||||
each package, and writes tarballs to `dist/npm/`.
|
||||
This downloads the native artifacts once, hydrates `vendor/` for each package, and writes
|
||||
tarballs to `dist/npm/`.
|
||||
|
||||
When `--package codex` is provided, the staging helper builds the lightweight
|
||||
`@openai/codex` meta package plus all platform-native `@openai/codex` variants
|
||||
that are later published under platform-specific dist-tags.
|
||||
|
||||
Direct `build_npm_package.py` invocations are still useful for package-specific
|
||||
debugging, but native packages expect `--vendor-src` to point at a prehydrated
|
||||
`vendor/` tree. Release packaging should use `scripts/stage_npm_packages.py`.
|
||||
If you need to invoke `build_npm_package.py` directly, run
|
||||
`codex-cli/scripts/install_native_deps.py` first and pass `--vendor-src` pointing to the
|
||||
directory that contains the populated `vendor/` tree.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -16,7 +15,6 @@ REPO_ROOT = CODEX_CLI_ROOT.parent
|
||||
RESPONSES_API_PROXY_NPM_ROOT = REPO_ROOT / "codex-rs" / "responses-api-proxy" / "npm"
|
||||
CODEX_SDK_ROOT = REPO_ROOT / "sdk" / "typescript"
|
||||
CODEX_NPM_NAME = "@openai/codex"
|
||||
CODEX_PACKAGE_COMPONENT = "codex-package"
|
||||
|
||||
# `npm_name` is the local optional-dependency alias consumed by `bin/codex.js`.
|
||||
# The underlying package published to npm is always `@openai/codex`.
|
||||
@@ -71,12 +69,12 @@ PACKAGE_EXPANSIONS: dict[str, list[str]] = {
|
||||
|
||||
PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
|
||||
"codex": [],
|
||||
"codex-linux-x64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-linux-arm64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-darwin-x64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-darwin-arm64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-win32-x64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-win32-arm64": [CODEX_PACKAGE_COMPONENT],
|
||||
"codex-linux-x64": ["bwrap", "codex", "rg"],
|
||||
"codex-linux-arm64": ["bwrap", "codex", "rg"],
|
||||
"codex-darwin-x64": ["codex", "rg"],
|
||||
"codex-darwin-arm64": ["codex", "rg"],
|
||||
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
"codex-win32-arm64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
"codex-responses-api-proxy": ["codex-responses-api-proxy"],
|
||||
"codex-sdk": [],
|
||||
}
|
||||
@@ -88,6 +86,16 @@ PACKAGE_TARGET_FILTERS: dict[str, str] = {
|
||||
|
||||
PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS)
|
||||
|
||||
COMPONENT_DEST_DIR: dict[str, str] = {
|
||||
"bwrap": "codex-resources",
|
||||
"codex": "codex",
|
||||
"codex-responses-api-proxy": "codex-responses-api-proxy",
|
||||
"codex-windows-sandbox-setup": "codex",
|
||||
"codex-command-runner": "codex",
|
||||
"rg": "path",
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Build or stage the Codex CLI npm package.")
|
||||
parser.add_argument(
|
||||
@@ -130,6 +138,16 @@ def parse_args() -> argparse.Namespace:
|
||||
type=Path,
|
||||
help="Directory containing pre-installed native binaries to bundle (vendor root).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-missing-native-component",
|
||||
dest="allow_missing_native_components",
|
||||
action="append",
|
||||
default=[],
|
||||
help=(
|
||||
"Native component that may be absent from --vendor-src. Intended for CI "
|
||||
"compatibility with older artifact workflows; releases should not use this."
|
||||
),
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -170,6 +188,7 @@ def main() -> int:
|
||||
staging_dir,
|
||||
native_components,
|
||||
target_filter={target_filter} if target_filter else None,
|
||||
allow_missing_components=set(args.allow_missing_native_components),
|
||||
)
|
||||
|
||||
if release_version:
|
||||
@@ -234,6 +253,9 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
|
||||
bin_dir = staging_dir / "bin"
|
||||
bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(CODEX_CLI_ROOT / "bin" / "codex.js", bin_dir / "codex.js")
|
||||
rg_manifest = CODEX_CLI_ROOT / "bin" / "rg"
|
||||
if rg_manifest.exists():
|
||||
shutil.copy2(rg_manifest, bin_dir / "rg")
|
||||
|
||||
readme_src = REPO_ROOT / "README.md"
|
||||
if readme_src.exists():
|
||||
@@ -292,7 +314,7 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
|
||||
package_json["version"] = version
|
||||
|
||||
if package == "codex":
|
||||
package_json["files"] = ["bin/codex.js"]
|
||||
package_json["files"] = ["bin"]
|
||||
package_json["optionalDependencies"] = {
|
||||
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: (
|
||||
f"npm:{CODEX_NPM_NAME}@"
|
||||
@@ -325,7 +347,7 @@ def compute_platform_package_version(version: str, platform_tag: str) -> str:
|
||||
|
||||
|
||||
def run_command(cmd: list[str], cwd: Path | None = None) -> None:
|
||||
print("+", " ".join(cmd), flush=True)
|
||||
print("+", " ".join(cmd))
|
||||
subprocess.run(cmd, cwd=cwd, check=True)
|
||||
|
||||
|
||||
@@ -355,12 +377,14 @@ def copy_native_binaries(
|
||||
staging_dir: Path,
|
||||
components: list[str],
|
||||
target_filter: set[str] | None = None,
|
||||
allow_missing_components: set[str] | None = None,
|
||||
) -> None:
|
||||
vendor_src = vendor_src.resolve()
|
||||
if not vendor_src.exists():
|
||||
raise RuntimeError(f"Vendor source directory not found: {vendor_src}")
|
||||
|
||||
components_set = set(components)
|
||||
components_set = {component for component in components if component in COMPONENT_DEST_DIR}
|
||||
allow_missing_components = allow_missing_components or set()
|
||||
if not components_set:
|
||||
return
|
||||
|
||||
@@ -378,25 +402,24 @@ def copy_native_binaries(
|
||||
if target_filter is not None and target_dir.name not in target_filter:
|
||||
continue
|
||||
|
||||
dest_target_dir = vendor_dest / target_dir.name
|
||||
dest_target_dir.mkdir(parents=True, exist_ok=True)
|
||||
copied_targets.add(target_dir.name)
|
||||
|
||||
dest_target_dir = vendor_dest / target_dir.name
|
||||
for component in components_set:
|
||||
dest_dir_name = COMPONENT_DEST_DIR.get(component)
|
||||
if dest_dir_name is None:
|
||||
continue
|
||||
|
||||
if CODEX_PACKAGE_COMPONENT in components_set:
|
||||
if dest_target_dir.exists():
|
||||
shutil.rmtree(dest_target_dir)
|
||||
shutil.copytree(target_dir, dest_target_dir)
|
||||
else:
|
||||
dest_target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for component in sorted(components_set - {CODEX_PACKAGE_COMPONENT}):
|
||||
src_component_dir = target_dir / component
|
||||
src_component_dir = target_dir / dest_dir_name
|
||||
if not src_component_dir.exists():
|
||||
if component in allow_missing_components:
|
||||
continue
|
||||
raise RuntimeError(
|
||||
f"Missing native component '{component}' in vendor source: {src_component_dir}"
|
||||
)
|
||||
|
||||
dest_component_dir = dest_target_dir / component
|
||||
dest_component_dir = dest_target_dir / dest_dir_name
|
||||
if dest_component_dir.exists():
|
||||
shutil.rmtree(dest_component_dir)
|
||||
shutil.copytree(src_component_dir, dest_component_dir)
|
||||
@@ -407,23 +430,16 @@ def copy_native_binaries(
|
||||
missing_list = ", ".join(missing_targets)
|
||||
raise RuntimeError(f"Missing target directories in vendor source: {missing_list}")
|
||||
|
||||
|
||||
def run_npm_pack(staging_dir: Path, output_path: Path) -> Path:
|
||||
output_path = output_path.resolve()
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="codex-npm-pack-") as pack_dir_str:
|
||||
pack_dir = Path(pack_dir_str)
|
||||
npm_cache_dir = pack_dir / "npm-cache"
|
||||
npm_logs_dir = pack_dir / "npm-logs"
|
||||
npm_cache_dir.mkdir()
|
||||
npm_logs_dir.mkdir()
|
||||
env = os.environ.copy()
|
||||
env["NPM_CONFIG_CACHE"] = str(npm_cache_dir)
|
||||
env["NPM_CONFIG_LOGS_DIR"] = str(npm_logs_dir)
|
||||
stdout = subprocess.check_output(
|
||||
["npm", "pack", "--json", "--pack-destination", str(pack_dir)],
|
||||
cwd=staging_dir,
|
||||
env=env,
|
||||
text=True,
|
||||
)
|
||||
try:
|
||||
|
||||
483
codex-cli/scripts/install_native_deps.py
Executable file
483
codex-cli/scripts/install_native_deps.py
Executable file
@@ -0,0 +1,483 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install Codex native binaries (Rust CLI, bwrap, and ripgrep helpers)."""
|
||||
|
||||
import argparse
|
||||
from contextlib import contextmanager
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import zipfile
|
||||
from dataclasses import dataclass
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Iterable, Sequence
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
CODEX_CLI_ROOT = SCRIPT_DIR.parent
|
||||
DEFAULT_WORKFLOW_URL = "https://github.com/openai/codex/actions/runs/17952349351" # rust-v0.40.0
|
||||
VENDOR_DIR_NAME = "vendor"
|
||||
RG_MANIFEST = CODEX_CLI_ROOT / "bin" / "rg"
|
||||
BINARY_TARGETS = (
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BinaryComponent:
|
||||
artifact_prefix: str # matches the artifact filename prefix (e.g. codex-<target>.zst)
|
||||
dest_dir: str # directory under vendor/<target>/ where the binary is installed
|
||||
binary_basename: str # executable name inside dest_dir (before optional .exe)
|
||||
targets: tuple[str, ...] | None = None # limit installation to specific targets
|
||||
|
||||
|
||||
WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target)
|
||||
LINUX_TARGETS = tuple(target for target in BINARY_TARGETS if "linux" in target)
|
||||
|
||||
BINARY_COMPONENTS = {
|
||||
"bwrap": BinaryComponent(
|
||||
artifact_prefix="bwrap",
|
||||
dest_dir="codex-resources",
|
||||
binary_basename="bwrap",
|
||||
targets=LINUX_TARGETS,
|
||||
),
|
||||
"codex": BinaryComponent(
|
||||
artifact_prefix="codex",
|
||||
dest_dir="codex",
|
||||
binary_basename="codex",
|
||||
),
|
||||
"codex-responses-api-proxy": BinaryComponent(
|
||||
artifact_prefix="codex-responses-api-proxy",
|
||||
dest_dir="codex-responses-api-proxy",
|
||||
binary_basename="codex-responses-api-proxy",
|
||||
),
|
||||
"codex-windows-sandbox-setup": BinaryComponent(
|
||||
artifact_prefix="codex-windows-sandbox-setup",
|
||||
dest_dir="codex",
|
||||
binary_basename="codex-windows-sandbox-setup",
|
||||
targets=WINDOWS_TARGETS,
|
||||
),
|
||||
"codex-command-runner": BinaryComponent(
|
||||
artifact_prefix="codex-command-runner",
|
||||
dest_dir="codex",
|
||||
binary_basename="codex-command-runner",
|
||||
targets=WINDOWS_TARGETS,
|
||||
),
|
||||
}
|
||||
|
||||
RG_TARGET_PLATFORM_PAIRS: list[tuple[str, str]] = [
|
||||
("x86_64-unknown-linux-musl", "linux-x86_64"),
|
||||
("aarch64-unknown-linux-musl", "linux-aarch64"),
|
||||
("x86_64-apple-darwin", "macos-x86_64"),
|
||||
("aarch64-apple-darwin", "macos-aarch64"),
|
||||
("x86_64-pc-windows-msvc", "windows-x86_64"),
|
||||
("aarch64-pc-windows-msvc", "windows-aarch64"),
|
||||
]
|
||||
RG_TARGET_TO_PLATFORM = {target: platform for target, platform in RG_TARGET_PLATFORM_PAIRS}
|
||||
DEFAULT_RG_TARGETS = [target for target, _ in RG_TARGET_PLATFORM_PAIRS]
|
||||
|
||||
# urllib.request.urlopen() defaults to no timeout (can hang indefinitely), which is painful in CI.
|
||||
DOWNLOAD_TIMEOUT_SECS = 60
|
||||
|
||||
|
||||
def _gha_enabled() -> bool:
|
||||
# GitHub Actions supports "workflow commands" (e.g. ::group:: / ::error::) that make logs
|
||||
# much easier to scan: groups collapse noisy sections and error annotations surface the
|
||||
# failure in the UI without changing the actual exception/traceback output.
|
||||
return os.environ.get("GITHUB_ACTIONS") == "true"
|
||||
|
||||
|
||||
def _gha_escape(value: str) -> str:
|
||||
# Workflow commands require percent/newline escaping.
|
||||
return value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
|
||||
|
||||
|
||||
def _gha_error(*, title: str, message: str) -> None:
|
||||
# Emit a GitHub Actions error annotation. This does not replace stdout/stderr logs; it just
|
||||
# adds a prominent summary line to the job UI so the root cause is easier to spot.
|
||||
if not _gha_enabled():
|
||||
return
|
||||
print(
|
||||
f"::error title={_gha_escape(title)}::{_gha_escape(message)}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _gha_group(title: str):
|
||||
# Wrap a block in a collapsible log group on GitHub Actions. Outside of GHA this is a no-op
|
||||
# so local output remains unchanged.
|
||||
if _gha_enabled():
|
||||
print(f"::group::{_gha_escape(title)}", flush=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if _gha_enabled():
|
||||
print("::endgroup::", flush=True)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Install native Codex binaries.")
|
||||
parser.add_argument(
|
||||
"--workflow-url",
|
||||
help=(
|
||||
"GitHub Actions workflow URL that produced the artifacts. Defaults to a "
|
||||
"known good run when omitted."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--component",
|
||||
dest="components",
|
||||
action="append",
|
||||
choices=tuple(list(BINARY_COMPONENTS) + ["rg"]),
|
||||
help=(
|
||||
"Limit installation to the specified components."
|
||||
" May be repeated. Defaults to bwrap, codex, codex-windows-sandbox-setup,"
|
||||
" codex-command-runner, and rg."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"root",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
help=(
|
||||
"Directory containing package.json for the staged package. If omitted, the "
|
||||
"repository checkout is used."
|
||||
),
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
|
||||
codex_cli_root = (args.root or CODEX_CLI_ROOT).resolve()
|
||||
vendor_dir = codex_cli_root / VENDOR_DIR_NAME
|
||||
vendor_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
components = args.components or [
|
||||
"bwrap",
|
||||
"codex",
|
||||
"codex-windows-sandbox-setup",
|
||||
"codex-command-runner",
|
||||
"rg",
|
||||
]
|
||||
|
||||
workflow_url = (args.workflow_url or DEFAULT_WORKFLOW_URL).strip()
|
||||
if not workflow_url:
|
||||
workflow_url = DEFAULT_WORKFLOW_URL
|
||||
|
||||
workflow_id = workflow_url.rstrip("/").split("/")[-1]
|
||||
print(f"Downloading native artifacts from workflow {workflow_id}...")
|
||||
|
||||
with _gha_group(f"Download native artifacts from workflow {workflow_id}"):
|
||||
with tempfile.TemporaryDirectory(prefix="codex-native-artifacts-") as artifacts_dir_str:
|
||||
artifacts_dir = Path(artifacts_dir_str)
|
||||
_download_artifacts(workflow_id, artifacts_dir)
|
||||
install_binary_components(
|
||||
artifacts_dir,
|
||||
vendor_dir,
|
||||
[BINARY_COMPONENTS[name] for name in components if name in BINARY_COMPONENTS],
|
||||
)
|
||||
|
||||
if "rg" in components:
|
||||
with _gha_group("Fetch ripgrep binaries"):
|
||||
print("Fetching ripgrep binaries...")
|
||||
fetch_rg(vendor_dir, DEFAULT_RG_TARGETS, manifest_path=RG_MANIFEST)
|
||||
|
||||
print(f"Installed native dependencies into {vendor_dir}")
|
||||
return 0
|
||||
|
||||
|
||||
def fetch_rg(
|
||||
vendor_dir: Path,
|
||||
targets: Sequence[str] | None = None,
|
||||
*,
|
||||
manifest_path: Path,
|
||||
) -> list[Path]:
|
||||
"""Download ripgrep binaries described by the DotSlash manifest."""
|
||||
|
||||
if targets is None:
|
||||
targets = DEFAULT_RG_TARGETS
|
||||
|
||||
if not manifest_path.exists():
|
||||
raise FileNotFoundError(f"DotSlash manifest not found: {manifest_path}")
|
||||
|
||||
manifest = _load_manifest(manifest_path)
|
||||
platforms = manifest.get("platforms", {})
|
||||
|
||||
vendor_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
targets = list(targets)
|
||||
if not targets:
|
||||
return []
|
||||
|
||||
task_configs: list[tuple[str, str, dict]] = []
|
||||
for target in targets:
|
||||
platform_key = RG_TARGET_TO_PLATFORM.get(target)
|
||||
if platform_key is None:
|
||||
raise ValueError(f"Unsupported ripgrep target '{target}'.")
|
||||
|
||||
platform_info = platforms.get(platform_key)
|
||||
if platform_info is None:
|
||||
raise RuntimeError(f"Platform '{platform_key}' not found in manifest {manifest_path}.")
|
||||
|
||||
task_configs.append((target, platform_key, platform_info))
|
||||
|
||||
results: dict[str, Path] = {}
|
||||
max_workers = min(len(task_configs), max(1, (os.cpu_count() or 1)))
|
||||
|
||||
print("Installing ripgrep binaries for targets: " + ", ".join(targets))
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_map = {
|
||||
executor.submit(
|
||||
_fetch_single_rg,
|
||||
vendor_dir,
|
||||
target,
|
||||
platform_key,
|
||||
platform_info,
|
||||
manifest_path,
|
||||
): target
|
||||
for target, platform_key, platform_info in task_configs
|
||||
}
|
||||
|
||||
for future in as_completed(future_map):
|
||||
target = future_map[future]
|
||||
try:
|
||||
results[target] = future.result()
|
||||
except Exception as exc:
|
||||
_gha_error(
|
||||
title="ripgrep install failed",
|
||||
message=f"target={target} error={exc!r}",
|
||||
)
|
||||
raise RuntimeError(f"Failed to install ripgrep for target {target}.") from exc
|
||||
print(f" installed ripgrep for {target}")
|
||||
|
||||
return [results[target] for target in targets]
|
||||
|
||||
|
||||
def _download_artifacts(workflow_id: str, dest_dir: Path) -> None:
|
||||
cmd = [
|
||||
"gh",
|
||||
"run",
|
||||
"download",
|
||||
"--dir",
|
||||
str(dest_dir),
|
||||
"--repo",
|
||||
"openai/codex",
|
||||
workflow_id,
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def install_binary_components(
|
||||
artifacts_dir: Path,
|
||||
vendor_dir: Path,
|
||||
selected_components: Sequence[BinaryComponent],
|
||||
) -> None:
|
||||
if not selected_components:
|
||||
return
|
||||
|
||||
for component in selected_components:
|
||||
component_targets = list(component.targets or BINARY_TARGETS)
|
||||
|
||||
print(
|
||||
f"Installing {component.binary_basename} binaries for targets: "
|
||||
+ ", ".join(component_targets)
|
||||
)
|
||||
max_workers = min(len(component_targets), max(1, (os.cpu_count() or 1)))
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {
|
||||
executor.submit(
|
||||
_install_single_binary,
|
||||
artifacts_dir,
|
||||
vendor_dir,
|
||||
target,
|
||||
component,
|
||||
): target
|
||||
for target in component_targets
|
||||
}
|
||||
for future in as_completed(futures):
|
||||
installed_path = future.result()
|
||||
print(f" installed {installed_path}")
|
||||
|
||||
|
||||
def _install_single_binary(
|
||||
artifacts_dir: Path,
|
||||
vendor_dir: Path,
|
||||
target: str,
|
||||
component: BinaryComponent,
|
||||
) -> Path:
|
||||
artifact_subdir = artifacts_dir / target
|
||||
archive_name = _archive_name_for_target(component.artifact_prefix, target)
|
||||
archive_path = artifact_subdir / archive_name
|
||||
if not archive_path.exists():
|
||||
raise FileNotFoundError(f"Expected artifact not found: {archive_path}")
|
||||
|
||||
dest_dir = vendor_dir / target / component.dest_dir
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
binary_name = (
|
||||
f"{component.binary_basename}.exe" if "windows" in target else component.binary_basename
|
||||
)
|
||||
dest = dest_dir / binary_name
|
||||
dest.unlink(missing_ok=True)
|
||||
extract_archive(archive_path, "zst", None, dest)
|
||||
if "windows" not in target:
|
||||
dest.chmod(0o755)
|
||||
return dest
|
||||
|
||||
|
||||
def _archive_name_for_target(artifact_prefix: str, target: str) -> str:
|
||||
if "windows" in target:
|
||||
return f"{artifact_prefix}-{target}.exe.zst"
|
||||
return f"{artifact_prefix}-{target}.zst"
|
||||
|
||||
|
||||
def _fetch_single_rg(
|
||||
vendor_dir: Path,
|
||||
target: str,
|
||||
platform_key: str,
|
||||
platform_info: dict,
|
||||
manifest_path: Path,
|
||||
) -> Path:
|
||||
providers = platform_info.get("providers", [])
|
||||
if not providers:
|
||||
raise RuntimeError(f"No providers listed for platform '{platform_key}' in {manifest_path}.")
|
||||
|
||||
url = providers[0]["url"]
|
||||
archive_format = platform_info.get("format", "zst")
|
||||
archive_member = platform_info.get("path")
|
||||
digest = platform_info.get("digest")
|
||||
expected_size = platform_info.get("size")
|
||||
|
||||
dest_dir = vendor_dir / target / "path"
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
is_windows = platform_key.startswith("win")
|
||||
binary_name = "rg.exe" if is_windows else "rg"
|
||||
dest = dest_dir / binary_name
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir_str:
|
||||
tmp_dir = Path(tmp_dir_str)
|
||||
archive_filename = os.path.basename(urlparse(url).path)
|
||||
download_path = tmp_dir / archive_filename
|
||||
print(
|
||||
f" downloading ripgrep for {target} ({platform_key}) from {url}",
|
||||
flush=True,
|
||||
)
|
||||
try:
|
||||
_download_file(url, download_path)
|
||||
except Exception as exc:
|
||||
_gha_error(
|
||||
title="ripgrep download failed",
|
||||
message=f"target={target} platform={platform_key} url={url} error={exc!r}",
|
||||
)
|
||||
raise RuntimeError(
|
||||
"Failed to download ripgrep "
|
||||
f"(target={target}, platform={platform_key}, format={archive_format}, "
|
||||
f"expected_size={expected_size!r}, digest={digest!r}, url={url}, dest={download_path})."
|
||||
) from exc
|
||||
|
||||
dest.unlink(missing_ok=True)
|
||||
try:
|
||||
extract_archive(download_path, archive_format, archive_member, dest)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
"Failed to extract ripgrep "
|
||||
f"(target={target}, platform={platform_key}, format={archive_format}, "
|
||||
f"member={archive_member!r}, url={url}, archive={download_path})."
|
||||
) from exc
|
||||
|
||||
if not is_windows:
|
||||
dest.chmod(0o755)
|
||||
|
||||
return dest
|
||||
|
||||
|
||||
def _download_file(url: str, dest: Path) -> None:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.unlink(missing_ok=True)
|
||||
|
||||
with urlopen(url, timeout=DOWNLOAD_TIMEOUT_SECS) as response, open(dest, "wb") as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
|
||||
|
||||
def extract_archive(
|
||||
archive_path: Path,
|
||||
archive_format: str,
|
||||
archive_member: str | None,
|
||||
dest: Path,
|
||||
) -> None:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if archive_format == "zst":
|
||||
output_path = archive_path.parent / dest.name
|
||||
subprocess.check_call(
|
||||
["zstd", "-f", "-d", str(archive_path), "-o", str(output_path)]
|
||||
)
|
||||
shutil.move(str(output_path), dest)
|
||||
return
|
||||
|
||||
if archive_format == "tar.gz":
|
||||
if not archive_member:
|
||||
raise RuntimeError("Missing 'path' for tar.gz archive in DotSlash manifest.")
|
||||
with tarfile.open(archive_path, "r:gz") as tar:
|
||||
try:
|
||||
member = tar.getmember(archive_member)
|
||||
except KeyError as exc:
|
||||
raise RuntimeError(
|
||||
f"Entry '{archive_member}' not found in archive {archive_path}."
|
||||
) from exc
|
||||
tar.extract(member, path=archive_path.parent, filter="data")
|
||||
extracted = archive_path.parent / archive_member
|
||||
shutil.move(str(extracted), dest)
|
||||
return
|
||||
|
||||
if archive_format == "zip":
|
||||
if not archive_member:
|
||||
raise RuntimeError("Missing 'path' for zip archive in DotSlash manifest.")
|
||||
with zipfile.ZipFile(archive_path) as archive:
|
||||
try:
|
||||
with archive.open(archive_member) as src, open(dest, "wb") as out:
|
||||
shutil.copyfileobj(src, out)
|
||||
except KeyError as exc:
|
||||
raise RuntimeError(
|
||||
f"Entry '{archive_member}' not found in archive {archive_path}."
|
||||
) from exc
|
||||
return
|
||||
|
||||
raise RuntimeError(f"Unsupported archive format '{archive_format}'.")
|
||||
|
||||
|
||||
def _load_manifest(manifest_path: Path) -> dict:
|
||||
cmd = ["dotslash", "--", "parse", str(manifest_path)]
|
||||
stdout = subprocess.check_output(cmd, text=True)
|
||||
try:
|
||||
manifest = json.loads(stdout)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise RuntimeError(f"Invalid DotSlash manifest output from {manifest_path}.") from exc
|
||||
|
||||
if not isinstance(manifest, dict):
|
||||
raise RuntimeError(
|
||||
f"Unexpected DotSlash manifest structure for {manifest_path}: {type(manifest)!r}"
|
||||
)
|
||||
|
||||
return manifest
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
sys.exit(main())
|
||||
@@ -1,5 +1,5 @@
|
||||
[target.'cfg(all(windows, target_env = "msvc"))']
|
||||
rustflags = ["-C", "link-arg=/STACK:8388608", "-C", "target-feature=+crt-static"]
|
||||
rustflags = ["-C", "link-arg=/STACK:8388608"]
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
[profile.default]
|
||||
# Retry once so one transient failure does not fail full-CI outright.
|
||||
# Fanout keeps the full-CI shards moving without treating every >30s test as
|
||||
# stuck. Keep this aligned with the broader timeout budget we give sharded CI.
|
||||
slow-timeout = { period = "30s", terminate-after = 2 }
|
||||
retries = 1
|
||||
|
||||
[profile.default.junit]
|
||||
path = "junit.xml"
|
||||
# Do not increase, fix your test instead
|
||||
slow-timeout = { period = "15s", terminate-after = 2 }
|
||||
|
||||
[test-groups.app_server_protocol_codegen]
|
||||
max-threads = 1
|
||||
@@ -20,9 +14,6 @@ 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)'
|
||||
@@ -53,18 +44,3 @@ 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 }
|
||||
|
||||
2
codex-rs/.github/workflows/cargo-audit.yml
vendored
2
codex-rs/.github/workflows/cargo-audit.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
working-directory: codex-rs
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
- uses: dtolnay/rust-toolchain@a0b273b48ed29de4470960879e8381ff45632f26 # 1.93.0
|
||||
- name: Install cargo-audit
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
|
||||
1254
codex-rs/Cargo.lock
generated
1254
codex-rs/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ members = [
|
||||
"app-server-client",
|
||||
"app-server-protocol",
|
||||
"app-server-test-client",
|
||||
"debug-client",
|
||||
"apply-patch",
|
||||
"arg0",
|
||||
"feedback",
|
||||
@@ -21,7 +22,7 @@ members = [
|
||||
"install-context",
|
||||
"codex-backend-openapi-models",
|
||||
"code-mode",
|
||||
"cloud-config",
|
||||
"cloud-requirements",
|
||||
"cloud-tasks",
|
||||
"cloud-tasks-client",
|
||||
"cloud-tasks-mock-client",
|
||||
@@ -44,11 +45,8 @@ members = [
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"ext/extension-api",
|
||||
"ext/goal",
|
||||
"ext/guardian",
|
||||
"ext/image-generation",
|
||||
"ext/memories",
|
||||
"ext/web-search",
|
||||
"external-agent-migration",
|
||||
"external-agent-sessions",
|
||||
"keyring-store",
|
||||
@@ -59,6 +57,7 @@ members = [
|
||||
"login",
|
||||
"codex-mcp",
|
||||
"mcp-server",
|
||||
"memories/mcp",
|
||||
"memories/read",
|
||||
"memories/write",
|
||||
"model-provider-info",
|
||||
@@ -68,7 +67,6 @@ members = [
|
||||
"process-hardening",
|
||||
"protocol",
|
||||
"realtime-webrtc",
|
||||
"prompts",
|
||||
"rollout",
|
||||
"rollout-trace",
|
||||
"rmcp-client",
|
||||
@@ -149,7 +147,7 @@ codex-chatgpt = { path = "chatgpt" }
|
||||
codex-cli = { path = "cli" }
|
||||
codex-client = { path = "codex-client" }
|
||||
codex-collaboration-mode-templates = { path = "collaboration-mode-templates" }
|
||||
codex-cloud-config = { path = "cloud-config" }
|
||||
codex-cloud-requirements = { path = "cloud-requirements" }
|
||||
codex-cloud-tasks-client = { path = "cloud-tasks-client" }
|
||||
codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
|
||||
codex-code-mode = { path = "code-mode" }
|
||||
@@ -164,9 +162,7 @@ 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-image-generation-extension = { path = "ext/image-generation" }
|
||||
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" }
|
||||
@@ -183,7 +179,6 @@ codex-lmstudio = { path = "lmstudio" }
|
||||
codex-login = { path = "login" }
|
||||
codex-message-history = { path = "message-history" }
|
||||
codex-memories-extension = { path = "ext/memories" }
|
||||
codex-web-search-extension = { path = "ext/web-search" }
|
||||
codex-memories-read = { path = "memories/read" }
|
||||
codex-memories-write = { path = "memories/write" }
|
||||
codex-mcp = { path = "codex-mcp" }
|
||||
@@ -198,7 +193,6 @@ codex-model-provider = { path = "model-provider" }
|
||||
codex-process-hardening = { path = "process-hardening" }
|
||||
codex-protocol = { path = "protocol" }
|
||||
codex-realtime-webrtc = { path = "realtime-webrtc" }
|
||||
codex-prompts = { path = "prompts" }
|
||||
codex-responses-api-proxy = { path = "responses-api-proxy" }
|
||||
codex-response-debug-context = { path = "response-debug-context" }
|
||||
codex-rmcp-client = { path = "rmcp-client" }
|
||||
@@ -279,7 +273,6 @@ deno_core_icudata = "0.77.0"
|
||||
derive_more = "2"
|
||||
diffy = "0.4.2"
|
||||
dirs = "6"
|
||||
divan = "0.1.21"
|
||||
dns-lookup = "3.0.1"
|
||||
dotenvy = "0.15.7"
|
||||
dunce = "1.0.4"
|
||||
@@ -306,7 +299,6 @@ indexmap = "2.12.0"
|
||||
insta = "1.46.3"
|
||||
inventory = "0.3.19"
|
||||
itertools = "0.14.0"
|
||||
jsonptr = { version = "0.7.1", default-features = false }
|
||||
jsonwebtoken = "9.3.1"
|
||||
keyring = { version = "3.6", default-features = false }
|
||||
landlock = "0.4.4"
|
||||
@@ -345,7 +337,7 @@ rcgen = { version = "0.14.7", default-features = false, features = [
|
||||
regex = "1.12.3"
|
||||
regex-lite = "0.1.8"
|
||||
reqwest = { version = "0.12", features = ["cookies"] }
|
||||
rmcp = { version = "1.7.0", default-features = false }
|
||||
rmcp = { version = "0.15.0", default-features = false }
|
||||
runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" }
|
||||
rustls = { version = "0.23", default-features = false, features = [
|
||||
"ring",
|
||||
@@ -369,14 +361,13 @@ sha2 = "0.10"
|
||||
shlex = "1.3.0"
|
||||
similar = "2.7.0"
|
||||
socket2 = "0.6.1"
|
||||
sqlx = { version = "0.9.0", default-features = false, features = [
|
||||
sqlx = { version = "0.8.6", default-features = false, features = [
|
||||
"chrono",
|
||||
"json",
|
||||
"macros",
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
"tls-rustls",
|
||||
"sqlite-bundled",
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"time",
|
||||
"uuid",
|
||||
] }
|
||||
@@ -420,7 +411,7 @@ unicode-width = "0.2"
|
||||
url = "2"
|
||||
urlencoding = "2.1"
|
||||
uuid = "1"
|
||||
v8 = "=147.4.0"
|
||||
v8 = "=146.4.0"
|
||||
vt100 = "0.16.2"
|
||||
walkdir = "2.5.0"
|
||||
webbrowser = "1.0"
|
||||
@@ -479,7 +470,6 @@ unwrap_used = "deny"
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = [
|
||||
"codex-agent-graph-store",
|
||||
"codex-goal-extension",
|
||||
"icu_provider",
|
||||
"openssl-sys",
|
||||
"codex-v8-poc",
|
||||
|
||||
@@ -55,20 +55,25 @@ Use `codex exec --ephemeral ...` to run without persisting session rollout files
|
||||
|
||||
### Experimenting with the Codex Sandbox
|
||||
|
||||
To test to see what happens when a command is run under the sandbox provided by Codex, use the `sandbox` subcommand in Codex CLI:
|
||||
To test to see what happens when a command is run under the sandbox provided by Codex, we provide the following subcommands in Codex CLI:
|
||||
|
||||
```
|
||||
# Uses the sandbox implementation for the current host OS:
|
||||
# Seatbelt on macOS, the Linux sandbox on Linux, and Windows restricted token on Windows.
|
||||
codex sandbox [COMMAND]...
|
||||
# macOS
|
||||
codex sandbox macos [--log-denials] [COMMAND]...
|
||||
|
||||
# macOS-only diagnostic option
|
||||
codex sandbox --log-denials [COMMAND]...
|
||||
# Linux
|
||||
codex sandbox linux [COMMAND]...
|
||||
|
||||
# Windows
|
||||
codex sandbox windows [COMMAND]...
|
||||
|
||||
# Legacy aliases
|
||||
codex debug seatbelt [--log-denials] [COMMAND]...
|
||||
codex debug landlock [COMMAND]...
|
||||
```
|
||||
|
||||
`codex sandbox` also accepts `--profile NAME` (`-p NAME`) to layer
|
||||
`$CODEX_HOME/NAME.config.toml` onto the base user config for the sandboxed
|
||||
command.
|
||||
To try a writable legacy sandbox mode with these commands, pass an explicit config override such
|
||||
as `-c 'sandbox_mode="workspace-write"'`.
|
||||
|
||||
### Selecting a sandbox policy via `--sandbox`
|
||||
|
||||
@@ -85,6 +90,7 @@ codex --sandbox workspace-write
|
||||
codex --sandbox danger-full-access
|
||||
```
|
||||
|
||||
The same setting can be persisted in `~/.codex/config.toml` via the top-level `sandbox_mode = "MODE"` key, e.g. `sandbox_mode = "workspace-write"`.
|
||||
In `workspace-write`, Codex also includes `~/.codex/memories` in its writable roots so memory maintenance does not require an extra approval.
|
||||
|
||||
## Code Organization
|
||||
|
||||
@@ -62,7 +62,6 @@ use crate::facts::SkillInvokedInput;
|
||||
use crate::facts::SubAgentThreadStartedInput;
|
||||
use crate::facts::ThreadInitializationMode;
|
||||
use crate::facts::TrackEventsContext;
|
||||
use crate::facts::TurnCodexErrorFact;
|
||||
use crate::facts::TurnResolvedConfigFact;
|
||||
use crate::facts::TurnStatus;
|
||||
use crate::facts::TurnSteerRequestError;
|
||||
@@ -133,13 +132,13 @@ use codex_plugin::PluginTelemetryMetadata;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
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;
|
||||
@@ -162,13 +161,11 @@ fn sample_thread_with_metadata(
|
||||
ephemeral: bool,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
parent_thread_id: Option<String>,
|
||||
) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
parent_thread_id,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral,
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -199,7 +196,6 @@ fn sample_thread_start_response(
|
||||
ephemeral,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
/*parent_thread_id*/ None,
|
||||
),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -245,7 +241,6 @@ fn sample_thread_resume_response(
|
||||
model,
|
||||
AppServerSessionSource::Exec,
|
||||
Some(AppServerThreadSource::User),
|
||||
/*parent_thread_id*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -255,16 +250,9 @@ fn sample_thread_resume_response_with_source(
|
||||
model: &str,
|
||||
source: AppServerSessionSource,
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
parent_thread_id: Option<String>,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
source,
|
||||
thread_source,
|
||||
parent_thread_id,
|
||||
),
|
||||
thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
service_tier: None,
|
||||
@@ -276,7 +264,6 @@ fn sample_thread_resume_response_with_source(
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
initial_turns_page: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -285,7 +272,6 @@ fn sample_turn_start_request(thread_id: &str, request_id: i64) -> ClientRequest
|
||||
request_id: RequestId::Integer(request_id),
|
||||
params: TurnStartParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
client_user_message_id: None,
|
||||
input: vec![
|
||||
UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
@@ -381,7 +367,9 @@ 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::read_only(),
|
||||
permission_profile: CorePermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
),
|
||||
permission_profile_cwd: PathBuf::from("/tmp"),
|
||||
reasoning_effort: None,
|
||||
reasoning_summary: None,
|
||||
@@ -391,7 +379,6 @@ fn sample_turn_resolved_config(thread_id: &str, turn_id: &str) -> TurnResolvedCo
|
||||
sandbox_network_access: true,
|
||||
collaboration_mode: ModeKind::Plan,
|
||||
personality: None,
|
||||
workspace_kind: None,
|
||||
is_first_turn: true,
|
||||
}
|
||||
}
|
||||
@@ -406,7 +393,6 @@ fn sample_turn_steer_request(
|
||||
params: TurnSteerParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
expected_turn_id: expected_turn_id.to_string(),
|
||||
client_user_message_id: None,
|
||||
input: vec![
|
||||
UserInput::Text {
|
||||
text: "more".to_string(),
|
||||
@@ -418,7 +404,6 @@ fn sample_turn_steer_request(
|
||||
},
|
||||
],
|
||||
responsesapi_client_metadata: None,
|
||||
additional_context: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -836,7 +821,6 @@ fn sample_permissions_approval_request(request_id: i64) -> ServerRequest {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "permissions-1".to_string(),
|
||||
environment_id: None,
|
||||
started_at_ms: 1_000,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
reason: Some("need network".to_string()),
|
||||
@@ -1231,7 +1215,6 @@ fn compaction_event_serializes_expected_shape() {
|
||||
completed_at: 106,
|
||||
duration_ms: Some(6543),
|
||||
},
|
||||
"session-thread-1".to_string(),
|
||||
sample_app_server_client_metadata(),
|
||||
sample_runtime_metadata(),
|
||||
Some(ThreadSource::User),
|
||||
@@ -1248,7 +1231,6 @@ fn compaction_event_serializes_expected_shape() {
|
||||
"event_type": "codex_compaction_event",
|
||||
"event_params": {
|
||||
"thread_id": "thread-1",
|
||||
"session_id": "session-thread-1",
|
||||
"turn_id": "turn-1",
|
||||
"app_server_client": {
|
||||
"product_client_id": DEFAULT_ORIGINATOR,
|
||||
@@ -1283,14 +1265,6 @@ fn compaction_event_serializes_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compaction_implementation_serializes_remote_v2() {
|
||||
let payload = serde_json::to_value(CompactionImplementation::ResponsesCompactionV2)
|
||||
.expect("serialize compaction implementation");
|
||||
|
||||
assert_eq!(payload, json!("responses_compaction_v2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_used_dedupe_is_keyed_by_turn_and_connector() {
|
||||
let (sender, _receiver) = mpsc::channel(1);
|
||||
@@ -1327,7 +1301,6 @@ fn thread_initialized_event_serializes_expected_shape() {
|
||||
event_type: "codex_thread_initialized",
|
||||
event_params: ThreadInitializedEventParams {
|
||||
thread_id: "thread-0".to_string(),
|
||||
session_id: "session-thread-0".to_string(),
|
||||
app_server_client: CodexAppServerClientMetadata {
|
||||
product_client_id: DEFAULT_ORIGINATOR.to_string(),
|
||||
client_name: Some("codex-tui".to_string()),
|
||||
@@ -1359,7 +1332,6 @@ fn thread_initialized_event_serializes_expected_shape() {
|
||||
"event_type": "codex_thread_initialized",
|
||||
"event_params": {
|
||||
"thread_id": "thread-0",
|
||||
"session_id": "session-thread-0",
|
||||
"app_server_client": {
|
||||
"product_client_id": DEFAULT_ORIGINATOR,
|
||||
"client_name": "codex-tui",
|
||||
@@ -1627,7 +1599,6 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 1);
|
||||
assert_eq!(payload[0]["event_type"], "codex_thread_initialized");
|
||||
assert_eq!(payload[0]["event_params"]["session_id"], "session-thread-1");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["app_server_client"]["product_client_id"],
|
||||
DEFAULT_ORIGINATOR
|
||||
@@ -1770,7 +1741,6 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
agent_role: None,
|
||||
}),
|
||||
Some(AppServerThreadSource::Subagent),
|
||||
Some(parent_thread_id.to_string()),
|
||||
)),
|
||||
},
|
||||
&mut events,
|
||||
@@ -1805,7 +1775,6 @@ async fn compaction_event_ingests_custom_fact() {
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 1);
|
||||
assert_eq!(payload[0]["event_type"], "codex_compaction_event");
|
||||
assert_eq!(payload[0]["event_params"]["session_id"], "session-thread-1");
|
||||
assert_eq!(payload[0]["event_params"]["thread_id"], "thread-1");
|
||||
assert_eq!(payload[0]["event_params"]["turn_id"], "turn-compact");
|
||||
assert_eq!(
|
||||
@@ -1930,10 +1899,6 @@ async fn guardian_review_event_ingests_custom_fact_with_optional_target_item() {
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload.as_array().expect("events array").len(), 1);
|
||||
assert_eq!(payload[0]["event_type"], "codex_guardian_review");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["session_id"],
|
||||
"session-thread-guardian"
|
||||
);
|
||||
assert_eq!(payload[0]["event_params"]["thread_id"], "thread-guardian");
|
||||
assert_eq!(payload[0]["event_params"]["turn_id"], "turn-guardian");
|
||||
assert_eq!(payload[0]["event_params"]["review_id"], "review-guardian");
|
||||
@@ -2426,7 +2391,6 @@ async fn item_review_summaries_do_not_cross_threads_with_reused_item_ids() {
|
||||
fn subagent_thread_started_review_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-review".to_string(),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
@@ -2470,9 +2434,8 @@ fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() {
|
||||
.expect("valid thread id");
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-spawn".to_string(),
|
||||
parent_thread_id: Some(parent_thread_id.to_string()),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
client_name: "codex-tui".to_string(),
|
||||
client_version: "1.0.0".to_string(),
|
||||
@@ -2490,21 +2453,18 @@ fn subagent_thread_started_thread_spawn_serializes_parent_thread_id() {
|
||||
));
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize thread spawn subagent event");
|
||||
assert_eq!(payload["event_params"]["thread_id"], "thread-spawn");
|
||||
assert_eq!(payload["event_params"]["thread_source"], "subagent");
|
||||
assert_eq!(payload["event_params"]["subagent_source"], "thread_spawn");
|
||||
assert_eq!(
|
||||
payload["event_params"]["parent_thread_id"],
|
||||
"11111111-1111-1111-1111-111111111111"
|
||||
);
|
||||
assert_eq!(payload["event_params"]["session_id"], "session-root");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_thread_started_memory_consolidation_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-memory".to_string(),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
@@ -2530,7 +2490,6 @@ fn subagent_thread_started_memory_consolidation_serializes_expected_shape() {
|
||||
fn subagent_thread_started_other_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-guardian".to_string(),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
@@ -2550,14 +2509,10 @@ fn subagent_thread_started_other_serializes_expected_shape() {
|
||||
|
||||
#[test]
|
||||
fn subagent_thread_started_other_serializes_explicit_parent_thread_id() {
|
||||
let parent_thread_id =
|
||||
codex_protocol::ThreadId::from_string("33333333-3333-4333-8333-333333333333")
|
||||
.expect("valid thread id");
|
||||
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-guardian".to_string(),
|
||||
parent_thread_id: Some(parent_thread_id.to_string()),
|
||||
parent_thread_id: Some("parent-thread-guardian".to_string()),
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
client_name: "codex-tui".to_string(),
|
||||
client_version: "1.0.0".to_string(),
|
||||
@@ -2572,7 +2527,7 @@ fn subagent_thread_started_other_serializes_explicit_parent_thread_id() {
|
||||
assert_eq!(payload["event_params"]["subagent_source"], "guardian");
|
||||
assert_eq!(
|
||||
payload["event_params"]["parent_thread_id"],
|
||||
"33333333-3333-4333-8333-333333333333"
|
||||
"parent-thread-guardian"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2585,7 +2540,6 @@ async fn subagent_thread_started_publishes_without_initialize() {
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-review".to_string(),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
@@ -2659,9 +2613,8 @@ async fn subagent_thread_started_inherits_parent_connection_for_new_thread() {
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-review".to_string(),
|
||||
parent_thread_id: Some(parent_thread_id.to_string()),
|
||||
parent_thread_id: None,
|
||||
product_client_id: "parent-client".to_string(),
|
||||
client_name: "parent-client".to_string(),
|
||||
client_version: "1.0.0".to_string(),
|
||||
@@ -2707,8 +2660,6 @@ async fn subagent_thread_started_inherits_parent_connection_for_new_thread() {
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(payload[0]["event_params"]["session_id"], "session-root");
|
||||
assert_eq!(payload[0]["event_params"]["thread_id"], "thread-review");
|
||||
assert_eq!(
|
||||
payload[0]["event_params"]["app_server_client"]["product_client_id"],
|
||||
"parent-client"
|
||||
@@ -2729,7 +2680,6 @@ async fn subagent_tool_items_inherit_parent_connection_metadata() {
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::SubAgentThreadStarted(
|
||||
SubAgentThreadStartedInput {
|
||||
session_id: "session-root".to_string(),
|
||||
thread_id: "thread-subagent".to_string(),
|
||||
parent_thread_id: Some("thread-1".to_string()),
|
||||
product_client_id: "codex-tui".to_string(),
|
||||
@@ -3235,7 +3185,6 @@ fn turn_event_serializes_expected_shape() {
|
||||
event_type: "codex_turn_event",
|
||||
event_params: crate::events::CodexTurnEventParams {
|
||||
thread_id: "thread-2".to_string(),
|
||||
session_id: "session-thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
app_server_client: sample_app_server_client_metadata(),
|
||||
runtime: sample_runtime_metadata(),
|
||||
@@ -3256,14 +3205,10 @@ fn turn_event_serializes_expected_shape() {
|
||||
sandbox_network_access: true,
|
||||
collaboration_mode: Some("plan"),
|
||||
personality: Some("pragmatic".to_string()),
|
||||
workspace_kind: Some("projectless".to_string()),
|
||||
num_input_images: 2,
|
||||
is_first_turn: true,
|
||||
status: Some(TurnStatus::Completed),
|
||||
turn_error: None,
|
||||
codex_error_kind: None,
|
||||
codex_error_subreason: None,
|
||||
codex_error_http_status_code: None,
|
||||
steer_count: Some(0),
|
||||
total_tool_call_count: None,
|
||||
shell_command_count: None,
|
||||
@@ -3290,7 +3235,6 @@ fn turn_event_serializes_expected_shape() {
|
||||
"event_type": "codex_turn_event",
|
||||
"event_params": {
|
||||
"thread_id": "thread-2",
|
||||
"session_id": "session-thread-2",
|
||||
"turn_id": "turn-2",
|
||||
"submission_type": null,
|
||||
"app_server_client": {
|
||||
@@ -3322,14 +3266,10 @@ fn turn_event_serializes_expected_shape() {
|
||||
"sandbox_network_access": true,
|
||||
"collaboration_mode": "plan",
|
||||
"personality": "pragmatic",
|
||||
"workspace_kind": "projectless",
|
||||
"num_input_images": 2,
|
||||
"is_first_turn": true,
|
||||
"status": "completed",
|
||||
"turn_error": null,
|
||||
"codex_error_kind": null,
|
||||
"codex_error_subreason": null,
|
||||
"codex_error_http_status_code": null,
|
||||
"steer_count": 0,
|
||||
"total_tool_call_count": null,
|
||||
"shell_command_count": null,
|
||||
@@ -3396,10 +3336,6 @@ async fn accepted_turn_steer_emits_expected_event() {
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize turn steer event");
|
||||
assert_eq!(payload["event_type"], json!("codex_turn_steer_event"));
|
||||
assert_eq!(payload["event_params"]["thread_id"], json!("thread-2"));
|
||||
assert_eq!(
|
||||
payload["event_params"]["session_id"],
|
||||
json!("session-thread-2")
|
||||
);
|
||||
assert_eq!(payload["event_params"]["expected_turn_id"], json!("turn-2"));
|
||||
assert_eq!(payload["event_params"]["accepted_turn_id"], json!("turn-2"));
|
||||
assert_eq!(payload["event_params"]["num_input_images"], json!(1));
|
||||
@@ -3617,10 +3553,6 @@ async fn turn_lifecycle_emits_turn_event() {
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize turn event");
|
||||
assert_eq!(payload["event_type"], json!("codex_turn_event"));
|
||||
assert_eq!(payload["event_params"]["thread_id"], json!("thread-2"));
|
||||
assert_eq!(
|
||||
payload["event_params"]["session_id"],
|
||||
json!("session-thread-2")
|
||||
);
|
||||
assert_eq!(payload["event_params"]["turn_id"], json!("turn-2"));
|
||||
assert_eq!(
|
||||
payload["event_params"]["app_server_client"],
|
||||
@@ -3643,7 +3575,6 @@ async fn turn_lifecycle_emits_turn_event() {
|
||||
);
|
||||
assert!(payload["event_params"].get("product_client_id").is_none());
|
||||
assert_eq!(payload["event_params"]["ephemeral"], json!(false));
|
||||
assert_eq!(payload["event_params"]["workspace_kind"], json!(null));
|
||||
assert_eq!(payload["event_params"]["num_input_images"], json!(1));
|
||||
assert_eq!(payload["event_params"]["status"], json!("completed"));
|
||||
assert_eq!(payload["event_params"]["steer_count"], json!(0));
|
||||
@@ -3700,7 +3631,6 @@ async fn turn_event_counts_completed_tool_items() {
|
||||
status: McpToolCallStatus::Completed,
|
||||
arguments: json!({}),
|
||||
mcp_app_resource_uri: None,
|
||||
plugin_id: None,
|
||||
result: None,
|
||||
error: None,
|
||||
duration_ms: Some(2),
|
||||
@@ -3996,18 +3926,6 @@ async fn turn_lifecycle_emits_failed_turn_event() {
|
||||
/*include_token_usage*/ false,
|
||||
)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::TurnCodexError(Box::new(
|
||||
TurnCodexErrorFact::from_codex_err(
|
||||
"thread-2".to_string(),
|
||||
"turn-2".to_string(),
|
||||
&CodexErr::InvalidRequest("unknown turn environment id `env-2`".to_string()),
|
||||
),
|
||||
))),
|
||||
&mut out,
|
||||
)
|
||||
.await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Notification(Box::new(sample_turn_completed_notification(
|
||||
@@ -4024,18 +3942,6 @@ async fn turn_lifecycle_emits_failed_turn_event() {
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize turn event");
|
||||
assert_eq!(payload["event_params"]["status"], json!("failed"));
|
||||
assert_eq!(payload["event_params"]["turn_error"], json!("badRequest"));
|
||||
assert_eq!(
|
||||
payload["event_params"]["codex_error_kind"],
|
||||
json!("invalid_request")
|
||||
);
|
||||
assert_eq!(
|
||||
payload["event_params"]["codex_error_subreason"],
|
||||
json!("unknown turn environment id `env-2`")
|
||||
);
|
||||
assert_eq!(
|
||||
payload["event_params"]["codex_error_http_status_code"],
|
||||
json!(null)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -4068,7 +3974,6 @@ async fn turn_lifecycle_emits_interrupted_turn_event_without_error() {
|
||||
let payload = serde_json::to_value(&out[0]).expect("serialize turn event");
|
||||
assert_eq!(payload["event_params"]["status"], json!("interrupted"));
|
||||
assert_eq!(payload["event_params"]["turn_error"], json!(null));
|
||||
assert_eq!(payload["event_params"]["codex_error_kind"], json!(null));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::facts::SkillInvocation;
|
||||
use crate::facts::SkillInvokedInput;
|
||||
use crate::facts::SubAgentThreadStartedInput;
|
||||
use crate::facts::TrackEventsContext;
|
||||
use crate::facts::TurnCodexErrorFact;
|
||||
use crate::facts::TurnResolvedConfigFact;
|
||||
use crate::facts::TurnTokenUsageFact;
|
||||
use crate::reducer::AnalyticsReducer;
|
||||
@@ -257,12 +256,6 @@ impl AnalyticsEventsClient {
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_turn_codex_error(&self, fact: TurnCodexErrorFact) {
|
||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::TurnCodexError(
|
||||
Box::new(fact),
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn track_plugin_installed(&self, plugin: PluginTelemetryMetadata) {
|
||||
self.record_fact(AnalyticsFact::Custom(
|
||||
CustomAnalyticsFact::PluginStateChanged(PluginStateChangedInput {
|
||||
|
||||
@@ -89,7 +89,6 @@ fn sample_turn_start_request() -> ClientRequest {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: TurnStartParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
client_user_message_id: None,
|
||||
input: Vec::new(),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -102,10 +101,8 @@ fn sample_turn_steer_request() -> ClientRequest {
|
||||
params: TurnSteerParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
expected_turn_id: "turn-1".to_string(),
|
||||
client_user_message_id: None,
|
||||
input: Vec::new(),
|
||||
responsesapi_client_metadata: None,
|
||||
additional_context: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -124,7 +121,6 @@ fn sample_thread(thread_id: &str) -> Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
parent_thread_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral: false,
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -175,7 +171,6 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
initial_turns_page: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::time::Instant;
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use crate::facts::AppInvocation;
|
||||
use crate::facts::CodexCompactionEvent;
|
||||
use crate::facts::CodexErrKind;
|
||||
use crate::facts::CompactionImplementation;
|
||||
use crate::facts::CompactionPhase;
|
||||
use crate::facts::CompactionReason;
|
||||
@@ -148,7 +147,6 @@ pub(crate) struct CodexRuntimeMetadata {
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct ThreadInitializedEventParams {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) session_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) model: String,
|
||||
@@ -422,7 +420,6 @@ impl GuardianReviewAnalyticsResult {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct GuardianReviewEventPayload {
|
||||
pub(crate) session_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
#[serde(flatten)]
|
||||
@@ -741,7 +738,6 @@ pub(crate) struct CodexHookRunEventRequest {
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexCompactionEventParams {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) session_id: String,
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
@@ -771,7 +767,6 @@ pub(crate) struct CodexCompactionEventRequest {
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexTurnEventParams {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) session_id: String,
|
||||
pub(crate) turn_id: String,
|
||||
// TODO(rhan-oai): Populate once queued/default submission type is plumbed from
|
||||
// the turn/start callsites instead of always being reported as None.
|
||||
@@ -794,14 +789,10 @@ pub(crate) struct CodexTurnEventParams {
|
||||
pub(crate) sandbox_network_access: bool,
|
||||
pub(crate) collaboration_mode: Option<&'static str>,
|
||||
pub(crate) personality: Option<String>,
|
||||
pub(crate) workspace_kind: Option<String>,
|
||||
pub(crate) num_input_images: usize,
|
||||
pub(crate) is_first_turn: bool,
|
||||
pub(crate) status: Option<TurnStatus>,
|
||||
pub(crate) turn_error: Option<CodexErrorInfo>,
|
||||
pub(crate) codex_error_kind: Option<CodexErrKind>,
|
||||
pub(crate) codex_error_subreason: Option<String>,
|
||||
pub(crate) codex_error_http_status_code: Option<u16>,
|
||||
pub(crate) steer_count: Option<usize>,
|
||||
pub(crate) total_tool_call_count: Option<usize>,
|
||||
pub(crate) shell_command_count: Option<usize>,
|
||||
@@ -830,7 +821,6 @@ pub(crate) struct CodexTurnEventRequest {
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct CodexTurnSteerEventParams {
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) session_id: String,
|
||||
pub(crate) expected_turn_id: Option<String>,
|
||||
pub(crate) accepted_turn_id: Option<String>,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
@@ -936,7 +926,6 @@ pub(crate) fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPlu
|
||||
|
||||
pub(crate) fn codex_compaction_event_params(
|
||||
input: CodexCompactionEvent,
|
||||
session_id: String,
|
||||
app_server_client: CodexAppServerClientMetadata,
|
||||
runtime: CodexRuntimeMetadata,
|
||||
thread_source: Option<ThreadSource>,
|
||||
@@ -945,7 +934,6 @@ pub(crate) fn codex_compaction_event_params(
|
||||
) -> CodexCompactionEventParams {
|
||||
CodexCompactionEventParams {
|
||||
thread_id: input.thread_id,
|
||||
session_id,
|
||||
turn_id: input.turn_id,
|
||||
app_server_client,
|
||||
runtime,
|
||||
@@ -1002,8 +990,6 @@ fn analytics_hook_event_name(event_name: HookEventName) -> &'static str {
|
||||
HookEventName::PostCompact => "PostCompact",
|
||||
HookEventName::SessionStart => "SessionStart",
|
||||
HookEventName::UserPromptSubmit => "UserPromptSubmit",
|
||||
HookEventName::SubagentStart => "SubagentStart",
|
||||
HookEventName::SubagentStop => "SubagentStop",
|
||||
HookEventName::Stop => "Stop",
|
||||
}
|
||||
}
|
||||
@@ -1017,7 +1003,6 @@ fn analytics_hook_source(source: HookSource) -> &'static str {
|
||||
HookSource::SessionFlags => "session_flags",
|
||||
HookSource::Plugin => "plugin",
|
||||
HookSource::CloudRequirements => "cloud_requirements",
|
||||
HookSource::CloudManagedConfig => "cloud_managed_config",
|
||||
HookSource::LegacyManagedConfigFile => "legacy_managed_config_file",
|
||||
HookSource::LegacyManagedConfigMdm => "legacy_managed_config_mdm",
|
||||
HookSource::Unknown => "unknown",
|
||||
@@ -1039,7 +1024,6 @@ pub(crate) fn subagent_thread_started_event_request(
|
||||
) -> ThreadInitializedEvent {
|
||||
let event_params = ThreadInitializedEventParams {
|
||||
thread_id: input.thread_id,
|
||||
session_id: input.session_id,
|
||||
app_server_client: CodexAppServerClientMetadata {
|
||||
product_client_id: input.product_client_id,
|
||||
client_name: Some(input.client_name),
|
||||
@@ -1053,7 +1037,9 @@ pub(crate) fn subagent_thread_started_event_request(
|
||||
thread_source: Some(ThreadSource::Subagent),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: Some(subagent_source_name(&input.subagent_source)),
|
||||
parent_thread_id: input.parent_thread_id,
|
||||
parent_thread_id: input
|
||||
.parent_thread_id
|
||||
.or_else(|| subagent_parent_thread_id(&input.subagent_source)),
|
||||
created_at: input.created_at,
|
||||
};
|
||||
ThreadInitializedEvent {
|
||||
@@ -1063,7 +1049,22 @@ pub(crate) fn subagent_thread_started_event_request(
|
||||
}
|
||||
|
||||
pub(crate) fn subagent_source_name(subagent_source: &SubAgentSource) -> String {
|
||||
subagent_source.kind().to_string()
|
||||
match subagent_source {
|
||||
SubAgentSource::Review => "review".to_string(),
|
||||
SubAgentSource::Compact => "compact".to_string(),
|
||||
SubAgentSource::ThreadSpawn { .. } => "thread_spawn".to_string(),
|
||||
SubAgentSource::MemoryConsolidation => "memory_consolidation".to_string(),
|
||||
SubAgentSource::Other(other) => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn subagent_parent_thread_id(subagent_source: &SubAgentSource) -> Option<String> {
|
||||
match subagent_source {
|
||||
SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id, ..
|
||||
} => Some(parent_thread_id.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn analytics_hook_status(status: HookRunStatus) -> HookRunStatus {
|
||||
|
||||
@@ -15,7 +15,6 @@ use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -30,9 +29,6 @@ use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const INVALID_REQUEST_SUBREASON_MAX_BYTES: usize = 512;
|
||||
const INVALID_REQUEST_SUBREASON_TRUNCATION_SUFFIX: &str = "...";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct AcceptedLineFingerprint {
|
||||
pub path_hash: String,
|
||||
@@ -85,7 +81,6 @@ pub struct TurnResolvedConfigFact {
|
||||
pub sandbox_network_access: bool,
|
||||
pub collaboration_mode: ModeKind,
|
||||
pub personality: Option<Personality>,
|
||||
pub workspace_kind: Option<String>,
|
||||
pub is_first_turn: bool,
|
||||
}
|
||||
|
||||
@@ -104,147 +99,6 @@ pub struct TurnTokenUsageFact {
|
||||
pub token_usage: TokenUsage,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TurnCodexErrorFact {
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) thread_id: String,
|
||||
pub(crate) error: TurnCodexError,
|
||||
}
|
||||
|
||||
impl TurnCodexErrorFact {
|
||||
pub fn from_codex_err(thread_id: String, turn_id: String, error: &CodexErr) -> Self {
|
||||
Self {
|
||||
turn_id,
|
||||
thread_id,
|
||||
error: TurnCodexError::from_codex_err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum CodexErrKind {
|
||||
TurnAborted,
|
||||
Stream,
|
||||
ContextWindowExceeded,
|
||||
ThreadNotFound,
|
||||
AgentLimitReached,
|
||||
SessionConfiguredNotFirstEvent,
|
||||
Timeout,
|
||||
RequestTimeout,
|
||||
Spawn,
|
||||
Interrupted,
|
||||
UnexpectedStatus,
|
||||
InvalidRequest,
|
||||
InvalidImageRequest,
|
||||
UsageLimitReached,
|
||||
ServerOverloaded,
|
||||
CyberPolicy,
|
||||
ResponseStreamFailed,
|
||||
ConnectionFailed,
|
||||
QuotaExceeded,
|
||||
UsageNotIncluded,
|
||||
InternalServerError,
|
||||
RetryLimit,
|
||||
InternalAgentDied,
|
||||
Sandbox,
|
||||
LandlockSandboxExecutableNotProvided,
|
||||
UnsupportedOperation,
|
||||
RefreshTokenFailed,
|
||||
Fatal,
|
||||
Io,
|
||||
Json,
|
||||
#[cfg(target_os = "linux")]
|
||||
LandlockRuleset,
|
||||
#[cfg(target_os = "linux")]
|
||||
LandlockPathFd,
|
||||
TokioJoin,
|
||||
EnvVar,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TurnCodexError {
|
||||
pub(crate) kind: CodexErrKind,
|
||||
pub(crate) subreason: Option<String>,
|
||||
pub(crate) http_status_code: Option<u16>,
|
||||
}
|
||||
|
||||
impl TurnCodexError {
|
||||
fn from_codex_err(error: &CodexErr) -> Self {
|
||||
Self {
|
||||
kind: error.into(),
|
||||
subreason: match error {
|
||||
CodexErr::InvalidRequest(message) => {
|
||||
// InvalidRequest can contain raw provider response bodies, so bound the
|
||||
// analytics copy without changing the source CodexErr.
|
||||
let subreason = if message.len() <= INVALID_REQUEST_SUBREASON_MAX_BYTES {
|
||||
message.clone()
|
||||
} else {
|
||||
let truncated_len = message.floor_char_boundary(
|
||||
INVALID_REQUEST_SUBREASON_MAX_BYTES
|
||||
.saturating_sub(INVALID_REQUEST_SUBREASON_TRUNCATION_SUFFIX.len()),
|
||||
);
|
||||
format!(
|
||||
"{}{INVALID_REQUEST_SUBREASON_TRUNCATION_SUFFIX}",
|
||||
&message[..truncated_len]
|
||||
)
|
||||
};
|
||||
Some(subreason)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
http_status_code: error.http_status_code_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CodexErr> for CodexErrKind {
|
||||
fn from(error: &CodexErr) -> Self {
|
||||
match error {
|
||||
CodexErr::TurnAborted => CodexErrKind::TurnAborted,
|
||||
CodexErr::Stream(..) => CodexErrKind::Stream,
|
||||
CodexErr::ContextWindowExceeded => CodexErrKind::ContextWindowExceeded,
|
||||
CodexErr::ThreadNotFound(_) => CodexErrKind::ThreadNotFound,
|
||||
CodexErr::AgentLimitReached { .. } => CodexErrKind::AgentLimitReached,
|
||||
CodexErr::SessionConfiguredNotFirstEvent => {
|
||||
CodexErrKind::SessionConfiguredNotFirstEvent
|
||||
}
|
||||
CodexErr::Timeout => CodexErrKind::Timeout,
|
||||
CodexErr::RequestTimeout => CodexErrKind::RequestTimeout,
|
||||
CodexErr::Spawn => CodexErrKind::Spawn,
|
||||
CodexErr::Interrupted => CodexErrKind::Interrupted,
|
||||
CodexErr::UnexpectedStatus(_) => CodexErrKind::UnexpectedStatus,
|
||||
CodexErr::InvalidRequest(_) => CodexErrKind::InvalidRequest,
|
||||
CodexErr::InvalidImageRequest() => CodexErrKind::InvalidImageRequest,
|
||||
CodexErr::UsageLimitReached(_) => CodexErrKind::UsageLimitReached,
|
||||
CodexErr::ServerOverloaded => CodexErrKind::ServerOverloaded,
|
||||
CodexErr::CyberPolicy { .. } => CodexErrKind::CyberPolicy,
|
||||
CodexErr::ResponseStreamFailed(_) => CodexErrKind::ResponseStreamFailed,
|
||||
CodexErr::ConnectionFailed(_) => CodexErrKind::ConnectionFailed,
|
||||
CodexErr::QuotaExceeded => CodexErrKind::QuotaExceeded,
|
||||
CodexErr::UsageNotIncluded => CodexErrKind::UsageNotIncluded,
|
||||
CodexErr::InternalServerError => CodexErrKind::InternalServerError,
|
||||
CodexErr::RetryLimit(_) => CodexErrKind::RetryLimit,
|
||||
CodexErr::InternalAgentDied => CodexErrKind::InternalAgentDied,
|
||||
CodexErr::Sandbox(_) => CodexErrKind::Sandbox,
|
||||
CodexErr::LandlockSandboxExecutableNotProvided => {
|
||||
CodexErrKind::LandlockSandboxExecutableNotProvided
|
||||
}
|
||||
CodexErr::UnsupportedOperation(_) => CodexErrKind::UnsupportedOperation,
|
||||
CodexErr::RefreshTokenFailed(_) => CodexErrKind::RefreshTokenFailed,
|
||||
CodexErr::Fatal(_) => CodexErrKind::Fatal,
|
||||
CodexErr::Io(_) => CodexErrKind::Io,
|
||||
CodexErr::Json(_) => CodexErrKind::Json,
|
||||
#[cfg(target_os = "linux")]
|
||||
CodexErr::LandlockRuleset(_) => CodexErrKind::LandlockRuleset,
|
||||
#[cfg(target_os = "linux")]
|
||||
CodexErr::LandlockPathFd(_) => CodexErrKind::LandlockPathFd,
|
||||
CodexErr::TokioJoin(_) => CodexErrKind::TokioJoin,
|
||||
CodexErr::EnvVar(_) => CodexErrKind::EnvVar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TurnStatus {
|
||||
@@ -345,7 +199,6 @@ pub struct AppInvocation {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubAgentThreadStartedInput {
|
||||
pub session_id: String,
|
||||
pub thread_id: String,
|
||||
pub parent_thread_id: Option<String>,
|
||||
pub product_client_id: String,
|
||||
@@ -376,7 +229,6 @@ pub enum CompactionReason {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompactionImplementation {
|
||||
Responses,
|
||||
ResponsesCompactionV2,
|
||||
ResponsesCompact,
|
||||
}
|
||||
|
||||
@@ -475,7 +327,6 @@ pub(crate) enum CustomAnalyticsFact {
|
||||
GuardianReview(Box<GuardianReviewEventParams>),
|
||||
TurnResolvedConfig(Box<TurnResolvedConfigFact>),
|
||||
TurnTokenUsage(Box<TurnTokenUsageFact>),
|
||||
TurnCodexError(Box<TurnCodexErrorFact>),
|
||||
SkillInvoked(SkillInvokedInput),
|
||||
AppMentioned(AppMentionedInput),
|
||||
AppUsed(AppUsedInput),
|
||||
|
||||
@@ -38,7 +38,6 @@ pub use facts::SkillInvocation;
|
||||
pub use facts::SubAgentThreadStartedInput;
|
||||
pub use facts::ThreadInitializationMode;
|
||||
pub use facts::TrackEventsContext;
|
||||
pub use facts::TurnCodexErrorFact;
|
||||
pub use facts::TurnResolvedConfigFact;
|
||||
pub use facts::TurnStatus;
|
||||
pub use facts::TurnSteerRejectionReason;
|
||||
|
||||
@@ -55,6 +55,7 @@ use crate::events::codex_hook_run_metadata;
|
||||
use crate::events::codex_plugin_metadata;
|
||||
use crate::events::codex_plugin_used_metadata;
|
||||
use crate::events::plugin_state_event_type;
|
||||
use crate::events::subagent_parent_thread_id;
|
||||
use crate::events::subagent_source_name;
|
||||
use crate::events::subagent_thread_started_event_request;
|
||||
use crate::facts::AnalyticsFact;
|
||||
@@ -70,8 +71,6 @@ use crate::facts::PluginUsedInput;
|
||||
use crate::facts::SkillInvokedInput;
|
||||
use crate::facts::SubAgentThreadStartedInput;
|
||||
use crate::facts::ThreadInitializationMode;
|
||||
use crate::facts::TurnCodexError;
|
||||
use crate::facts::TurnCodexErrorFact;
|
||||
use crate::facts::TurnResolvedConfigFact;
|
||||
use crate::facts::TurnStatus;
|
||||
use crate::facts::TurnSteerRejectionReason;
|
||||
@@ -256,7 +255,6 @@ struct ItemReviewSummary {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ThreadMetadataState {
|
||||
session_id: String,
|
||||
thread_source: Option<ThreadSource>,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
subagent_source: Option<String>,
|
||||
@@ -265,24 +263,24 @@ struct ThreadMetadataState {
|
||||
|
||||
impl ThreadMetadataState {
|
||||
fn from_thread_metadata(
|
||||
session_id: String,
|
||||
session_source: &SessionSource,
|
||||
thread_source: Option<ThreadSource>,
|
||||
parent_thread_id: Option<String>,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
) -> Self {
|
||||
let subagent_source = match session_source {
|
||||
SessionSource::SubAgent(subagent_source) => Some(subagent_source_name(subagent_source)),
|
||||
let (subagent_source, parent_thread_id) = match session_source {
|
||||
SessionSource::SubAgent(subagent_source) => (
|
||||
Some(subagent_source_name(subagent_source)),
|
||||
subagent_parent_thread_id(subagent_source),
|
||||
),
|
||||
SessionSource::Cli
|
||||
| SessionSource::VSCode
|
||||
| SessionSource::Exec
|
||||
| SessionSource::Mcp
|
||||
| SessionSource::Custom(_)
|
||||
| SessionSource::Internal(_)
|
||||
| SessionSource::Unknown => None,
|
||||
| SessionSource::Unknown => (None, None),
|
||||
};
|
||||
Self {
|
||||
session_id,
|
||||
thread_source,
|
||||
initialization_mode,
|
||||
subagent_source,
|
||||
@@ -324,7 +322,6 @@ struct TurnState {
|
||||
started_at: Option<u64>,
|
||||
token_usage: Option<TokenUsage>,
|
||||
completed: Option<CompletedTurnState>,
|
||||
codex_error: Option<TurnCodexError>,
|
||||
latest_diff: Option<String>,
|
||||
steer_count: usize,
|
||||
tool_counts: TurnToolCounts,
|
||||
@@ -464,9 +461,6 @@ impl AnalyticsReducer {
|
||||
CustomAnalyticsFact::TurnTokenUsage(input) => {
|
||||
self.ingest_turn_token_usage(*input, out).await;
|
||||
}
|
||||
CustomAnalyticsFact::TurnCodexError(input) => {
|
||||
self.ingest_turn_codex_error(*input);
|
||||
}
|
||||
CustomAnalyticsFact::SkillInvoked(input) => {
|
||||
self.ingest_skill_invoked(input, out).await;
|
||||
}
|
||||
@@ -519,7 +513,10 @@ impl AnalyticsReducer {
|
||||
input: SubAgentThreadStartedInput,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let parent_thread_id = input.parent_thread_id.clone();
|
||||
let parent_thread_id = input
|
||||
.parent_thread_id
|
||||
.clone()
|
||||
.or_else(|| subagent_parent_thread_id(&input.subagent_source));
|
||||
let parent_connection_id = parent_thread_id
|
||||
.as_ref()
|
||||
.and_then(|parent_thread_id| self.threads.get(parent_thread_id))
|
||||
@@ -528,7 +525,6 @@ impl AnalyticsReducer {
|
||||
thread_state
|
||||
.metadata
|
||||
.get_or_insert_with(|| ThreadMetadataState {
|
||||
session_id: input.session_id.clone(),
|
||||
thread_source: Some(ThreadSource::Subagent),
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
subagent_source: Some(subagent_source_name(&input.subagent_source)),
|
||||
@@ -547,8 +543,8 @@ impl AnalyticsReducer {
|
||||
input: GuardianReviewEventParams,
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let Some((connection_state, thread_metadata)) =
|
||||
self.thread_context_or_warn(AnalyticsDropSite::guardian(&input))
|
||||
let Some(connection_state) =
|
||||
self.thread_connection_or_warn(AnalyticsDropSite::guardian(&input))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -556,7 +552,6 @@ impl AnalyticsReducer {
|
||||
GuardianReviewEventRequest {
|
||||
event_type: "codex_guardian_review",
|
||||
event_params: GuardianReviewEventPayload {
|
||||
session_id: thread_metadata.session_id.clone(),
|
||||
app_server_client: connection_state.app_server_client.clone(),
|
||||
runtime: connection_state.runtime.clone(),
|
||||
guardian_review: input,
|
||||
@@ -612,7 +607,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -637,7 +631,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -647,29 +640,6 @@ impl AnalyticsReducer {
|
||||
self.maybe_emit_turn_event(&turn_id, out).await;
|
||||
}
|
||||
|
||||
fn ingest_turn_codex_error(&mut self, input: TurnCodexErrorFact) {
|
||||
let TurnCodexErrorFact {
|
||||
turn_id,
|
||||
thread_id,
|
||||
error,
|
||||
} = input;
|
||||
let turn_state = self.turns.entry(turn_id).or_insert(TurnState {
|
||||
connection_id: None,
|
||||
thread_id: None,
|
||||
num_input_images: None,
|
||||
resolved_config: None,
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
});
|
||||
turn_state.thread_id.get_or_insert(thread_id);
|
||||
turn_state.codex_error = Some(error);
|
||||
}
|
||||
|
||||
async fn ingest_skill_invoked(
|
||||
&mut self,
|
||||
input: SkillInvokedInput,
|
||||
@@ -826,7 +796,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -1186,7 +1155,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -1208,7 +1176,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -1228,7 +1195,6 @@ impl AnalyticsReducer {
|
||||
started_at: None,
|
||||
token_usage: None,
|
||||
completed: None,
|
||||
codex_error: None,
|
||||
latest_diff: None,
|
||||
steer_count: 0,
|
||||
tool_counts: TurnToolCounts::default(),
|
||||
@@ -1265,17 +1231,13 @@ impl AnalyticsReducer {
|
||||
out: &mut Vec<TrackEventRequest>,
|
||||
) {
|
||||
let session_source: SessionSource = thread.source.into();
|
||||
let session_id = thread.session_id;
|
||||
let thread_id = thread.id;
|
||||
let parent_thread_id = thread.parent_thread_id;
|
||||
let Some(connection_state) = self.connections.get(&connection_id) else {
|
||||
return;
|
||||
};
|
||||
let thread_metadata = ThreadMetadataState::from_thread_metadata(
|
||||
session_id.clone(),
|
||||
&session_source,
|
||||
thread.thread_source.map(Into::into),
|
||||
parent_thread_id,
|
||||
initialization_mode,
|
||||
);
|
||||
self.threads.insert(
|
||||
@@ -1290,7 +1252,6 @@ impl AnalyticsReducer {
|
||||
event_type: "codex_thread_initialized",
|
||||
event_params: ThreadInitializedEventParams {
|
||||
thread_id,
|
||||
session_id,
|
||||
app_server_client: connection_state.app_server_client.clone(),
|
||||
runtime: connection_state.runtime.clone(),
|
||||
model,
|
||||
@@ -1316,7 +1277,6 @@ impl AnalyticsReducer {
|
||||
event_type: "codex_compaction_event",
|
||||
event_params: codex_compaction_event_params(
|
||||
input,
|
||||
thread_metadata.session_id.clone(),
|
||||
connection_state.app_server_client.clone(),
|
||||
connection_state.runtime.clone(),
|
||||
thread_metadata.thread_source,
|
||||
@@ -1419,7 +1379,6 @@ impl AnalyticsReducer {
|
||||
event_type: "codex_turn_steer_event",
|
||||
event_params: CodexTurnSteerEventParams {
|
||||
thread_id: pending_request.thread_id,
|
||||
session_id: thread_metadata.session_id.clone(),
|
||||
expected_turn_id: Some(pending_request.expected_turn_id),
|
||||
accepted_turn_id,
|
||||
app_server_client: connection_state.app_server_client.clone(),
|
||||
@@ -2483,14 +2442,11 @@ fn codex_turn_event_params(
|
||||
sandbox_network_access,
|
||||
collaboration_mode,
|
||||
personality,
|
||||
workspace_kind,
|
||||
is_first_turn,
|
||||
} = resolved_config;
|
||||
let token_usage = turn_state.token_usage.clone();
|
||||
let codex_error = turn_state.codex_error.as_ref();
|
||||
CodexTurnEventParams {
|
||||
thread_id,
|
||||
session_id: thread_metadata.session_id.clone(),
|
||||
turn_id,
|
||||
app_server_client,
|
||||
runtime,
|
||||
@@ -2516,14 +2472,10 @@ fn codex_turn_event_params(
|
||||
sandbox_network_access,
|
||||
collaboration_mode: Some(collaboration_mode_mode(collaboration_mode)),
|
||||
personality: personality_mode(personality),
|
||||
workspace_kind,
|
||||
num_input_images,
|
||||
is_first_turn,
|
||||
status: completed.status,
|
||||
turn_error: completed.turn_error,
|
||||
codex_error_kind: codex_error.map(|error| error.kind),
|
||||
codex_error_subreason: codex_error.and_then(|error| error.subreason.clone()),
|
||||
codex_error_http_status_code: codex_error.and_then(|error| error.http_status_code),
|
||||
steer_count: Some(turn_state.steer_count),
|
||||
total_tool_call_count: Some(turn_state.tool_counts.total),
|
||||
shell_command_count: Some(turn_state.tool_counts.shell_command),
|
||||
|
||||
@@ -43,7 +43,7 @@ use codex_app_server_protocol::Result as JsonRpcResult;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::NoopThreadConfigLoader;
|
||||
use codex_config::RemoteThreadConfigLoader;
|
||||
@@ -176,7 +176,6 @@ pub(crate) fn server_notification_requires_delivery(notification: &ServerNotific
|
||||
matches!(
|
||||
notification,
|
||||
ServerNotification::TurnCompleted(_)
|
||||
| ServerNotification::ThreadSettingsUpdated(_)
|
||||
| ServerNotification::ItemCompleted(_)
|
||||
| ServerNotification::AgentMessageDelta(_)
|
||||
| ServerNotification::PlanDelta(_)
|
||||
@@ -339,8 +338,8 @@ pub struct InProcessClientStartArgs {
|
||||
pub loader_overrides: LoaderOverrides,
|
||||
/// Whether config API paths should reject unknown config fields.
|
||||
pub strict_config: bool,
|
||||
/// Preloaded cloud config bundle provider.
|
||||
pub cloud_config_bundle: CloudConfigBundleLoader,
|
||||
/// Preloaded cloud requirements provider.
|
||||
pub cloud_requirements: CloudRequirementsLoader,
|
||||
/// Feedback sink used by app-server/core telemetry and logs.
|
||||
pub feedback: CodexFeedback,
|
||||
/// SQLite tracing layer used to flush recently emitted logs before feedback upload.
|
||||
@@ -406,7 +405,7 @@ impl InProcessClientStartArgs {
|
||||
cli_overrides: self.cli_overrides,
|
||||
loader_overrides: self.loader_overrides,
|
||||
strict_config: self.strict_config,
|
||||
cloud_config_bundle: self.cloud_config_bundle,
|
||||
cloud_requirements: self.cloud_requirements,
|
||||
thread_config_loader,
|
||||
feedback: self.feedback,
|
||||
log_db: self.log_db,
|
||||
@@ -1035,7 +1034,7 @@ mod tests {
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_config_bundle: CloudConfigBundleLoader::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: Some(state_db),
|
||||
@@ -1122,9 +1121,7 @@ mod tests {
|
||||
websocket,
|
||||
JSONRPCMessage::Response(JSONRPCResponse {
|
||||
id: request.id,
|
||||
result: serde_json::json!({
|
||||
"userAgent": "codex_cli_rs/9.8.7-test (Test OS; x86_64) rust",
|
||||
}),
|
||||
result: serde_json::json!({}),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -1459,7 +1456,6 @@ mod tests {
|
||||
.await
|
||||
.expect("remote client should connect");
|
||||
|
||||
assert_eq!(client.server_version(), Some("9.8.7-test"));
|
||||
let response: GetAccountResponse = client
|
||||
.request_typed(ClientRequest::GetAccount {
|
||||
request_id: RequestId::Integer(1),
|
||||
@@ -2182,13 +2178,11 @@ mod tests {
|
||||
let environment_manager = Arc::new(
|
||||
EnvironmentManager::create_for_tests(
|
||||
Some("ws://127.0.0.1:8765".to_string()),
|
||||
Some(
|
||||
ExecServerRuntimePaths::new(
|
||||
std::env::current_exe().expect("current exe"),
|
||||
/*codex_linux_sandbox_exe*/ None,
|
||||
)
|
||||
.expect("runtime paths"),
|
||||
),
|
||||
ExecServerRuntimePaths::new(
|
||||
std::env::current_exe().expect("current exe"),
|
||||
/*codex_linux_sandbox_exe*/ None,
|
||||
)
|
||||
.expect("runtime paths"),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
@@ -2199,7 +2193,7 @@ mod tests {
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_config_bundle: CloudConfigBundleLoader::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
@@ -2240,7 +2234,7 @@ mod tests {
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
strict_config: false,
|
||||
cloud_config_bundle: CloudConfigBundleLoader::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
|
||||
@@ -150,7 +150,6 @@ pub struct RemoteAppServerClient {
|
||||
command_tx: mpsc::Sender<RemoteClientCommand>,
|
||||
event_rx: mpsc::UnboundedReceiver<AppServerEvent>,
|
||||
pending_events: VecDeque<AppServerEvent>,
|
||||
server_version: Option<String>,
|
||||
worker_handle: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
@@ -181,10 +180,6 @@ impl RemoteAppServerClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_version(&self) -> Option<&str> {
|
||||
self.server_version.as_deref()
|
||||
}
|
||||
|
||||
async fn connect_with_stream<S>(
|
||||
channel_capacity: usize,
|
||||
endpoint: String,
|
||||
@@ -195,7 +190,7 @@ impl RemoteAppServerClient {
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
let mut stream = stream;
|
||||
let (pending_events, server_version) = initialize_remote_connection(
|
||||
let pending_events = initialize_remote_connection(
|
||||
&mut stream,
|
||||
&endpoint,
|
||||
initialize_params,
|
||||
@@ -471,7 +466,6 @@ impl RemoteAppServerClient {
|
||||
command_tx,
|
||||
event_rx,
|
||||
pending_events: pending_events.into(),
|
||||
server_version,
|
||||
worker_handle,
|
||||
})
|
||||
}
|
||||
@@ -612,7 +606,6 @@ impl RemoteAppServerClient {
|
||||
command_tx,
|
||||
event_rx,
|
||||
pending_events: _pending_events,
|
||||
server_version: _server_version,
|
||||
worker_handle,
|
||||
} = self;
|
||||
let mut worker_handle = worker_handle;
|
||||
@@ -800,13 +793,12 @@ async fn initialize_remote_connection<S>(
|
||||
endpoint: &str,
|
||||
params: InitializeParams,
|
||||
initialize_timeout: Duration,
|
||||
) -> IoResult<(Vec<AppServerEvent>, Option<String>)>
|
||||
) -> IoResult<Vec<AppServerEvent>>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
let initialize_request_id = RequestId::String("initialize".to_string());
|
||||
let mut pending_events = Vec::new();
|
||||
let mut server_version = None;
|
||||
write_jsonrpc_message(
|
||||
stream,
|
||||
JSONRPCMessage::Request(jsonrpc_request_from_client_request(
|
||||
@@ -830,14 +822,6 @@ where
|
||||
})?;
|
||||
match message {
|
||||
JSONRPCMessage::Response(response) if response.id == initialize_request_id => {
|
||||
server_version = response
|
||||
.result
|
||||
.get("userAgent")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|user_agent| {
|
||||
let (_, rest) = user_agent.split_once('/')?;
|
||||
rest.split_whitespace().next().map(str::to_string)
|
||||
});
|
||||
break Ok(());
|
||||
}
|
||||
JSONRPCMessage::Error(error) if error.id == initialize_request_id => {
|
||||
@@ -929,7 +913,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((pending_events, server_version))
|
||||
Ok(pending_events)
|
||||
}
|
||||
|
||||
fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent> {
|
||||
@@ -1023,7 +1007,6 @@ mod tests {
|
||||
command_tx,
|
||||
event_rx,
|
||||
pending_events: VecDeque::new(),
|
||||
server_version: None,
|
||||
worker_handle,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod pid;
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Serialize;
|
||||
@@ -32,15 +31,3 @@ 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:#}"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::io::SeekFrom;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
@@ -11,8 +10,6 @@ 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;
|
||||
@@ -21,7 +18,6 @@ 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))]
|
||||
@@ -39,25 +35,6 @@ 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,
|
||||
@@ -152,18 +129,11 @@ 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::from(stderr_log.into_std().await));
|
||||
.stderr(Stdio::null());
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -199,11 +169,8 @@ 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).context(context);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
let contents = serde_json::to_vec(&record).context("failed to serialize pid record")?;
|
||||
@@ -377,23 +344,6 @@ 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 {
|
||||
@@ -426,56 +376,6 @@ 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,10 +6,7 @@ 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]
|
||||
@@ -173,24 +170,3 @@ fn app_server_remote_control_uses_runtime_flag() {
|
||||
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(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -15,16 +14,12 @@ 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;
|
||||
|
||||
pub(crate) const CONTROL_SOCKET_RESPONSE_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
const PROBE_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 {
|
||||
@@ -32,7 +27,7 @@ pub(crate) struct ProbeInfo {
|
||||
}
|
||||
|
||||
pub(crate) async fn probe(socket_path: &Path) -> Result<ProbeInfo> {
|
||||
timeout(CONTROL_SOCKET_RESPONSE_TIMEOUT, probe_inner(socket_path))
|
||||
timeout(PROBE_TIMEOUT, probe_inner(socket_path))
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -43,42 +38,15 @@ pub(crate) async fn probe(socket_path: &Path) -> Result<ProbeInfo> {
|
||||
}
|
||||
|
||||
async fn probe_inner(socket_path: &Path) -> Result<ProbeInfo> {
|
||||
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,
|
||||
});
|
||||
send_message(&mut websocket, &initialized)
|
||||
.await
|
||||
.context("failed to send initialized notification")?;
|
||||
websocket.close(None).await.ok();
|
||||
|
||||
Ok(ProbeInfo {
|
||||
app_server_version: parse_version_from_user_agent(&initialize_response.user_agent)?,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
let (mut 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,
|
||||
id: RequestId::Integer(1),
|
||||
method: "initialize".to_string(),
|
||||
params: Some(serde_json::to_value(InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
@@ -86,63 +54,45 @@ where
|
||||
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
|
||||
},
|
||||
capabilities: None,
|
||||
})?),
|
||||
trace: None,
|
||||
});
|
||||
send_message(websocket, &initialize)
|
||||
websocket
|
||||
.send(Message::Text(serde_json::to_string(&initialize)?.into()))
|
||||
.await
|
||||
.context("failed to send initialize request")?;
|
||||
|
||||
let response = loop {
|
||||
let message = timeout(CONTROL_SOCKET_RESPONSE_TIMEOUT, read_message(websocket))
|
||||
let frame = websocket
|
||||
.next()
|
||||
.await
|
||||
.context("timed out waiting for initialize response")??;
|
||||
.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 == INITIALIZE_REQUEST_ID
|
||||
&& response.id == RequestId::Integer(1)
|
||||
{
|
||||
break response;
|
||||
}
|
||||
};
|
||||
serde_json::from_value::<InitializeResponse>(response.result)
|
||||
.context("failed to parse initialize response")
|
||||
}
|
||||
let initialize_response = serde_json::from_value::<InitializeResponse>(response.result)?;
|
||||
|
||||
pub(crate) async fn send_message<S>(
|
||||
websocket: &mut WebSocketStream<S>,
|
||||
message: &JSONRPCMessage,
|
||||
) -> Result<()>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
let initialized = JSONRPCMessage::Notification(JSONRPCNotification {
|
||||
method: "initialized".to_string(),
|
||||
params: None,
|
||||
});
|
||||
websocket
|
||||
.send(Message::Text(serde_json::to_string(message)?.into()))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
.send(Message::Text(serde_json::to_string(&initialized)?.into()))
|
||||
.await
|
||||
.context("failed to send initialized notification")?;
|
||||
websocket.close(None).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");
|
||||
}
|
||||
Ok(ProbeInfo {
|
||||
app_server_version: parse_version_from_user_agent(&initialize_response.user_agent)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_version_from_user_agent(user_agent: &str) -> Result<String> {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
mod backend;
|
||||
mod client;
|
||||
mod managed_install;
|
||||
mod remote_control_client;
|
||||
mod settings;
|
||||
mod update_loop;
|
||||
|
||||
@@ -14,7 +13,6 @@ 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;
|
||||
@@ -60,8 +58,6 @@ 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>,
|
||||
@@ -74,12 +70,6 @@ pub struct BootstrapOptions {
|
||||
pub remote_control_enabled: bool,
|
||||
}
|
||||
|
||||
/// Passively probes an existing app-server socket and returns its reported
|
||||
/// app-server version.
|
||||
pub async fn probe_app_server_version(socket_path: &Path) -> Result<String> {
|
||||
Ok(client::probe(socket_path).await?.app_server_version)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum BootstrapStatus {
|
||||
@@ -94,7 +84,6 @@ 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,
|
||||
@@ -107,20 +96,6 @@ 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,
|
||||
@@ -204,27 +179,6 @@ 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
|
||||
@@ -294,39 +248,33 @@ 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),
|
||||
)
|
||||
.await);
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::AlreadyRunning,
|
||||
self.running_backend(&settings).await?,
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
));
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
.await);
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::AlreadyRunning,
|
||||
Some(BackendKind::Pid),
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
));
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
.await)
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Started,
|
||||
Some(BackendKind::Pid),
|
||||
pid,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
async fn restart(&self) -> Result<LifecycleOutput> {
|
||||
@@ -346,14 +294,12 @@ 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),
|
||||
)
|
||||
.await)
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Restarted,
|
||||
Some(BackendKind::Pid),
|
||||
pid,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -406,14 +352,12 @@ 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,
|
||||
)
|
||||
.await);
|
||||
return Ok(self.output(
|
||||
LifecycleStatus::Stopped,
|
||||
Some(BackendKind::Pid),
|
||||
/*pid*/ None,
|
||||
/*app_server_version*/ None,
|
||||
));
|
||||
}
|
||||
|
||||
if client::probe(&self.socket_path).await.is_ok() {
|
||||
@@ -422,27 +366,23 @@ impl Daemon {
|
||||
));
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.output(
|
||||
LifecycleStatus::NotRunning,
|
||||
/*backend*/ None,
|
||||
/*pid*/ None,
|
||||
/*app_server_version*/ None,
|
||||
)
|
||||
.await)
|
||||
Ok(self.output(
|
||||
LifecycleStatus::NotRunning,
|
||||
/*backend*/ None,
|
||||
/*pid*/ None,
|
||||
/*app_server_version*/ None,
|
||||
))
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
.await)
|
||||
Ok(self.output(
|
||||
LifecycleStatus::Running,
|
||||
self.running_backend(&settings).await?,
|
||||
/*pid*/ None,
|
||||
Some(info.app_server_version),
|
||||
))
|
||||
}
|
||||
|
||||
async fn wait_until_ready(&self) -> Result<client::ProbeInfo> {
|
||||
@@ -455,34 +395,17 @@ impl Daemon {
|
||||
sleep(START_POLL_INTERVAL).await;
|
||||
}
|
||||
Err(err) => {
|
||||
let context = self.app_server_not_ready_context().await;
|
||||
return Err(err).context(context);
|
||||
return Err(err).with_context(|| {
|
||||
format!(
|
||||
"app server did not become ready on {}",
|
||||
self.socket_path.display()
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -507,16 +430,6 @@ 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
|
||||
@@ -599,14 +512,12 @@ 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,
|
||||
@@ -666,16 +577,6 @@ impl Daemon {
|
||||
))
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
@@ -735,20 +636,17 @@ impl Daemon {
|
||||
})
|
||||
}
|
||||
|
||||
async fn output(
|
||||
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,
|
||||
@@ -837,12 +735,10 @@ 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;
|
||||
@@ -855,6 +751,22 @@ 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!(
|
||||
@@ -935,26 +847,12 @@ 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")
|
||||
@@ -966,59 +864,15 @@ 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
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(¬ification).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(¬ification) 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,32 +5,30 @@
|
||||
"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"
|
||||
},
|
||||
"AddCreditsNudgeCreditType": {
|
||||
"enum": [
|
||||
"credits",
|
||||
"usage_limit"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalContextEntry": {
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"$ref": "#/definitions/AdditionalContextKind"
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"value"
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalContextKind": {
|
||||
"AddCreditsNudgeCreditType": {
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"application"
|
||||
"credits",
|
||||
"usage_limit"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -445,6 +443,7 @@
|
||||
]
|
||||
},
|
||||
"includeLayers": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
@@ -612,13 +611,6 @@
|
||||
"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"
|
||||
@@ -742,7 +734,8 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"classification"
|
||||
"classification",
|
||||
"includeLogs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1004,26 +997,6 @@
|
||||
],
|
||||
"title": "InputImageFunctionCallOutputContentItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"encrypted_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"encrypted_content"
|
||||
],
|
||||
"title": "EncryptedContentFunctionCallOutputContentItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"encrypted_content",
|
||||
"type"
|
||||
],
|
||||
"title": "EncryptedContentFunctionCallOutputContentItem",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1054,6 +1027,7 @@
|
||||
"GetAccountParams": {
|
||||
"properties": {
|
||||
"refreshToken": {
|
||||
"default": false,
|
||||
"description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
@@ -1085,8 +1059,6 @@
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"low",
|
||||
"high",
|
||||
"original"
|
||||
],
|
||||
@@ -1167,12 +1139,6 @@
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -1588,34 +1554,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PermissionProfileListParams": {
|
||||
"properties": {
|
||||
"cursor": {
|
||||
"description": "Opaque pagination cursor returned by a previous call.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cwd": {
|
||||
"description": "Optional working directory to resolve project config layers.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"limit": {
|
||||
"description": "Optional page size; defaults to the full result set.",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
@@ -1651,35 +1589,9 @@
|
||||
],
|
||||
"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",
|
||||
"vertical",
|
||||
"workspace-directory",
|
||||
"shared-with-me"
|
||||
],
|
||||
@@ -2988,20 +2900,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsExtraRootsSetParams": {
|
||||
"properties": {
|
||||
"extraRoots": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extraRoots"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsListParams": {
|
||||
"properties": {
|
||||
"cwds": {
|
||||
@@ -3097,7 +2995,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 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.",
|
||||
"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.",
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
@@ -3197,68 +3095,10 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadGoalClearParams": {
|
||||
"properties": {
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadGoalGetParams": {
|
||||
"properties": {
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadGoalSetParams": {
|
||||
"properties": {
|
||||
"objective": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadGoalStatus"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tokenBudget": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadGoalStatus": {
|
||||
"enum": [
|
||||
"active",
|
||||
"paused",
|
||||
"blocked",
|
||||
"usageLimited",
|
||||
"budgetLimited",
|
||||
"complete"
|
||||
],
|
||||
@@ -3465,6 +3305,7 @@
|
||||
"ThreadReadParams": {
|
||||
"properties": {
|
||||
"includeTurns": {
|
||||
"default": false,
|
||||
"description": "When true, include turns and their items from rollout history.",
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -3557,44 +3398,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreadResumeInitialTurnsPageParams": {
|
||||
"properties": {
|
||||
"itemsView": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TurnItemsView"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "How much item detail to include for each returned turn; defaults to summary."
|
||||
},
|
||||
"limit": {
|
||||
"description": "Optional turn page size.",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"sortDirection": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SortDirection"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional turn pagination direction; defaults to descending."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadResumeParams": {
|
||||
"description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\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.",
|
||||
"description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
@@ -3999,12 +3804,6 @@
|
||||
],
|
||||
"description": "Override where approval requests are routed for review on this turn and subsequent turns."
|
||||
},
|
||||
"clientUserMessageId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cwd": {
|
||||
"description": "Override the working directory for this turn and subsequent turns.",
|
||||
"type": [
|
||||
@@ -4091,12 +3890,6 @@
|
||||
},
|
||||
"TurnSteerParams": {
|
||||
"properties": {
|
||||
"clientUserMessageId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"expectedTurnId": {
|
||||
"description": "Required active turn id precondition. The request fails when it does not match the currently active turn.",
|
||||
"type": "string"
|
||||
@@ -4460,78 +4253,6 @@
|
||||
"title": "Thread/name/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/goal/set"
|
||||
],
|
||||
"title": "Thread/goal/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadGoalSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/goal/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/goal/get"
|
||||
],
|
||||
"title": "Thread/goal/getRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadGoalGetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/goal/getRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/goal/clear"
|
||||
],
|
||||
"title": "Thread/goal/clearRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadGoalClearParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/goal/clearRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -4797,30 +4518,6 @@
|
||||
"title": "Skills/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"skills/extraRoots/set"
|
||||
],
|
||||
"title": "Skills/extraRoots/setRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/SkillsExtraRootsSetParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Skills/extraRoots/setRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -4941,30 +4638,6 @@
|
||||
"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": {
|
||||
@@ -5613,30 +5286,6 @@
|
||||
"title": "ExperimentalFeature/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"permissionProfile/list"
|
||||
],
|
||||
"title": "PermissionProfile/listRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PermissionProfileListParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "PermissionProfile/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -277,7 +277,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -285,13 +285,6 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"itemId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -326,4 +319,4 @@
|
||||
],
|
||||
"title": "PermissionsRequestApprovalParams",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"AccountRateLimitsUpdatedNotification": {
|
||||
"description": "Sparse rolling rate-limit update.\n\nClients should merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and does not clear a previously observed value.",
|
||||
"properties": {
|
||||
"rateLimits": {
|
||||
"$ref": "#/definitions/RateLimitSnapshot"
|
||||
@@ -65,26 +64,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier from the selected permissions profile's `extends` setting, when present.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
@@ -436,65 +415,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"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": [
|
||||
"user",
|
||||
"auto_review",
|
||||
"guardian_subagent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AskForApproval": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"granular"
|
||||
],
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AuthMode": {
|
||||
"description": "Authentication mode for OpenAI-backed providers.",
|
||||
"oneOf": [
|
||||
@@ -738,22 +658,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CollaborationMode": {
|
||||
"description": "Collaboration mode for a Codex session.",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"$ref": "#/definitions/ModeKind"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/definitions/Settings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mode",
|
||||
"settings"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandAction": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -1181,7 +1085,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1836,8 +1740,6 @@
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"subagentStart",
|
||||
"subagentStop",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -2003,7 +1905,6 @@
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"cloudManagedConfig",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
@@ -2033,8 +1934,6 @@
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"low",
|
||||
"high",
|
||||
"original"
|
||||
],
|
||||
@@ -2358,14 +2257,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ModeKind": {
|
||||
"description": "Initial collaboration mode to use when the TUI starts.",
|
||||
"enum": [
|
||||
"plan",
|
||||
"default"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ModelRerouteReason": {
|
||||
"enum": [
|
||||
"highRiskCyberActivity"
|
||||
@@ -2427,13 +2318,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkAccess": {
|
||||
"enum": [
|
||||
"restricted",
|
||||
"enabled"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkApprovalProtocol": {
|
||||
"enum": [
|
||||
"http",
|
||||
@@ -2517,14 +2401,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
"friendly",
|
||||
"pragmatic"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PlanDeltaNotification": {
|
||||
"description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.",
|
||||
"properties": {
|
||||
@@ -2678,16 +2554,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limitId": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -2788,26 +2654,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ReasoningSummary": {
|
||||
"description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"auto",
|
||||
"concise",
|
||||
"detailed"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Option to disable reasoning summaries.",
|
||||
"enum": [
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReasoningSummaryPartAddedNotification": {
|
||||
"properties": {
|
||||
"itemId": {
|
||||
@@ -2960,105 +2806,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SandboxPolicy": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"dangerFullAccess"
|
||||
],
|
||||
"title": "DangerFullAccessSandboxPolicyType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "DangerFullAccessSandboxPolicy",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"networkAccess": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
],
|
||||
"title": "ReadOnlySandboxPolicyType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ReadOnlySandboxPolicy",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"networkAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/NetworkAccess"
|
||||
}
|
||||
],
|
||||
"default": "restricted"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"externalSandbox"
|
||||
],
|
||||
"title": "ExternalSandboxSandboxPolicyType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ExternalSandboxSandboxPolicy",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"excludeSlashTmp": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"excludeTmpdirEnvVar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"networkAccess": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicyType",
|
||||
"type": "string"
|
||||
},
|
||||
"writableRoots": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicy",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ServerRequestResolvedNotification": {
|
||||
"properties": {
|
||||
"requestId": {
|
||||
@@ -3114,63 +2861,10 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Settings": {
|
||||
"description": "Settings for a collaboration mode.",
|
||||
"properties": {
|
||||
"developer_instructions": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReasoningEffort"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"model"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SkillsChangedNotification": {
|
||||
"description": "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.",
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetsAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remainingPercent",
|
||||
"resetsAt",
|
||||
"used"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubAgentSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -3402,13 +3096,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"parentThreadId": {
|
||||
"description": "The ID of the parent thread. This will only be set if this thread is a subagent.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"description": "[UNSTABLE] Path to the thread on disk.",
|
||||
"type": [
|
||||
@@ -3570,8 +3257,6 @@
|
||||
"enum": [
|
||||
"active",
|
||||
"paused",
|
||||
"blocked",
|
||||
"usageLimited",
|
||||
"budgetLimited",
|
||||
"complete"
|
||||
],
|
||||
@@ -3605,12 +3290,6 @@
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/UserInput"
|
||||
@@ -3913,12 +3592,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"pluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -4472,102 +4145,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSettings": {
|
||||
"properties": {
|
||||
"activePermissionProfile": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ActivePermissionProfile"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"approvalPolicy": {
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
},
|
||||
"approvalsReviewer": {
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
"collaborationMode": {
|
||||
"$ref": "#/definitions/CollaborationMode"
|
||||
},
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReasoningEffort"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"modelProvider": {
|
||||
"type": "string"
|
||||
},
|
||||
"personality": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Personality"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sandboxPolicy": {
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
},
|
||||
"serviceTier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReasoningSummary"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"approvalPolicy",
|
||||
"approvalsReviewer",
|
||||
"collaborationMode",
|
||||
"cwd",
|
||||
"model",
|
||||
"modelProvider",
|
||||
"sandboxPolicy"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSettingsUpdatedNotification": {
|
||||
"properties": {
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadSettings": {
|
||||
"$ref": "#/definitions/ThreadSettings"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"threadId",
|
||||
"threadSettings"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
@@ -5509,26 +5086,6 @@
|
||||
"title": "Thread/goal/clearedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/settings/updated"
|
||||
],
|
||||
"title": "Thread/settings/updatedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadSettingsUpdatedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/settings/updatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
@@ -631,7 +631,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1590,13 +1590,6 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"environmentId": {
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"itemId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2005,4 +1998,4 @@
|
||||
}
|
||||
],
|
||||
"title": "ServerRequest"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -61,16 +61,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limitId": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -151,34 +141,8 @@
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetsAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remainingPercent",
|
||||
"resetsAt",
|
||||
"used"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Sparse rolling rate-limit update.\n\nClients should merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and does not clear a previously observed value.",
|
||||
"properties": {
|
||||
"rateLimits": {
|
||||
"$ref": "#/definitions/RateLimitSnapshot"
|
||||
|
||||
@@ -5,6 +5,26 @@
|
||||
"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"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandExecTerminalSize": {
|
||||
"description": "PTY size in character cells for `command/exec` PTY sessions.",
|
||||
"properties": {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
]
|
||||
},
|
||||
"includeLayers": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,16 +19,6 @@
|
||||
},
|
||||
"AppConfig": {
|
||||
"properties": {
|
||||
"approvals_reviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_tools_approval_mode": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -198,25 +188,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -309,16 +280,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model_auto_compact_token_limit_scope": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AutoCompactTokenLimitScope"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"model_context_window": {
|
||||
"format": "int64",
|
||||
"type": [
|
||||
@@ -362,6 +323,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"profile": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"profiles": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/ProfileV2"
|
||||
},
|
||||
"default": {},
|
||||
"type": "object"
|
||||
},
|
||||
"review_model": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -508,33 +482,6 @@
|
||||
"title": "SystemConfigLayerSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Enterprise-managed config layer delivered by the cloud config bundle.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier for the delivered layer.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"enterpriseManaged"
|
||||
],
|
||||
"title": "EnterpriseManagedConfigLayerSourceType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"title": "EnterpriseManagedConfigLayerSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
|
||||
"properties": {
|
||||
@@ -666,6 +613,107 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ProfileV2": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"approval_policy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"approvals_reviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "[UNSTABLE] Optional profile-level override for where approval requests are routed for review. If omitted, the enclosing config default is used."
|
||||
},
|
||||
"chatgpt_base_url": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model_provider": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"model_reasoning_effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReasoningEffort"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"model_reasoning_summary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReasoningSummary"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"model_verbosity": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Verbosity"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"service_tier": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ToolsV2"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"web_search": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/WebSearchMode"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ReasoningEffort": {
|
||||
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
|
||||
"enum": [
|
||||
|
||||
@@ -60,25 +60,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ComputerUseRequirements": {
|
||||
"properties": {
|
||||
"allowLockedComputerUse": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigRequirements": {
|
||||
"properties": {
|
||||
"allowAppshots": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"allowManagedHooksOnly": {
|
||||
"type": [
|
||||
"boolean",
|
||||
@@ -94,15 +77,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"allowedPermissions": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"allowedSandboxModes": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/SandboxMode"
|
||||
@@ -121,25 +95,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"allowedWindowsSandboxImplementations": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/WindowsSandboxSetupMode"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"enforceResidency": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -306,18 +261,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"SubagentStart": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"SubagentStop": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"UserPromptSubmit": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/ConfiguredHookMatcherGroup"
|
||||
@@ -345,8 +288,6 @@
|
||||
"PreToolUse",
|
||||
"SessionStart",
|
||||
"Stop",
|
||||
"SubagentStart",
|
||||
"SubagentStop",
|
||||
"UserPromptSubmit"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -469,7 +410,7 @@
|
||||
"NetworkUnixSocketPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -494,13 +435,6 @@
|
||||
"live"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"WindowsSandboxSetupMode": {
|
||||
"enum": [
|
||||
"elevated",
|
||||
"unelevated"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -73,33 +73,6 @@
|
||||
"title": "SystemConfigLayerSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Enterprise-managed config layer delivered by the cloud config bundle.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier for the delivered layer.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"enterpriseManaged"
|
||||
],
|
||||
"title": "EnterpriseManagedConfigLayerSourceType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"title": "EnterpriseManagedConfigLayerSource",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
|
||||
"properties": {
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
"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",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"classification"
|
||||
"classification",
|
||||
"includeLogs"
|
||||
],
|
||||
"title": "FeedbackUploadParams",
|
||||
"type": "object"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"refreshToken": {
|
||||
"default": false,
|
||||
"description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
|
||||
@@ -61,16 +61,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"individualLimit": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SpendControlLimitSnapshot"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limitId": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -151,31 +141,6 @@
|
||||
"usedPercent"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SpendControlLimitSnapshot": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "string"
|
||||
},
|
||||
"remainingPercent": {
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
"resetsAt": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"limit",
|
||||
"remainingPercent",
|
||||
"resetsAt",
|
||||
"used"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"subagentStart",
|
||||
"subagentStop",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -166,7 +164,6 @@
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"cloudManagedConfig",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"subagentStart",
|
||||
"subagentStop",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -166,7 +164,6 @@
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"cloudManagedConfig",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
"postCompact",
|
||||
"sessionStart",
|
||||
"userPromptSubmit",
|
||||
"subagentStart",
|
||||
"subagentStop",
|
||||
"stop"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -130,7 +128,6 @@
|
||||
"sessionFlags",
|
||||
"plugin",
|
||||
"cloudRequirements",
|
||||
"cloudManagedConfig",
|
||||
"legacyManagedConfigFile",
|
||||
"legacyManagedConfigMdm",
|
||||
"unknown"
|
||||
|
||||
@@ -287,8 +287,6 @@
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"low",
|
||||
"high",
|
||||
"original"
|
||||
],
|
||||
@@ -500,12 +498,6 @@
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/UserInput"
|
||||
@@ -808,12 +800,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"pluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"deny"
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -287,8 +287,6 @@
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"low",
|
||||
"high",
|
||||
"original"
|
||||
],
|
||||
@@ -500,12 +498,6 @@
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/UserInput"
|
||||
@@ -808,12 +800,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"pluginId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -36,12 +36,6 @@
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"threadId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "ListMcpServerStatusParams",
|
||||
|
||||
@@ -10,47 +10,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerInfo": {
|
||||
"description": "Presentation metadata advertised by an initialized MCP server.",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"icons": {
|
||||
"items": true,
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"websiteUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"version"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerStatus": {
|
||||
"properties": {
|
||||
"authStatus": {
|
||||
@@ -71,16 +30,6 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"serverInfo": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpServerInfo"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Tool"
|
||||
|
||||
@@ -43,14 +43,6 @@
|
||||
"defaultReasoningEffort": {
|
||||
"$ref": "#/definitions/ReasoningEffort"
|
||||
},
|
||||
"defaultServiceTier": {
|
||||
"default": null,
|
||||
"description": "Catalog default service tier id for this model, when one is configured.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cursor": {
|
||||
"description": "Opaque pagination cursor returned by a previous call.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"cwd": {
|
||||
"description": "Optional working directory to resolve project config layers.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"limit": {
|
||||
"description": "Optional page size; defaults to the full result set.",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "PermissionProfileListParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PermissionProfileSummary": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional user-facing description for display in clients.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "Available permission profile identifier.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionProfileSummary"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"nextCursor": {
|
||||
"description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"title": "PermissionProfileListResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
@@ -1,525 +0,0 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user