From 168e35b6f237678ced5eaf8766e1c83c83259b29 Mon Sep 17 00:00:00 2001 From: EFRAZER-oai Date: Tue, 3 Mar 2026 09:25:50 -0800 Subject: [PATCH 01/17] Add Windows direct install script (#12741) ## Summary - add a direct install script for Windows at `scripts/install/install.ps1` - extend release staging so `install.ps1` is published alongside `install.sh` - install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper binaries) from the existing platform npm package ## Dependencies - Depends on https://github.com/openai/codex/pull/12740 ## Testing - Smoke-tested with powershell --- .github/workflows/rust-release.yml | 3 +- scripts/install/install.ps1 | 196 +++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 scripts/install/install.ps1 diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 2d137de6ff..5d6d3f7ca0 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -494,9 +494,10 @@ jobs: --package codex-responses-api-proxy \ --package codex-sdk - - name: Stage macOS and Linux installer script + - name: Stage installer scripts run: | cp scripts/install/install.sh dist/install.sh + cp scripts/install/install.ps1 dist/install.ps1 - name: Create GitHub Release uses: softprops/action-gh-release@v2 diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 new file mode 100644 index 0000000000..40328db700 --- /dev/null +++ b/scripts/install/install.ps1 @@ -0,0 +1,196 @@ +param( + [Parameter(Position=0)] + [string]$Version = "latest" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +function Write-Step { + param( + [string]$Message + ) + + Write-Host "==> $Message" +} + +function Normalize-Version { + param( + [string]$RawVersion + ) + + if ([string]::IsNullOrWhiteSpace($RawVersion) -or $RawVersion -eq "latest") { + return "latest" + } + + if ($RawVersion.StartsWith("rust-v")) { + return $RawVersion.Substring(6) + } + + if ($RawVersion.StartsWith("v")) { + return $RawVersion.Substring(1) + } + + return $RawVersion +} + +function Get-ReleaseUrl { + param( + [string]$AssetName, + [string]$ResolvedVersion + ) + + return "https://github.com/openai/codex/releases/download/rust-v$ResolvedVersion/$AssetName" +} + +function Path-Contains { + param( + [string]$PathValue, + [string]$Entry + ) + + if ([string]::IsNullOrWhiteSpace($PathValue)) { + return $false + } + + $needle = $Entry.TrimEnd("\") + foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) { + if ($segment.TrimEnd("\") -ieq $needle) { + return $true + } + } + + return $false +} + +function Resolve-Version { + $normalizedVersion = Normalize-Version -RawVersion $Version + if ($normalizedVersion -ne "latest") { + return $normalizedVersion + } + + $release = Invoke-RestMethod -Uri "https://api.github.com/repos/openai/codex/releases/latest" + if (-not $release.tag_name) { + Write-Error "Failed to resolve the latest Codex release version." + exit 1 + } + + return (Normalize-Version -RawVersion $release.tag_name) +} + +if ($env:OS -ne "Windows_NT") { + Write-Error "install.ps1 supports Windows only. Use install.sh on macOS or Linux." + exit 1 +} + +if (-not [Environment]::Is64BitOperatingSystem) { + Write-Error "Codex requires a 64-bit version of Windows." + exit 1 +} + +$architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture +$target = $null +$platformLabel = $null +$npmTag = $null +switch ($architecture) { + "Arm64" { + $target = "aarch64-pc-windows-msvc" + $platformLabel = "Windows (ARM64)" + $npmTag = "win32-arm64" + } + "X64" { + $target = "x86_64-pc-windows-msvc" + $platformLabel = "Windows (x64)" + $npmTag = "win32-x64" + } + default { + Write-Error "Unsupported architecture: $architecture" + exit 1 + } +} + +if ([string]::IsNullOrWhiteSpace($env:CODEX_INSTALL_DIR)) { + $installDir = Join-Path $env:LOCALAPPDATA "Programs\OpenAI\Codex\bin" +} else { + $installDir = $env:CODEX_INSTALL_DIR +} + +$codexPath = Join-Path $installDir "codex.exe" +$installMode = if (Test-Path $codexPath) { "Updating" } else { "Installing" } + +Write-Step "$installMode Codex CLI" +Write-Step "Detected platform: $platformLabel" + +New-Item -ItemType Directory -Force -Path $installDir | Out-Null + +$resolvedVersion = Resolve-Version +Write-Step "Resolved version: $resolvedVersion" +$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz" + +$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Force -Path $tempDir | Out-Null + +try { + $archivePath = Join-Path $tempDir $packageAsset + $extractDir = Join-Path $tempDir "extract" + $url = Get-ReleaseUrl -AssetName $packageAsset -ResolvedVersion $resolvedVersion + + Write-Step "Downloading Codex CLI" + Invoke-WebRequest -Uri $url -OutFile $archivePath + + New-Item -ItemType Directory -Force -Path $extractDir | Out-Null + tar -xzf $archivePath -C $extractDir + + $vendorRoot = Join-Path $extractDir "package/vendor/$target" + Write-Step "Installing to $installDir" + $copyMap = @{ + "codex/codex.exe" = "codex.exe" + "codex/codex-command-runner.exe" = "codex-command-runner.exe" + "codex/codex-windows-sandbox-setup.exe" = "codex-windows-sandbox-setup.exe" + "path/rg.exe" = "rg.exe" + } + + foreach ($relativeSource in $copyMap.Keys) { + $sourcePath = Join-Path $vendorRoot $relativeSource + $destinationPath = Join-Path $installDir $copyMap[$relativeSource] + Move-Item -Force $sourcePath $destinationPath + } +} finally { + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue +} + +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") +$pathNeedsNewShell = $false +if (-not (Path-Contains -PathValue $userPath -Entry $installDir)) { + if ([string]::IsNullOrWhiteSpace($userPath)) { + $newUserPath = $installDir + } else { + $newUserPath = "$installDir;$userPath" + } + + [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") + if (-not (Path-Contains -PathValue $env:Path -Entry $installDir)) { + if ([string]::IsNullOrWhiteSpace($env:Path)) { + $env:Path = $installDir + } else { + $env:Path = "$installDir;$env:Path" + } + } + Write-Step "PATH updated for future PowerShell sessions." + $pathNeedsNewShell = $true +} elseif (Path-Contains -PathValue $env:Path -Entry $installDir) { + Write-Step "$installDir is already on PATH." +} else { + Write-Step "PATH is already configured for future PowerShell sessions." + $pathNeedsNewShell = $true +} + +if ($pathNeedsNewShell) { + Write-Step ('Run now: $env:Path = "{0};$env:Path"; codex' -f $installDir) + Write-Step "Or open a new PowerShell window and run: codex" +} else { + Write-Step "Run: codex" +} + +Write-Host "Codex CLI $resolvedVersion installed successfully." From 57a13ada7557f1dc026a2c6914941c952abdf04d Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 17:29:40 +0000 Subject: [PATCH 02/17] feat: massive improvement --- MODULE.bazel.lock | 2 + codex-rs/Cargo.lock | 17 + codex-rs/Cargo.toml | 1 + codex-rs/artifact-presentation/Cargo.toml | 1 + .../src/presentation_artifact/api.rs | 3 - .../src/presentation_artifact/manager.rs | 8 +- .../src/presentation_artifact/model.rs | 66 +- .../src/presentation_artifact/pptx.rs | 1038 ++++++-- .../src/presentation_artifact/render.rs | 377 ++- codex-rs/artifact-presentation/src/tests.rs | 496 +++- codex-rs/artifact-spreadsheet/src/manager.rs | 3 +- codex-rs/artifact-spreadsheet/src/model.rs | 25 +- codex-rs/artifact-spreadsheet/src/style.rs | 115 +- codex-rs/artifact-spreadsheet/src/tests.rs | 1094 +++++++++ codex-rs/artifact-spreadsheet/src/xlsx.rs | 2079 ++++++++++++++++- 15 files changed, 5045 insertions(+), 280 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index e5931d4187..4f8801a3c3 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -849,6 +849,7 @@ "foldhash_0.1.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"hashbrown\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"uuid\",\"req\":\"^1.8\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "foldhash_0.2.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rapidhash\",\"req\":\"^3.1.0\"},{\"kind\":\"dev\",\"name\":\"uuid\",\"req\":\"^1.8\"}],\"features\":{\"default\":[\"std\"],\"nightly\":[],\"std\":[]}}", "font8x8_0.3.1": "{\"dependencies\":[],\"features\":{\"default\":[\"unicode\",\"std\"],\"std\":[],\"unicode\":[]}}", + "fontdue_0.9.3": "{\"dependencies\":[{\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.10\"},{\"default_features\":false,\"features\":[\"opentype-layout\"],\"name\":\"ttf-parser\",\"req\":\"^0.21\"}],\"features\":{\"default\":[\"simd\",\"hashbrown\"],\"parallel\":[\"rayon\",\"hashbrown\",\"hashbrown/rayon\"],\"simd\":[],\"std\":[]}}", "foreign-types-shared_0.1.1": "{\"dependencies\":[],\"features\":{}}", "foreign-types_0.3.2": "{\"dependencies\":[{\"name\":\"foreign-types-shared\",\"req\":\"^0.1\"}],\"features\":{}}", "form_urlencoded_1.2.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"percent-encoding\",\"req\":\"^2.3.0\"}],\"features\":{\"alloc\":[\"percent-encoding/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"percent-encoding/std\"]}}", @@ -1420,6 +1421,7 @@ "try-lock_0.2.5": "{\"dependencies\":[],\"features\":{}}", "ts-rs-macros_11.1.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.28\"},{\"name\":\"termcolor\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"no-serde-warnings\":[],\"serde-compat\":[\"termcolor\"]}}", "ts-rs_11.1.0": "{\"dependencies\":[{\"features\":[\"serde\"],\"name\":\"bigdecimal\",\"optional\":true,\"req\":\">=0.0.13, <0.5\"},{\"name\":\"bson\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"dprint-plugin-typescript\",\"optional\":true,\"req\":\"=0.95\"},{\"name\":\"heapless\",\"optional\":true,\"req\":\">=0.7, <0.9\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"ordered-float\",\"optional\":true,\"req\":\">=3, <6\"},{\"name\":\"semver\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"name\":\"smol_str\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"sync\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.40\"},{\"name\":\"ts-rs-macros\",\"req\":\"=11.1.0\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"bigdecimal-impl\":[\"bigdecimal\"],\"bson-uuid-impl\":[\"bson\"],\"bytes-impl\":[\"bytes\"],\"chrono-impl\":[\"chrono\"],\"default\":[\"serde-compat\"],\"format\":[\"dprint-plugin-typescript\"],\"heapless-impl\":[\"heapless\"],\"import-esm\":[],\"indexmap-impl\":[\"indexmap\"],\"no-serde-warnings\":[\"ts-rs-macros/no-serde-warnings\"],\"ordered-float-impl\":[\"ordered-float\"],\"semver-impl\":[\"semver\"],\"serde-compat\":[\"ts-rs-macros/serde-compat\"],\"serde-json-impl\":[\"serde_json\"],\"smol_str-impl\":[\"smol_str\"],\"tokio-impl\":[\"tokio\"],\"url-impl\":[\"url\"],\"uuid-impl\":[\"uuid\"]}}", + "ttf-parser_0.21.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22.1\"},{\"kind\":\"dev\",\"name\":\"pico-args\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"tiny-skia-path\",\"req\":\"^0.11.4\"},{\"kind\":\"dev\",\"name\":\"xmlwriter\",\"req\":\"^0.1\"}],\"features\":{\"apple-layout\":[],\"default\":[\"std\",\"opentype-layout\",\"apple-layout\",\"variable-fonts\",\"glyph-names\"],\"glyph-names\":[],\"gvar-alloc\":[\"std\"],\"opentype-layout\":[],\"std\":[],\"variable-fonts\":[]}}", "two-face_0.5.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cargo-lock\",\"req\":\"^10.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.44.3\"},{\"default_features\":false,\"features\":[\"read\"],\"kind\":\"dev\",\"name\":\"object\",\"req\":\"^0.36.7\"},{\"name\":\"serde\",\"req\":\"^1.0.228\"},{\"name\":\"serde_derive\",\"req\":\"^1.0.228\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.26.3\"},{\"default_features\":false,\"features\":[\"dump-load\",\"parsing\"],\"name\":\"syntect\",\"req\":\"^5.3.0\"},{\"default_features\":false,\"features\":[\"html\"],\"kind\":\"dev\",\"name\":\"syntect\",\"req\":\"^5.3.0\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.8.23\"},{\"default_features\":false,\"features\":[\"std\",\"xxhash64\"],\"kind\":\"dev\",\"name\":\"twox-hash\",\"req\":\"^2.1.2\"}],\"features\":{\"default\":[\"syntect-onig\"],\"syntect-default-fancy\":[\"syntect-fancy\",\"syntect/default-fancy\"],\"syntect-default-onig\":[\"syntect-onig\",\"syntect/default-onig\"],\"syntect-fancy\":[\"syntect/regex-fancy\"],\"syntect-onig\":[\"syntect/regex-onig\"]}}", "type-map_0.5.1": "{\"dependencies\":[{\"name\":\"rustc-hash\",\"req\":\"^2\"}],\"features\":{}}", "typenum_1.19.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"scale-info\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"const-generics\":[],\"force_unix_path_separator\":[],\"i128\":[],\"no_std\":[],\"scale_info\":[\"scale-info/derive\"],\"strict\":[]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 778735bd24..b98dc9da09 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1552,6 +1552,7 @@ version = "0.0.0" dependencies = [ "base64 0.22.1", "font8x8", + "fontdue", "image", "ppt-rs", "pretty_assertions", @@ -4102,6 +4103,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" +[[package]] +name = "fontdue" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -10568,6 +10579,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "tungstenite" version = "0.27.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 1f4c1b9648..5ba5bc6474 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -219,6 +219,7 @@ owo-colors = "4.3.0" path-absolutize = "3.1.1" pathdiff = "0.2" font8x8 = "0.3.1" +fontdue = "0.9.3" tiny-skia = "0.11.4" portable-pty = "0.9.0" ppt-rs = "0.2.6" diff --git a/codex-rs/artifact-presentation/Cargo.toml b/codex-rs/artifact-presentation/Cargo.toml index b30809f9e9..786b8fb256 100644 --- a/codex-rs/artifact-presentation/Cargo.toml +++ b/codex-rs/artifact-presentation/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] base64 = { workspace = true } font8x8 = { workspace = true } +fontdue = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } ppt-rs = { workspace = true } reqwest = { workspace = true, features = ["blocking"] } diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/api.rs b/codex-rs/artifact-presentation/src/presentation_artifact/api.rs index 65fd637687..1b9127e998 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/api.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/api.rs @@ -4,9 +4,6 @@ use image::GenericImageView; use image::ImageFormat; use image::codecs::jpeg::JpegEncoder; use image::imageops::FilterType; -use ppt_rs::Chart; -use ppt_rs::ChartSeries; -use ppt_rs::ChartType; use ppt_rs::Hyperlink as PptHyperlink; use ppt_rs::HyperlinkAction as PptHyperlinkAction; use ppt_rs::Image; diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs index 86ba674f4a..aabd63ebb7 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs @@ -307,7 +307,7 @@ impl PresentationArtifactManager { document, slide_index, RenderOptions { - scale: 1.0, + scale, include_background: true, }, )?; @@ -315,7 +315,7 @@ impl PresentationArtifactManager { &png_bytes, &output_path, preview_format, - scale, + 1.0, quality, &request.action, )?; @@ -332,7 +332,7 @@ impl PresentationArtifactManager { document, slide_index, RenderOptions { - scale: 1.0, + scale, include_background: true, }, )?; @@ -342,7 +342,7 @@ impl PresentationArtifactManager { &png_bytes, &target, preview_format, - scale, + 1.0, quality, &request.action, )?; diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs index b5cd5d00a3..e81e158122 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs @@ -85,14 +85,14 @@ struct NamedTextStyle { built_in: bool, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct HyperlinkState { target: HyperlinkTarget, tooltip: Option, highlight_click: bool, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] enum HyperlinkTarget { Url(String), Slide(u32), @@ -926,16 +926,16 @@ impl PresentationDocument { }) } - fn to_ppt_rs(&self) -> Presentation { + fn to_ppt_rs(&self) -> Result { let mut presentation = self .name .as_deref() .map(Presentation::with_title) .unwrap_or_default(); for slide in &self.slides { - presentation = presentation.add_slide(slide.to_ppt_rs(self.slide_size)); + presentation = presentation.add_slide(slide.to_ppt_rs(self.slide_size, self)?); } - presentation + Ok(presentation) } } @@ -1223,7 +1223,11 @@ fn xml_attribute(tag: &str, attribute: &str) -> Option { } impl PresentationSlide { - fn to_ppt_rs(&self, slide_size: Rect) -> SlideContent { + fn to_ppt_rs( + &self, + slide_size: Rect, + document: &PresentationDocument, + ) -> Result { let mut content = SlideContent::new("").layout(SlideLayout::Blank); if self.notes.visible && !self.notes.text.is_empty() { content = content.notes(&self.notes.text); @@ -1391,24 +1395,22 @@ impl PresentationSlide { content = content.table(builder.build()); } PresentationElement::Chart(chart) => { - let mut ppt_chart = Chart::new( - chart.title.as_deref().unwrap_or("Chart"), - chart.chart_type.to_ppt_rs(), - chart.categories, - points_to_emu(chart.frame.left), - points_to_emu(chart.frame.top), + let chart_bytes = render_chart_png_bytes(document, &chart)?; + let ppt_chart = Image::from_bytes( + chart_bytes, points_to_emu(chart.frame.width), points_to_emu(chart.frame.height), + "png", + ) + .position( + points_to_emu(chart.frame.left), + points_to_emu(chart.frame.top), ); - for series in chart.series { - ppt_chart = - ppt_chart.add_series(ChartSeries::new(&series.name, series.values)); - } - content = content.add_chart(ppt_chart); + content = content.add_image(ppt_chart); } } } - content + Ok(content) } } @@ -1724,34 +1726,6 @@ enum ChartTypeSpec { Combo, } -impl ChartTypeSpec { - fn to_ppt_rs(self) -> ChartType { - match self { - Self::Bar => ChartType::Bar, - Self::BarHorizontal => ChartType::BarHorizontal, - Self::BarStacked => ChartType::BarStacked, - Self::BarStacked100 => ChartType::BarStacked100, - Self::Line => ChartType::Line, - Self::LineMarkers => ChartType::LineMarkers, - Self::LineStacked => ChartType::LineStacked, - Self::Pie => ChartType::Pie, - Self::Doughnut => ChartType::Doughnut, - Self::Area => ChartType::Area, - Self::AreaStacked => ChartType::AreaStacked, - Self::AreaStacked100 => ChartType::AreaStacked100, - Self::Scatter => ChartType::Scatter, - Self::ScatterLines => ChartType::ScatterLines, - Self::ScatterSmooth => ChartType::ScatterSmooth, - Self::Bubble => ChartType::Bubble, - Self::Radar => ChartType::Radar, - Self::RadarFilled => ChartType::RadarFilled, - Self::StockHlc => ChartType::StockHLC, - Self::StockOhlc => ChartType::StockOHLC, - Self::Combo => ChartType::Combo, - } - } -} - impl ConnectorKind { fn to_ppt_rs(self) -> ConnectorType { match self { diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index e4ff48f520..b9854745c7 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -19,6 +19,7 @@ fn import_codex_metadata_document(path: &Path) -> Result Result, String> { let bytes = document .to_ppt_rs() + .map_err(|error| format!("{action}: {error}"))? .build() .map_err(|error| format!("{action}: {error}"))?; patch_pptx_package(bytes, document).map_err(|error| format!("{action}: {error}")) @@ -32,6 +33,18 @@ struct SlideImageAsset { extension: String, } +struct PictureXmlSpec<'a> { + description: Option<&'a str>, + lock_aspect_ratio: bool, + rotation_degrees: Option, + flip_horizontal: bool, + flip_vertical: bool, + shape_id: usize, + relationship_id: &'a str, + frame: Rect, + crop: Option, +} + fn normalized_image_extension(format: &str) -> String { match format.to_ascii_lowercase().as_str() { "jpeg" => "jpg".to_string(), @@ -46,13 +59,18 @@ fn image_relationship_xml(relationship_id: &str, target: &str) -> String { ) } -fn image_picture_xml( - image: &ImageElement, - shape_id: usize, - relationship_id: &str, - frame: Rect, - crop: Option, -) -> String { +fn picture_xml(spec: PictureXmlSpec<'_>) -> String { + let PictureXmlSpec { + description, + lock_aspect_ratio, + rotation_degrees, + flip_horizontal, + flip_vertical, + shape_id, + relationship_id, + frame, + crop, + } = spec; let blip_fill = if let Some((crop_left, crop_top, crop_right, crop_bottom)) = crop { format!( r#" @@ -77,22 +95,19 @@ fn image_picture_xml( "# ) }; - let descr = image - .alt_text - .as_deref() + let descr = description .map(|alt| format!(r#" descr="{}""#, ppt_rs::escape_xml(alt))) .unwrap_or_default(); - let no_change_aspect = if image.lock_aspect_ratio { 1 } else { 0 }; - let rotation = image - .rotation_degrees + let no_change_aspect = if lock_aspect_ratio { 1 } else { 0 }; + let rotation = rotation_degrees .map(|rotation| format!(r#" rot="{}""#, i64::from(rotation) * 60_000)) .unwrap_or_default(); - let flip_horizontal = if image.flip_horizontal { + let flip_horizontal = if flip_horizontal { r#" flipH="1""# } else { "" }; - let flip_vertical = if image.flip_vertical { + let flip_vertical = if flip_vertical { r#" flipV="1""# } else { "" @@ -124,10 +139,31 @@ fn image_picture_xml( ) } +fn image_picture_xml( + image: &ImageElement, + shape_id: usize, + relationship_id: &str, + frame: Rect, + crop: Option, +) -> String { + picture_xml(PictureXmlSpec { + description: image.alt_text.as_deref(), + lock_aspect_ratio: image.lock_aspect_ratio, + rotation_degrees: image.rotation_degrees, + flip_horizontal: image.flip_horizontal, + flip_vertical: image.flip_vertical, + shape_id, + relationship_id, + frame, + crop, + }) +} + fn slide_image_assets( + document: &PresentationDocument, slide: &PresentationSlide, next_media_index: &mut usize, -) -> Vec { +) -> Result, String> { let mut ordered = slide.elements.iter().collect::>(); ordered.sort_by_key(|element| element.z_order()); let shape_count = ordered @@ -145,51 +181,84 @@ fn slide_image_assets( let mut image_index = 0_usize; let mut assets = Vec::new(); for element in ordered { - let PresentationElement::Image(image) = element else { - continue; - }; - let Some(payload) = &image.payload else { - continue; - }; - let (left, top, width, height, fitted_crop) = if image.fit_mode != ImageFitMode::Stretch { - fit_image(image) - } else { - ( - image.frame.left, - image.frame.top, - image.frame.width, - image.frame.height, - None, - ) - }; - image_index += 1; - let relationship_id = format!("rIdImage{image_index}"); - let extension = normalized_image_extension(&payload.format); - let media_name = format!("image{next_media_index}.{extension}"); - *next_media_index += 1; - assets.push(SlideImageAsset { - xml: image_picture_xml( - image, - 20 + shape_count + image_index - 1, - &relationship_id, - Rect { - left, - top, - width, - height, - }, - image.crop.or(fitted_crop), - ), - relationship_xml: image_relationship_xml( - &relationship_id, - &format!("../media/{media_name}"), - ), - media_path: format!("ppt/media/{media_name}"), - media_bytes: payload.bytes.clone(), - extension, - }); + match element { + PresentationElement::Image(image) => { + let Some(payload) = &image.payload else { + continue; + }; + let (left, top, width, height, fitted_crop) = if image.fit_mode != ImageFitMode::Stretch { + fit_image(image) + } else { + ( + image.frame.left, + image.frame.top, + image.frame.width, + image.frame.height, + None, + ) + }; + image_index += 1; + let relationship_id = format!("rIdImage{image_index}"); + let extension = normalized_image_extension(&payload.format); + let media_name = format!("image{next_media_index}.{extension}"); + *next_media_index += 1; + assets.push(SlideImageAsset { + xml: image_picture_xml( + image, + 20 + shape_count + image_index - 1, + &relationship_id, + Rect { + left, + top, + width, + height, + }, + image.crop.or(fitted_crop), + ), + relationship_xml: image_relationship_xml( + &relationship_id, + &format!("../media/{media_name}"), + ), + media_path: format!("ppt/media/{media_name}"), + media_bytes: payload.bytes.clone(), + extension, + }); + } + PresentationElement::Chart(chart) => { + image_index += 1; + let relationship_id = format!("rIdImage{image_index}"); + let extension = "png".to_string(); + let media_name = format!("image{next_media_index}.{extension}"); + *next_media_index += 1; + assets.push(SlideImageAsset { + xml: picture_xml(PictureXmlSpec { + description: chart.title.as_deref(), + lock_aspect_ratio: true, + rotation_degrees: None, + flip_horizontal: false, + flip_vertical: false, + shape_id: 20 + shape_count + image_index - 1, + relationship_id: &relationship_id, + frame: chart.frame, + crop: None, + }), + relationship_xml: image_relationship_xml( + &relationship_id, + &format!("../media/{media_name}"), + ), + media_path: format!("ppt/media/{media_name}"), + media_bytes: render_chart_png_bytes(document, chart) + .map_err(|error| error.to_string())?, + extension, + }); + } + PresentationElement::Text(_) + | PresentationElement::Shape(_) + | PresentationElement::Connector(_) + | PresentationElement::Table(_) => {} + } } - assets + Ok(assets) } fn patch_pptx_package( @@ -201,17 +270,22 @@ fn patch_pptx_package( let mut writer = ZipWriter::new(Cursor::new(Vec::new())); let mut next_media_index = 1_usize; let mut pending_slide_relationships = HashMap::new(); + let mut pending_notes_relationships = HashMap::new(); let mut pending_slide_images = HashMap::new(); let mut pending_media = Vec::new(); let mut image_extensions = BTreeSet::new(); for (slide_index, slide) in document.slides.iter().enumerate() { let slide_number = slide_index + 1; - let images = slide_image_assets(slide, &mut next_media_index); + let images = slide_image_assets(document, slide, &mut next_media_index)?; let mut relationships = slide_hyperlink_relationships(slide); relationships.extend(images.iter().map(|image| image.relationship_xml.clone())); if !relationships.is_empty() { pending_slide_relationships.insert(slide_number, relationships); } + let notes_relationships = notes_hyperlink_relationships(slide); + if !notes_relationships.is_empty() { + pending_notes_relationships.insert(slide_number, notes_relationships); + } if !images.is_empty() { image_extensions.extend(images.iter().map(|image| image.extension.clone())); pending_media.extend( @@ -253,6 +327,12 @@ fn patch_pptx_package( .map_err(|error| error.to_string())?; continue; } + if name == "ppt/theme/theme1.xml" { + writer + .write_all(update_theme_xml(bytes, &document.theme)?.as_bytes()) + .map_err(|error| error.to_string())?; + continue; + } if let Some(slide_number) = parse_slide_xml_path(&name) { writer .write_all( @@ -277,6 +357,23 @@ fn patch_pptx_package( .map_err(|error| error.to_string())?; continue; } + if let Some(slide_number) = parse_notes_slide_xml_path(&name) { + writer + .write_all( + update_notes_slide_xml(bytes, &document.slides[slide_number - 1], document)? + .as_bytes(), + ) + .map_err(|error| error.to_string())?; + continue; + } + if let Some(slide_number) = parse_notes_slide_relationships_path(&name) + && let Some(relationships) = pending_notes_relationships.remove(&slide_number) + { + writer + .write_all(update_slide_relationships_xml(bytes, &relationships)?.as_bytes()) + .map_err(|error| error.to_string())?; + continue; + } writer .write_all(&bytes) .map_err(|error| error.to_string())?; @@ -294,6 +391,18 @@ fn patch_pptx_package( .map_err(|error| error.to_string())?; } + for (slide_number, relationships) in pending_notes_relationships { + writer + .start_file( + format!("ppt/notesSlides/_rels/notesSlide{slide_number}.xml.rels"), + SimpleFileOptions::default(), + ) + .map_err(|error| error.to_string())?; + writer + .write_all(slide_relationships_xml(&relationships).as_bytes()) + .map_err(|error| error.to_string())?; + } + for (path, bytes) in pending_media { writer .start_file(path, SimpleFileOptions::default()) @@ -354,29 +463,180 @@ fn replace_self_closing_xml_tag(xml: &str, tag: &str, replacement: &str) -> Resu Ok(format!("{}{replacement}{}", &xml[..start], &xml[end..])) } +fn update_theme_xml(existing_bytes: Vec, theme: &ThemeState) -> Result { + let mut existing = String::from_utf8(existing_bytes).map_err(|error| error.to_string())?; + + if let Some(major_font) = theme_font_value(theme, &["major_font"]) { + existing = replace_theme_font(existing, "majorFont", &major_font)?; + } + if let Some(minor_font) = theme_font_value(theme, &["minor_font"]) { + existing = replace_theme_font(existing, "minorFont", &minor_font)?; + } + + for (tag, keys) in [ + ("dk1", &["dk1", "tx1", "text1", "dark1"][..]), + ("lt1", &["lt1", "bg1", "background1", "light1"][..]), + ("dk2", &["dk2", "tx2", "text2", "dark2"][..]), + ("lt2", &["lt2", "bg2", "background2", "light2"][..]), + ("accent1", &["accent1"][..]), + ("accent2", &["accent2"][..]), + ("accent3", &["accent3"][..]), + ("accent4", &["accent4"][..]), + ("accent5", &["accent5"][..]), + ("accent6", &["accent6"][..]), + ("hlink", &["hlink"][..]), + ("folHlink", &["folhlink"][..]), + ] { + if let Some(color) = theme_color_value(theme, keys) { + existing = replace_theme_color(existing, tag, &color)?; + } + } + + Ok(existing) +} + +fn theme_font_value(theme: &ThemeState, keys: &[&str]) -> Option { + keys.iter().find_map(|key| match *key { + "major_font" => theme.major_font.clone(), + "minor_font" => theme.minor_font.clone(), + _ => None, + }) +} + +fn theme_color_value(theme: &ThemeState, keys: &[&str]) -> Option { + keys.iter().find_map(|key| { + theme.color_scheme.get(*key).map(|value| { + value + .trim_start_matches('#') + .trim() + .to_ascii_uppercase() + }) + }) +} + +fn replace_theme_font(xml: String, tag: &str, font: &str) -> Result { + let start_tag = format!(""); + let end_tag = format!(""); + let start = xml + .find(&start_tag) + .ok_or_else(|| format!("theme xml is missing ``"))?; + let end = xml[start..] + .find(&end_tag) + .map(|offset| start + offset + end_tag.len()) + .ok_or_else(|| format!("theme xml is missing ``"))?; + let replacement = format!( + r#" + + + + "#, + ppt_rs::escape_xml(font) + ); + Ok(format!("{}{replacement}{}", &xml[..start], &xml[end..])) +} + +fn replace_theme_color(xml: String, tag: &str, color: &str) -> Result { + let start_tag = format!(""); + let start = xml + .find(&start_tag) + .ok_or_else(|| format!("theme xml is missing ``"))?; + let end = xml[start..] + .find(&end_tag) + .map(|offset| start + offset + end_tag.len()) + .ok_or_else(|| format!("theme xml is missing ``"))?; + let replacement = format!(r#""#); + Ok(format!("{}{replacement}{}", &xml[..start], &xml[end..])) +} + fn slide_hyperlink_relationships(slide: &PresentationSlide) -> Vec { let mut ordered = slide.elements.iter().collect::>(); ordered.sort_by_key(|element| element.z_order()); - let mut hyperlink_index = 1_u32; + let mut relationship_ids = HashSet::new(); let mut relationships = Vec::new(); for element in ordered { - let Some(hyperlink) = (match element { - PresentationElement::Text(text) => text.hyperlink.as_ref(), - PresentationElement::Shape(shape) => shape.hyperlink.as_ref(), + match element { + PresentationElement::Text(text) => { + if let Some(hyperlink) = &text.hyperlink { + let relationship_id = format!("rIdHyperlink_{}", text.element_id); + if relationship_ids.insert(relationship_id.clone()) { + relationships.push(hyperlink.relationship_xml(&relationship_id)); + } + } + collect_rich_text_hyperlinks( + &mut relationships, + &mut relationship_ids, + &text.element_id, + &text.rich_text, + ); + } + PresentationElement::Shape(shape) => { + if let Some(hyperlink) = &shape.hyperlink { + let relationship_id = format!("rIdHyperlink_{}", shape.element_id); + if relationship_ids.insert(relationship_id.clone()) { + relationships.push(hyperlink.relationship_xml(&relationship_id)); + } + } + if let Some(rich_text) = &shape.rich_text { + collect_rich_text_hyperlinks( + &mut relationships, + &mut relationship_ids, + &shape.element_id, + rich_text, + ); + } + } + PresentationElement::Table(table) => { + for (row_index, row) in table.rows.iter().enumerate() { + for (column_index, cell) in row.iter().enumerate() { + collect_rich_text_hyperlinks( + &mut relationships, + &mut relationship_ids, + &table_cell_export_id(&table.element_id, row_index, column_index), + &cell.rich_text, + ); + } + } + } PresentationElement::Connector(_) | PresentationElement::Image(_) - | PresentationElement::Table(_) - | PresentationElement::Chart(_) => None, - }) else { - continue; - }; - let relationship_id = format!("rIdHyperlink{hyperlink_index}"); - hyperlink_index += 1; - relationships.push(hyperlink.relationship_xml(&relationship_id)); + | PresentationElement::Chart(_) => {} + } } relationships } +fn collect_rich_text_hyperlinks( + relationships: &mut Vec, + relationship_ids: &mut HashSet, + element_id: &str, + rich_text: &RichTextState, +) { + for range in &rich_text.ranges { + let Some(hyperlink) = &range.hyperlink else { + continue; + }; + let relationship_id = rich_text_relationship_id(element_id, &range.range_id); + if relationship_ids.insert(relationship_id.clone()) { + relationships.push(hyperlink.relationship_xml(&relationship_id)); + } + } +} + +fn rich_text_relationship_id(element_id: &str, range_id: &str) -> String { + let sanitize = |value: &str| { + value + .chars() + .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) + .collect::() + }; + format!("rIdRange_{}_{}", sanitize(element_id), sanitize(range_id)) +} + +fn table_cell_export_id(table_id: &str, row_index: usize, column_index: usize) -> String { + format!("{table_id}_r{row_index}_c{column_index}") +} + fn parse_slide_relationships_path(path: &str) -> Option { path.strip_prefix("ppt/slides/_rels/slide")? .strip_suffix(".xml.rels")? @@ -391,6 +651,20 @@ fn parse_slide_xml_path(path: &str) -> Option { .ok() } +fn parse_notes_slide_xml_path(path: &str) -> Option { + path.strip_prefix("ppt/notesSlides/notesSlide")? + .strip_suffix(".xml")? + .parse::() + .ok() +} + +fn parse_notes_slide_relationships_path(path: &str) -> Option { + path.strip_prefix("ppt/notesSlides/_rels/notesSlide")? + .strip_suffix(".xml.rels")? + .parse::() + .ok() +} + fn update_slide_relationships_xml( existing_bytes: Vec, relationships: &[String], @@ -451,7 +725,7 @@ fn update_slide_xml( let existing = String::from_utf8(existing_bytes).map_err(|error| error.to_string())?; let existing = replace_image_placeholders(existing, slide_images)?; let existing = apply_shape_block_patches(existing, slide)?; - let table_xml = slide_table_xml(slide); + let table_xml = slide_table_xml(slide)?; if table_xml.is_empty() { return Ok(existing); } @@ -461,6 +735,57 @@ fn update_slide_xml( .ok_or_else(|| "slide xml is missing a closing ``".to_string()) } +fn update_notes_slide_xml( + existing_bytes: Vec, + slide: &PresentationSlide, + document: &PresentationDocument, +) -> Result { + let existing = String::from_utf8(existing_bytes).map_err(|error| error.to_string())?; + if slide.notes.text.is_empty() { + return Ok(existing); + } + patch_notes_body_text( + existing, + &export_notes_text_body_xml( + &format!("notes_{}", slide.slide_id), + &slide.notes, + document, + ), + ) +} + +fn notes_hyperlink_relationships(slide: &PresentationSlide) -> Vec { + if !slide.notes.visible || slide.notes.text.is_empty() { + return Vec::new(); + } + let mut relationship_ids = HashSet::new(); + let mut relationships = Vec::new(); + collect_rich_text_hyperlinks( + &mut relationships, + &mut relationship_ids, + &format!("notes_{}", slide.slide_id), + &slide.notes.rich_text, + ); + relationships +} + +fn patch_notes_body_text(existing: String, text_body_xml: &str) -> Result { + let placeholder_marker = "") + .ok_or_else(|| "notes xml is missing an opening `` for the body placeholder".to_string())?; + let end = existing[marker..] + .find("") + .map(|offset| marker + offset + "".len()) + .ok_or_else(|| "notes xml is missing a closing `` for the body placeholder".to_string())?; + let block = &existing[start..end]; + let patched = patch_shape_block_text(block, text_body_xml)?; + Ok(format!("{}{}{}", &existing[..start], patched, &existing[end..])) +} + fn replace_image_placeholders( existing: String, slide_images: &[SlideImageAsset], @@ -493,11 +818,12 @@ fn replace_image_placeholders( Ok(updated) } -#[derive(Clone, Copy)] +#[derive(Clone)] struct ShapeXmlPatch { line_style: Option, flip_horizontal: bool, flip_vertical: bool, + text_body_xml: Option, } fn apply_shape_block_patches( @@ -512,16 +838,32 @@ fn apply_shape_block_patches( ordered.sort_by_key(|element| element.z_order()); for element in ordered { match element { - PresentationElement::Text(_) => patches.push(None), - PresentationElement::Shape(shape) => patches.push(Some(ShapeXmlPatch { - line_style: shape - .stroke - .as_ref() - .map(|stroke| stroke.style) - .filter(|style| *style != LineStyle::Solid), - flip_horizontal: shape.flip_horizontal, - flip_vertical: shape.flip_vertical, + PresentationElement::Text(text) => patches.push(Some(ShapeXmlPatch { + line_style: None, + flip_horizontal: false, + flip_vertical: false, + text_body_xml: Some(export_text_body_xml( + &text.element_id, + &text.text, + &text.style, + &text.rich_text, + )), })), + PresentationElement::Shape(shape) => { + let rich_text = shape.rich_text.clone().unwrap_or_default(); + patches.push(Some(ShapeXmlPatch { + line_style: shape + .stroke + .as_ref() + .map(|stroke| stroke.style) + .filter(|style| *style != LineStyle::Solid), + flip_horizontal: shape.flip_horizontal, + flip_vertical: shape.flip_vertical, + text_body_xml: shape.text.as_ref().map(|text| { + export_text_body_xml(&shape.element_id, text, &shape.text_style, &rich_text) + }), + })); + } PresentationElement::Image(ImageElement { payload: None, .. }) => patches.push(None), PresentationElement::Connector(_) | PresentationElement::Image(_) @@ -530,8 +872,11 @@ fn apply_shape_block_patches( } } if patches.iter().all(|patch| { - patch.is_none_or(|patch| { - patch.line_style.is_none() && !patch.flip_horizontal && !patch.flip_vertical + patch.as_ref().is_none_or(|patch| { + patch.line_style.is_none() + && !patch.flip_horizontal + && !patch.flip_vertical + && patch.text_body_xml.is_none() }) }) { return Ok(existing); @@ -561,11 +906,16 @@ fn apply_shape_block_patches( } fn patch_shape_block(block: &str, patch: ShapeXmlPatch) -> Result { - let block = if let Some(line_style) = patch.line_style { - patch_shape_block_dash(block, line_style)? + let block = if let Some(text_body_xml) = patch.text_body_xml { + patch_shape_block_text(block, &text_body_xml)? } else { block.to_string() }; + let block = if let Some(line_style) = patch.line_style { + patch_shape_block_dash(&block, line_style)? + } else { + block + }; if patch.flip_horizontal || patch.flip_vertical { patch_shape_block_flip(&block, patch.flip_horizontal, patch.flip_vertical) } else { @@ -573,6 +923,25 @@ fn patch_shape_block(block: &str, patch: ShapeXmlPatch) -> Result Result { + if let Some(text_start) = block.find("") { + let text_end = block[text_start..] + .find("") + .map(|offset| text_start + offset + "".len()) + .ok_or_else(|| "shape text body is missing a closing ``".to_string())?; + let mut patched = String::with_capacity(block.len() + text_body_xml.len()); + patched.push_str(&block[..text_start]); + patched.push_str(text_body_xml); + patched.push_str(&block[text_end..]); + Ok(patched) + } else { + block + .find("") + .map(|index| format!("{}{text_body_xml}{}", &block[..index], &block[index..])) + .ok_or_else(|| "shape block is missing a closing ``".to_string()) + } +} + fn patch_shape_block_dash(block: &str, line_style: LineStyle) -> Result { let Some(line_start) = block.find("` entry for stroke styling".to_string()); @@ -652,6 +1021,353 @@ fn patch_shape_block_flip( )) } +fn export_text_body_xml( + element_id: &str, + text: &str, + style: &TextStyle, + rich_text: &RichTextState, +) -> String { + let insets = rich_text.layout.insets.unwrap_or(TextInsets { + left: 6, + right: 6, + top: 6, + bottom: 6, + }); + let wrap = match rich_text.layout.wrap.unwrap_or(TextWrapMode::Square) { + TextWrapMode::Square => "square", + TextWrapMode::None => "none", + }; + let anchor = match rich_text + .layout + .vertical_alignment + .unwrap_or(TextVerticalAlignment::Top) + { + TextVerticalAlignment::Top => "t", + TextVerticalAlignment::Middle => "ctr", + TextVerticalAlignment::Bottom => "b", + }; + let auto_fit = match rich_text.layout.auto_fit.unwrap_or(TextAutoFitMode::None) { + TextAutoFitMode::None => String::new(), + TextAutoFitMode::ShrinkText => "".to_string(), + TextAutoFitMode::ResizeShapeToFitText => "".to_string(), + }; + let paragraphs = export_text_paragraphs_xml( + element_id, + text, + style, + style.alignment.unwrap_or(TextAlignment::Left), + rich_text, + ); + format!( + r#"{auto_fit}{paragraphs}"#, + points_to_emu(insets.left), + points_to_emu(insets.right), + points_to_emu(insets.top), + points_to_emu(insets.bottom), + ) +} + +fn export_notes_text_body_xml( + element_id: &str, + notes: &NotesState, + document: &PresentationDocument, +) -> String { + let style = TextStyle { + font_size: Some(12), + font_family: document.theme.minor_font.clone(), + color: document.theme.resolve_color("tx1"), + ..TextStyle::default() + }; + export_text_body_xml(element_id, ¬es.text, &style, ¬es.rich_text) +} + +fn export_table_cell_text_body_xml( + element_id: &str, + cell: &TableCellSpec, +) -> String { + let wrap = match cell.rich_text.layout.wrap.unwrap_or(TextWrapMode::Square) { + TextWrapMode::Square => "square", + TextWrapMode::None => "none", + }; + let anchor = match cell + .rich_text + .layout + .vertical_alignment + .unwrap_or(TextVerticalAlignment::Top) + { + TextVerticalAlignment::Top => "t", + TextVerticalAlignment::Middle => "ctr", + TextVerticalAlignment::Bottom => "b", + }; + let auto_fit = match cell + .rich_text + .layout + .auto_fit + .unwrap_or(TextAutoFitMode::None) + { + TextAutoFitMode::None => String::new(), + TextAutoFitMode::ShrinkText => "".to_string(), + TextAutoFitMode::ResizeShapeToFitText => "".to_string(), + }; + let insets = cell.rich_text.layout.insets.map(|insets| { + format!( + r#" lIns="{}" rIns="{}" tIns="{}" bIns="{}""#, + points_to_emu(insets.left), + points_to_emu(insets.right), + points_to_emu(insets.top), + points_to_emu(insets.bottom), + ) + }); + let paragraphs = export_text_paragraphs_xml( + element_id, + &cell.text, + &cell.text_style, + cell.alignment + .or(cell.text_style.alignment) + .unwrap_or(TextAlignment::Left), + &cell.rich_text, + ); + format!( + r#"{auto_fit}{paragraphs}"#, + insets.unwrap_or_default(), + ) +} + +fn export_text_paragraphs_xml( + element_id: &str, + text: &str, + style: &TextStyle, + alignment: TextAlignment, + rich_text: &RichTextState, +) -> String { + let chars = text.chars().collect::>(); + let mut styles = vec![style.clone(); chars.len()]; + let mut hyperlinks = vec![None; chars.len()]; + for range in &rich_text.ranges { + let start = range.start_cp.min(chars.len()); + let end = range.start_cp.saturating_add(range.length).min(chars.len()); + for entry in styles.iter_mut().skip(start).take(end.saturating_sub(start)) { + *entry = merged_text_style(entry, &range.style); + } + if let Some(hyperlink) = &range.hyperlink { + let relationship_id = rich_text_relationship_id(element_id, &range.range_id); + for entry in hyperlinks + .iter_mut() + .skip(start) + .take(end.saturating_sub(start)) + { + *entry = Some((relationship_id.clone(), hyperlink.clone())); + } + } + } + + let alignment = match alignment { + TextAlignment::Left => "l", + TextAlignment::Center => "ctr", + TextAlignment::Right => "r", + TextAlignment::Justify => "just", + }; + + let mut paragraphs = Vec::new(); + let mut paragraph_runs: Vec = Vec::new(); + let mut current_text = String::new(); + let mut current_style: Option = None; + let mut current_hyperlink: Option<(String, HyperlinkState)> = None; + let mut paragraph_start = 0_usize; + + let flush_run = |paragraph_runs: &mut Vec, + current_text: &mut String, + current_style: &mut Option, + current_hyperlink: &mut Option<(String, HyperlinkState)>| { + if let Some(style) = current_style.take() + && !current_text.is_empty() + { + paragraph_runs.push(TextRunExport { + text: std::mem::take(current_text), + style, + hyperlink: current_hyperlink.take(), + }); + } + }; + + for (index, ch) in chars.iter().enumerate() { + if *ch == '\n' { + flush_run( + &mut paragraph_runs, + &mut current_text, + &mut current_style, + &mut current_hyperlink, + ); + paragraphs.push(render_text_paragraph_xml( + ¶graph_runs, + alignment, + paragraph_spacing_xml(rich_text, paragraph_start, index), + )); + paragraph_runs.clear(); + paragraph_start = index + 1; + continue; + } + + let next_style = styles[index].clone(); + let next_hyperlink = hyperlinks[index].clone(); + match ¤t_style { + Some(existing) + if text_style_eq(existing, &next_style) && current_hyperlink == next_hyperlink => + { + current_text.push(*ch) + } + Some(_) => { + flush_run( + &mut paragraph_runs, + &mut current_text, + &mut current_style, + &mut current_hyperlink, + ); + current_style = Some(next_style); + current_hyperlink = next_hyperlink; + current_text.push(*ch); + } + None => { + current_style = Some(next_style); + current_hyperlink = next_hyperlink; + current_text.push(*ch); + } + } + } + flush_run( + &mut paragraph_runs, + &mut current_text, + &mut current_style, + &mut current_hyperlink, + ); + if !paragraph_runs.is_empty() || paragraphs.is_empty() { + paragraphs.push(render_text_paragraph_xml( + ¶graph_runs, + alignment, + paragraph_spacing_xml(rich_text, paragraph_start, chars.len()), + )); + } + paragraphs.join("") +} + +#[derive(Clone)] +struct TextRunExport { + text: String, + style: TextStyle, + hyperlink: Option<(String, HyperlinkState)>, +} + +fn render_text_paragraph_xml( + runs: &[TextRunExport], + alignment: &str, + spacing_xml: String, +) -> String { + if runs.is_empty() { + return format!(r#"{spacing_xml}"#); + } + let runs_xml = runs + .iter() + .map(render_text_run_xml) + .collect::(); + format!(r#"{spacing_xml}{runs_xml}"#) +} + +fn render_text_run_xml(run: &TextRunExport) -> String { + let TextRunExport { + text, + style, + hyperlink, + } = run; + let mut attrs = vec!["lang=\"en-US\"".to_string(), "dirty=\"0\"".to_string()]; + if let Some(font_size) = style.font_size { + attrs.push(format!(r#"sz="{}""#, font_size * 100)); + } + if style.bold { + attrs.push(r#"b="1""#.to_string()); + } + if style.italic { + attrs.push(r#"i="1""#.to_string()); + } + if style.underline { + attrs.push(r#"u="sng""#.to_string()); + } + + let mut children = String::new(); + if let Some(color) = &style.color { + children.push_str(&format!( + r#""#, + color.trim_start_matches('#').to_ascii_uppercase() + )); + } + if let Some(font_family) = &style.font_family { + let escaped = ppt_rs::escape_xml(font_family); + children.push_str(&format!( + r#""# + )); + } + if let Some((relationship_id, hyperlink)) = hyperlink { + let tooltip = hyperlink + .tooltip + .as_ref() + .map(|tooltip| format!(r#" tooltip="{}""#, ppt_rs::escape_xml(tooltip))) + .unwrap_or_default(); + let highlight = if hyperlink.highlight_click { "1" } else { "0" }; + children.push_str(&format!( + r#""# + )); + } + + format!( + r#"{children}{}"#, + attrs.join(" "), + ppt_rs::escape_xml(text) + ) +} + +fn paragraph_spacing_xml(rich_text: &RichTextState, start: usize, end: usize) -> String { + let mut spacing_before = None; + let mut spacing_after = None; + let mut line_spacing = None; + for range in &rich_text.ranges { + let range_end = range.start_cp.saturating_add(range.length); + if range.start_cp >= end || range_end <= start { + continue; + } + if spacing_before.is_none() { + spacing_before = range.spacing_before; + } + if spacing_after.is_none() { + spacing_after = range.spacing_after; + } + if line_spacing.is_none() { + line_spacing = range.line_spacing; + } + } + + let mut xml = String::new(); + if let Some(value) = spacing_before { + xml.push_str(&format!(r#""#)); + } + if let Some(value) = spacing_after { + xml.push_str(&format!(r#""#)); + } + if let Some(value) = line_spacing { + let spacing = (value * 100_000.0).round() as i32; + xml.push_str(&format!(r#""#)); + } + xml +} + +fn text_style_eq(left: &TextStyle, right: &TextStyle) -> bool { + left.font_size == right.font_size + && left.font_family == right.font_family + && left.color == right.color + && left.alignment == right.alignment + && left.bold == right.bold + && left.italic == right.italic + && left.underline == right.underline +} + fn upsert_xml_attribute(tag: &str, attribute: &str, value: &str) -> String { let needle = format!(r#"{attribute}=""#); if let Some(start) = tag.find(&needle) { @@ -665,54 +1381,112 @@ fn upsert_xml_attribute(tag: &str, attribute: &str, value: &str) -> String { format!(r#"{} {attribute}="{value}""#, &tag[..insert_at]) + &tag[insert_at..] } -fn slide_table_xml(slide: &PresentationSlide) -> String { +fn slide_table_xml(slide: &PresentationSlide) -> Result { let mut ordered = slide.elements.iter().collect::>(); ordered.sort_by_key(|element| element.z_order()); let mut table_index = 0_usize; - ordered - .into_iter() - .filter_map(|element| { - let PresentationElement::Table(table) = element else { - return None; + let mut xml = Vec::new(); + for element in ordered { + let PresentationElement::Table(table) = element else { + continue; + }; + table_index += 1; + let rows = table + .rows + .clone() + .into_iter() + .enumerate() + .map(|(row_index, row)| { + let cells = row + .into_iter() + .enumerate() + .map(|(column_index, cell)| { + build_table_cell(cell, &table.merges, row_index, column_index) + }) + .collect::>(); + let mut table_row = TableRow::new(cells); + if let Some(height) = table.row_heights.get(row_index) { + table_row = table_row.with_height(points_to_emu(*height)); + } + Some(table_row) + }) + .collect::>>() + .ok_or_else(|| "table export failed to build rows".to_string())?; + let table_xml = ppt_rs::generator::table::generate_table_xml( + &ppt_rs::generator::table::Table::new( + rows, + table + .column_widths + .iter() + .copied() + .map(points_to_emu) + .collect(), + points_to_emu(table.frame.left), + points_to_emu(table.frame.top), + ), + 300 + table_index, + ); + xml.push(patch_table_cell_text_bodies(table_xml, table)?); + } + Ok(xml.join("\n")) +} + +fn patch_table_cell_text_bodies(table_xml: String, table: &TableElement) -> Result { + let mut updated = String::with_capacity(table_xml.len()); + let mut remaining = table_xml.as_str(); + for (row_index, row) in table.rows.iter().enumerate() { + for (column_index, cell) in row.iter().enumerate() { + let Some(start) = remaining.find("` block".to_string()); }; - table_index += 1; - let rows = table - .rows - .clone() - .into_iter() - .enumerate() - .map(|(row_index, row)| { - let cells = row - .into_iter() - .enumerate() - .map(|(column_index, cell)| { - build_table_cell(cell, &table.merges, row_index, column_index) - }) - .collect::>(); - let mut table_row = TableRow::new(cells); - if let Some(height) = table.row_heights.get(row_index) { - table_row = table_row.with_height(points_to_emu(*height)); - } - Some(table_row) - }) - .collect::>>()?; - Some(ppt_rs::generator::table::generate_table_xml( - &ppt_rs::generator::table::Table::new( - rows, - table - .column_widths - .iter() - .copied() - .map(points_to_emu) - .collect(), - points_to_emu(table.frame.left), - points_to_emu(table.frame.top), - ), - 300 + table_index, - )) - }) - .collect::>() - .join("\n") + let end = remaining[start..] + .find("") + .map(|offset| start + offset + "".len()) + .ok_or_else(|| "table xml is missing a closing `` block".to_string())?; + updated.push_str(&remaining[..start]); + let block = &remaining[start..end]; + if table_cell_is_merged(table, row_index, column_index) { + updated.push_str(block); + } else { + updated.push_str(&patch_table_cell_text( + block, + &export_table_cell_text_body_xml( + &table_cell_export_id(&table.element_id, row_index, column_index), + cell, + ), + )?); + } + remaining = &remaining[end..]; + } + } + updated.push_str(remaining); + Ok(updated) +} + +fn table_cell_is_merged(table: &TableElement, row_index: usize, column_index: usize) -> bool { + table.merges.iter().any(|merge| { + row_index >= merge.start_row + && row_index <= merge.end_row + && column_index >= merge.start_column + && column_index <= merge.end_column + && (row_index != merge.start_row || column_index != merge.start_column) + }) +} + +fn patch_table_cell_text(block: &str, text_body_xml: &str) -> Result { + if let Some(text_start) = block.find("") { + let text_end = block[text_start..] + .find("") + .map(|offset| text_start + offset + "".len()) + .ok_or_else(|| "table cell text body is missing a closing ``".to_string())?; + let mut patched = String::with_capacity(block.len() + text_body_xml.len()); + patched.push_str(&block[..text_start]); + patched.push_str(text_body_xml); + patched.push_str(&block[text_end..]); + Ok(patched) + } else { + Err("table cell block is missing an `` entry".to_string()) + } } pub(crate) fn write_preview_image_bytes( diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs index 22da641013..83e8cdfc71 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs @@ -1,9 +1,11 @@ use font8x8::BASIC_FONTS; use font8x8::UnicodeFonts; +use fontdue::Font; use image::DynamicImage; use image::Rgba; use image::RgbaImage; use std::f32::consts::PI; +use std::sync::{Arc, Mutex, OnceLock}; use tiny_skia::FillRule; use tiny_skia::LineCap; use tiny_skia::LineJoin; @@ -22,6 +24,19 @@ const DEFAULT_TABLE_GRID_HEX: &str = "BFC5CC"; const DEFAULT_COMMENT_HEX: &str = "F3C83C"; const DEFAULT_TEXT_INSET: u32 = 6; const MIN_TEXT_PIXELS: u32 = 8; +const PREVIEW_SUPERSAMPLE_SCALE: f32 = 2.0; +const CHART_EXPORT_SCALE: f32 = 2.0; +const DEFAULT_PREVIEW_FONT_SIZE_RATIO: f32 = 0.85; +const PREVIEW_FONT_DIRS: [&str; 3] = [ + "/Library/Fonts", + "/System/Library/Fonts/Supplemental", + "/System/Library/Fonts", +]; +const PREVIEW_FONT_FALLBACKS: [&str; 5] = + ["Arial", "Helvetica", "Avenir", "Times", "Courier"]; + +static PREVIEW_FONT_CACHE: OnceLock>>>> = + OnceLock::new(); #[derive(Debug, Clone, Copy)] pub(crate) struct RenderOptions { @@ -47,6 +62,7 @@ struct TextRenderConfig { width: u32, font_px: u32, line_height: u32, + baseline_offset: u32, wrap: TextWrapMode, vertical_alignment: TextVerticalAlignment, alignment: TextAlignment, @@ -72,12 +88,57 @@ fn render_slide_png( }); } + let target_width = scaled_dimension(document.slide_size.width, options.scale); + let target_height = scaled_dimension(document.slide_size.height, options.scale); + let render_scale = options.scale * PREVIEW_SUPERSAMPLE_SCALE; + let mut canvas = render_slide_image(document, slide_index, options, render_scale)?; + if PREVIEW_SUPERSAMPLE_SCALE > 1.0 && (canvas.width() != target_width || canvas.height() != target_height) + { + canvas = image::imageops::resize( + &canvas, + target_width.max(1), + target_height.max(1), + image::imageops::FilterType::Lanczos3, + ); + } + + let mut output = std::io::Cursor::new(Vec::new()); + DynamicImage::ImageRgba8(canvas) + .write_to(&mut output, image::ImageFormat::Png) + .map_err(|error| PresentationArtifactError::RenderFailed { + action: "render_preview".to_string(), + message: error.to_string(), + })?; + Ok(output.into_inner()) +} + +fn render_chart_png_bytes( + document: &PresentationDocument, + chart: &ChartElement, +) -> Result, PresentationArtifactError> { + let rendered = render_chart_element(chart, document, CHART_EXPORT_SCALE)?; + let mut output = std::io::Cursor::new(Vec::new()); + DynamicImage::ImageRgba8(rendered) + .write_to(&mut output, image::ImageFormat::Png) + .map_err(|error| PresentationArtifactError::RenderFailed { + action: "export_pptx".to_string(), + message: error.to_string(), + })?; + Ok(output.into_inner()) +} + +fn render_slide_image( + document: &PresentationDocument, + slide_index: usize, + options: RenderOptions, + render_scale: f32, +) -> Result { let slide = document .slides .get(slide_index) .ok_or_else(|| index_out_of_range("render_preview", slide_index, document.slides.len()))?; - let width = scaled_dimension(document.slide_size.width, options.scale); - let height = scaled_dimension(document.slide_size.height, options.scale); + let width = scaled_dimension(document.slide_size.width, render_scale); + let height = scaled_dimension(document.slide_size.height, render_scale); let mut canvas = RgbaImage::from_pixel(width, height, Rgba([0, 0, 0, 0])); if options.include_background { @@ -94,19 +155,11 @@ fn render_slide_png( let mut ordered = slide.elements.iter().collect::>(); ordered.sort_by_key(|element| element.z_order()); for element in ordered { - render_element(&mut canvas, document, slide, element, options.scale)?; + render_element(&mut canvas, document, slide, element, render_scale)?; } - render_comment_overlays(&mut canvas, document, slide, options.scale); - - let mut output = std::io::Cursor::new(Vec::new()); - DynamicImage::ImageRgba8(canvas) - .write_to(&mut output, image::ImageFormat::Png) - .map_err(|error| PresentationArtifactError::RenderFailed { - action: "render_preview".to_string(), - message: error.to_string(), - })?; - Ok(output.into_inner()) + render_comment_overlays(&mut canvas, document, slide, render_scale); + Ok(canvas) } fn render_element( @@ -776,9 +829,30 @@ fn render_chart_legend( if cursor >= left + width { break; } - fill_rect(image, cursor, top + (height.saturating_sub(swatch)) / 2, swatch, swatch, chart_series_color(series, index)); + fill_rect( + image, + cursor, + top + (height.saturating_sub(swatch)) / 2, + swatch, + swatch, + chart_series_color(series, index), + ); cursor += swatch + scaled_dimension(4, scale); - let label_width = scaled_dimension((series.name.chars().count() as u32).saturating_mul(7), scale).max(40); + let label_style = TextStyle { + font_size: Some(12), + color: Some(DEFAULT_TEXT_HEX.to_string()), + ..TextStyle::default() + }; + let label_rich_text = RichTextState { + layout: TextLayoutState { + vertical_alignment: Some(TextVerticalAlignment::Middle), + ..TextLayoutState::default() + }, + ..RichTextState::default() + }; + let label_width = measure_text_width(&series.name, &label_style, &label_rich_text, None, scale) + .saturating_add(scaled_dimension(8, scale)) + .max(40); draw_text_in_bounds( image, TextBounds { @@ -788,18 +862,8 @@ fn render_chart_legend( height, }, &series.name, - &TextStyle { - font_size: Some(12), - color: Some(DEFAULT_TEXT_HEX.to_string()), - ..TextStyle::default() - }, - &RichTextState { - layout: TextLayoutState { - vertical_alignment: Some(TextVerticalAlignment::Middle), - ..TextLayoutState::default() - }, - ..RichTextState::default() - }, + &label_style, + &label_rich_text, None, scale, ); @@ -807,6 +871,20 @@ fn render_chart_legend( } } +fn measure_text_width( + text: &str, + style: &TextStyle, + rich_text: &RichTextState, + hyperlink: Option<&HyperlinkState>, + scale: f32, +) -> u32 { + let font_px = scaled_dimension(style.font_size.unwrap_or(14), scale).max(MIN_TEXT_PIXELS); + styled_glyphs(text, style, rich_text, hyperlink) + .iter() + .map(|glyph| measure_glyph_width(glyph, font_px)) + .sum() +} + fn render_comment_overlays( canvas: &mut RgbaImage, document: &PresentationDocument, @@ -922,11 +1000,12 @@ fn draw_text_in_bounds( let mut font_px = scaled_dimension(style.font_size.unwrap_or(14), scale).max(MIN_TEXT_PIXELS); let auto_fit = rich_text.layout.auto_fit.unwrap_or(TextAutoFitMode::None); let config = loop { - let line_height = ((font_px as f32) * 1.35).round().max(font_px as f32) as u32; + let (line_height, baseline_offset) = preview_line_metrics(style, font_px); let config = TextRenderConfig { width: content_width, font_px, line_height, + baseline_offset, wrap, vertical_alignment, alignment, @@ -972,7 +1051,8 @@ fn draw_text_in_bounds( let mut x = start_x; for glyph in &line.glyphs { - let glyph_width = draw_bitmap_glyph(image, x, y, glyph, config.font_px); + let glyph_width = + draw_bitmap_glyph(image, x, y, glyph, config.font_px, config.baseline_offset); x = x.saturating_add(glyph_width); if glyph.ch == ' ' && justify_gap > 0 { x = x.saturating_add(justify_gap); @@ -1087,6 +1167,20 @@ fn draw_bitmap_glyph( top: u32, glyph: &StyledGlyph, font_px: u32, + baseline_offset: u32, +) -> u32 { + if let Some(font) = preview_font_for_style(&glyph.style) { + return draw_outline_glyph(image, left, top, glyph, font_px, baseline_offset, &font); + } + draw_bitmap_glyph_fallback(image, left, top, glyph, font_px) +} + +fn draw_bitmap_glyph_fallback( + image: &mut RgbaImage, + left: u32, + top: u32, + glyph: &StyledGlyph, + font_px: u32, ) -> u32 { let scale = (font_px / 8).max(1); if glyph.ch == ' ' { @@ -1122,6 +1216,30 @@ fn draw_bitmap_glyph( } fn measure_glyph_width(glyph: &StyledGlyph, font_px: u32) -> u32 { + if let Some(font) = preview_font_for_style(&glyph.style) { + return measure_outline_glyph_width(glyph, font_px, &font); + } + measure_bitmap_glyph_width(glyph, font_px) +} + +fn preview_line_metrics(style: &TextStyle, font_px: u32) -> (u32, u32) { + if let Some(font) = preview_font_for_style(style) + && let Some(metrics) = font.horizontal_line_metrics(font_px as f32) + { + return ( + metrics.new_line_size.ceil().max(font_px as f32).max(1.0) as u32, + metrics.ascent.ceil().max(1.0) as u32, + ); + } + ( + ((font_px as f32) * 1.35).round().max(font_px as f32) as u32, + ((font_px as f32) * DEFAULT_PREVIEW_FONT_SIZE_RATIO) + .round() + .max(1.0) as u32, + ) +} + +fn measure_bitmap_glyph_width(glyph: &StyledGlyph, font_px: u32) -> u32 { let scale = (font_px / 8).max(1); match glyph.ch { ' ' => 4 * scale, @@ -1130,6 +1248,207 @@ fn measure_glyph_width(glyph: &StyledGlyph, font_px: u32) -> u32 { } } +fn preview_font_for_style(style: &TextStyle) -> Option> { + let key = normalize_font_name(style.font_family.as_deref().unwrap_or("default")); + let cache = PREVIEW_FONT_CACHE.get_or_init(|| Mutex::new(std::collections::HashMap::new())); + if let Ok(cache) = cache.lock() + && let Some(font) = cache.get(&key).cloned() + { + return font; + } + + let font = load_preview_font(style.font_family.as_deref()); + if let Ok(mut cache) = cache.lock() { + cache.insert(key, font.clone()); + } + font +} + +fn load_preview_font(requested_family: Option<&str>) -> Option> { + let mut candidates = requested_family + .into_iter() + .flat_map(preview_font_family_candidates) + .collect::>(); + candidates.extend(PREVIEW_FONT_FALLBACKS.iter().map(std::string::ToString::to_string)); + + for family in candidates { + if let Some(path) = find_system_font_path(&family) + && let Some(font) = load_preview_font_from_path(&path) + { + return Some(font); + } + } + None +} + +fn preview_font_family_candidates(family: &str) -> Vec { + let normalized = normalize_font_name(family); + let mut candidates = vec![family.to_string()]; + if normalized.contains("aptos") { + candidates.extend(["Arial", "Helvetica", "Avenir"].map(str::to_string)); + } else if normalized.contains("timesnewroman") { + candidates.push("Times".to_string()); + } else if normalized.contains("couriernew") { + candidates.push("Courier".to_string()); + } + candidates +} + +fn find_system_font_path(family: &str) -> Option { + let needle = normalize_font_name(family); + let mut partial_match = None; + for directory in PREVIEW_FONT_DIRS { + let Ok(entries) = std::fs::read_dir(directory) else { + continue; + }; + for entry in entries.filter_map(Result::ok) { + let path = entry.path(); + if !supported_preview_font_path(&path) { + continue; + } + let Some(stem) = path.file_stem() else { + continue; + }; + let stem = stem.to_string_lossy(); + let normalized_stem = normalize_font_name(&stem); + if normalized_stem == needle { + return Some(path); + } + if partial_match.is_none() && normalized_stem.contains(&needle) { + partial_match = Some(path); + } + } + } + partial_match +} + +fn supported_preview_font_path(path: &Path) -> bool { + path.extension() + .and_then(|extension| extension.to_str()) + .map(|extension| matches!(extension.to_ascii_lowercase().as_str(), "ttf" | "otf")) + .unwrap_or(false) +} + +fn normalize_font_name(name: &str) -> String { + name.chars() + .filter(char::is_ascii_alphanumeric) + .flat_map(char::to_lowercase) + .collect() +} + +fn load_preview_font_from_path(path: &Path) -> Option> { + let bytes = std::fs::read(path).ok()?; + Font::from_bytes(bytes, fontdue::FontSettings::default()) + .ok() + .map(Arc::new) +} + +fn measure_outline_glyph_width(glyph: &StyledGlyph, font_px: u32, font: &Font) -> u32 { + let font_px = font_px.max(MIN_TEXT_PIXELS) as f32; + if glyph.ch == '\t' { + let space_metrics = font.metrics(' ', font_px); + return (space_metrics.advance_width.max(font_px / 3.0) * 4.0) + .ceil() + .max(1.0) as u32; + } + let metrics = font.metrics(glyph.ch, font_px); + metrics + .advance_width + .max(metrics.width as f32 + metrics.xmin.max(0) as f32) + .max(font_px / 3.0) + .ceil() + .max(1.0) as u32 +} + +fn draw_outline_glyph( + image: &mut RgbaImage, + left: u32, + top: u32, + glyph: &StyledGlyph, + font_px: u32, + baseline_offset: u32, + font: &Font, +) -> u32 { + let font_px = font_px.max(MIN_TEXT_PIXELS); + let width = measure_outline_glyph_width(glyph, font_px, font); + if glyph.ch == ' ' || glyph.ch == '\t' { + return width; + } + + let (metrics, bitmap) = font.rasterize(glyph.ch, font_px as f32); + let baseline = top as i32 + baseline_offset as i32; + let color = glyph_color(&glyph.style, glyph.is_link); + blit_font_bitmap(image, left, baseline, glyph, &metrics, &bitmap, color); + if glyph.style.bold { + blit_font_bitmap( + image, + left.saturating_add(1), + baseline, + glyph, + &metrics, + &bitmap, + color, + ); + } + if glyph.style.underline || glyph.is_link { + let underline_y = top.saturating_add(font_px).saturating_sub(font_px / 8 + 1); + fill_rect(image, left, underline_y, width.max(1), (font_px / 12).max(1), color); + } + width +} + +fn blit_font_bitmap( + image: &mut RgbaImage, + left: u32, + baseline: i32, + glyph: &StyledGlyph, + metrics: &fontdue::Metrics, + bitmap: &[u8], + color: Rgba, +) { + if metrics.width == 0 || metrics.height == 0 { + return; + } + let origin_x = left as i32 + metrics.xmin; + let origin_y = baseline - metrics.height as i32 - metrics.ymin; + let italic_skew = if glyph.style.italic { + (metrics.height as f32 * 0.18).round().max(1.0) as i32 + } else { + 0 + }; + + for row in 0..metrics.height { + let row_skew = if italic_skew > 0 { + (((metrics.height - row) as f32 / metrics.height as f32) * italic_skew as f32).round() + as i32 + } else { + 0 + }; + for column in 0..metrics.width { + let alpha = bitmap[row * metrics.width + column]; + if alpha == 0 { + continue; + } + let dest_x = origin_x + column as i32 + row_skew; + let dest_y = origin_y + row as i32; + if dest_x < 0 + || dest_y < 0 + || dest_x >= image.width() as i32 + || dest_y >= image.height() as i32 + { + continue; + } + let source = Rgba([ + color.0[0], + color.0[1], + color.0[2], + ((u16::from(alpha) * u16::from(color.0[3])) / 255) as u8, + ]); + blend_pixel(image.get_pixel_mut(dest_x as u32, dest_y as u32), source); + } + } +} + fn glyph_color(style: &TextStyle, is_link: bool) -> Rgba { if let Some(color) = &style.color { return parse_rgba(color, 255); diff --git a/codex-rs/artifact-presentation/src/tests.rs b/codex-rs/artifact-presentation/src/tests.rs index 9cd00e19b6..793d646976 100644 --- a/codex-rs/artifact-presentation/src/tests.rs +++ b/codex-rs/artifact-presentation/src/tests.rs @@ -281,6 +281,408 @@ fn exported_images_are_real_pictures_with_media_parts() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Chart Export" }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id.clone()), + action: "add_chart".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "chart_type": "bar", + "position": { "left": 48, "top": 64, "width": 320, "height": 220 }, + "title": "Weekly Active Teams", + "categories": ["Week 1", "Week 2", "Week 3", "Week 4"], + "series": [ + { "name": "Actual", "values": [12, 18, 24, 31], "fill": "#2B5797" }, + { "name": "Target", "values": [15, 20, 26, 30], "fill": "#D97A00" } + ] + }), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("charts-as-images.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let pptx_path = temp_dir.path().join("charts-as-images.pptx"); + let slide_xml = zip_entry_text(&pptx_path, "ppt/slides/slide1.xml")?; + let rels_xml = zip_entry_text(&pptx_path, "ppt/slides/_rels/slide1.xml.rels")?; + let content_types_xml = zip_entry_text(&pptx_path, "[Content_Types].xml")?; + let entry_names = zip_entry_names(&pptx_path)?; + + assert!(slide_xml.contains("")); + assert!(slide_xml.contains(r#"descr="Weekly Active Teams""#)); + assert!(slide_xml.contains(r#"r:embed="rIdImage1""#)); + assert!(!slide_xml.contains(" Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Theme Export", + "theme": { + "color_scheme": { + "accent1": "#123456", + "bg1": "#FAF7F0", + "tx1": "#222222" + }, + "major_font": "Aptos Display", + "minor_font": "Aptos" + } + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("theme-export.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let theme_xml = zip_entry_text( + &temp_dir.path().join("theme-export.pptx"), + "ppt/theme/theme1.xml", + )?; + assert!(theme_xml.contains(r#""#)); + assert!(theme_xml.contains(r#"typeface="Aptos Display""#)); + assert!(theme_xml.contains(r#"typeface="Aptos""#)); + assert!(theme_xml.contains(r#""#)); + assert!(theme_xml.contains(r#""#)); + assert!(theme_xml.contains(r#""#)); + Ok(()) +} + +#[test] +fn exported_text_shapes_preserve_text_styling() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Styled Text Export", + "theme": { + "color_scheme": {}, + "major_font": "Aptos Display", + "minor_font": "Aptos" + } + }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id.clone(); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added_text = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "Alpha Beta", + "position": { "left": 40, "top": 40, "width": 220, "height": 80 }, + "font_size": 24, + "font_family": "Aptos", + "color": "#112233", + "alignment": "right", + "bold": true, + "text_layout": { + "vertical_alignment": "bottom", + "auto_fit": "shrinkText" + } + }), + }, + temp_dir.path(), + )?; + let text_id = added_text + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("text id"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "format_text_range".to_string(), + args: serde_json::json!({ + "element_id": text_id, + "query": "Beta", + "link": { + "uri": "https://example.com/docs", + "is_external": true + }, + "spacing_before": 12, + "spacing_after": 6, + "line_spacing": 1.5, + "styling": { + "italic": true, + "underline": true, + "color": "#445566" + } + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rectangle", + "position": { "left": 300, "top": 60, "width": 180, "height": 100 }, + "fill": "#2F6B5F", + "text": "Status", + "text_style": { + "font_family": "Aptos Display", + "font_size": 20, + "color": "#FFFFFF", + "alignment": "center", + "bold": true + }, + "text_layout": { + "vertical_alignment": "middle" + } + }), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("styled-text-export.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let slide_xml = zip_entry_text( + &temp_dir.path().join("styled-text-export.pptx"), + "ppt/slides/slide1.xml", + )?; + let rels_xml = zip_entry_text( + &temp_dir.path().join("styled-text-export.pptx"), + "ppt/slides/_rels/slide1.xml.rels", + )?; + assert!(slide_xml.contains(r#"anchor="b""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#"sz="2400""#)); + assert!(slide_xml.contains(r#"b="1""#)); + assert!(slide_xml.contains(r#"typeface="Aptos""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(">Beta<")); + assert!(slide_xml.contains(r#"i="1""#)); + assert!(slide_xml.contains(r#"u="sng""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#"anchor="ctr""#)); + assert!(slide_xml.contains(r#""#)); + assert!(rels_xml.contains("relationships/hyperlink")); + assert!(rels_xml.contains(r#"Target="https://example.com/docs""#)); + Ok(()) +} + +#[test] +fn exported_table_cells_preserve_rich_text_styling() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Styled Table Export", + "theme": { + "color_scheme": {}, + "major_font": "Aptos Display", + "minor_font": "Aptos" + } + }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id.clone(); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added_table = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_table".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 40, "top": 40, "width": 240, "height": 90 }, + "rows": [["Alpha Beta"]] + }), + }, + temp_dir.path(), + )?; + let table_id = added_table + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("table id"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_table_cell".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "row": 0, + "column": 0, + "value": "Alpha Beta", + "alignment": "right", + "styling": { + "font_family": "Aptos", + "font_size": 20, + "color": "#112233", + "bold": true + } + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "format_text_range".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "row": 0, + "column": 0, + "query": "Beta", + "link": { + "uri": "https://example.com/table", + "is_external": true + }, + "spacing_before": 8, + "spacing_after": 4, + "line_spacing": 1.25, + "styling": { + "italic": true, + "underline": true, + "color": "#445566" + } + }), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("styled-table-export.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let slide_xml = zip_entry_text( + &temp_dir.path().join("styled-table-export.pptx"), + "ppt/slides/slide1.xml", + )?; + let rels_xml = zip_entry_text( + &temp_dir.path().join("styled-table-export.pptx"), + "ppt/slides/_rels/slide1.xml.rels", + )?; + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#"sz="2000""#)); + assert!(slide_xml.contains(r#"b="1""#)); + assert!(slide_xml.contains(r#"typeface="Aptos""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(">Beta<")); + assert!(slide_xml.contains(r#"i="1""#)); + assert!(slide_xml.contains(r#"u="sng""#)); + assert!(slide_xml.contains(r#""#)); + assert!(slide_xml.contains(r#" Result<(), Box> { let request: PresentationArtifactToolRequest = serde_json::from_value(serde_json::json!({ @@ -2205,6 +2607,95 @@ fn notes_visibility_controls_exported_notes() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Styled Notes Export", + "theme": { + "color_scheme": { + "tx1": "#222222" + }, + "major_font": "Aptos Display", + "minor_font": "Aptos" + } + }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id.clone(); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({ "notes": "Alpha Beta" }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "format_text_range".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "notes": true, + "query": "Beta", + "link": { + "uri": "https://example.com/notes", + "is_external": true + }, + "spacing_before": 10, + "spacing_after": 5, + "line_spacing": 1.4, + "styling": { + "italic": true, + "underline": true, + "color": "#445566" + } + }), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("styled-notes-export.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let notes_xml = zip_entry_text( + &temp_dir.path().join("styled-notes-export.pptx"), + "ppt/notesSlides/notesSlide1.xml", + )?; + let rels_xml = zip_entry_text( + &temp_dir.path().join("styled-notes-export.pptx"), + "ppt/notesSlides/_rels/notesSlide1.xml.rels", + )?; + assert!(notes_xml.contains(r#"typeface="Aptos""#)); + assert!(notes_xml.contains(r#"sz="1200""#)); + assert!(notes_xml.contains(r#""#)); + assert!(notes_xml.contains(">Beta<")); + assert!(notes_xml.contains(r#"i="1""#)); + assert!(notes_xml.contains(r#"u="sng""#)); + assert!(notes_xml.contains(r#""#)); + assert!(notes_xml.contains(r#""#)); + assert!(notes_xml.contains(r#""#)); + assert!(notes_xml.contains(r#""#)); + assert!(rels_xml.contains("relationships/hyperlink")); + assert!(rels_xml.contains(r#"Target="https://example.com/notes""#)); + Ok(()) +} + #[test] fn image_placeholders_and_anchor_updates_work() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -3689,7 +4180,10 @@ fn render_preview_renders_image_and_table_content() -> Result<(), Box { - for sheet in &self.sheets { - if !sheet.charts.is_empty() - || !sheet.tables.is_empty() - || !sheet.conditional_formats.is_empty() - || !sheet.pivot_tables.is_empty() - { - return Err(SpreadsheetArtifactError::ExportFailed { - path: path.to_path_buf(), - message: format!( - "xlsx export does not yet support charts, tables, conditional formats, or pivot tables on sheet `{}`; use json or bin export instead", - sheet.name - ), - }); - } - } - write_xlsx(self, path) - } + "xlsx" => write_xlsx(self, path), "json" => { let json = self.to_json()?; std::fs::write(path, json).map_err(|error| { diff --git a/codex-rs/artifact-spreadsheet/src/style.rs b/codex-rs/artifact-spreadsheet/src/style.rs index 0c9a3782a5..2b7857ecfc 100644 --- a/codex-rs/artifact-spreadsheet/src/style.rs +++ b/codex-rs/artifact-spreadsheet/src/style.rs @@ -239,6 +239,18 @@ impl SpreadsheetNumberFormat { } fn normalized(mut self) -> Self { + if let Some(format_id) = self.format_id + && let Some(format_code) = builtin_number_format_code(format_id) + { + self.format_code = Some(format_code); + return self; + } + if self.format_id.is_none() { + self.format_id = self + .format_code + .as_deref() + .and_then(builtin_number_format_id); + } if self.format_code.is_none() { self.format_code = self.format_id.and_then(builtin_number_format_code); } @@ -493,6 +505,7 @@ impl SpreadsheetArtifact { } else { format }; + self.validate_cell_format_references(&created, "create_cell_format")?; Ok(insert_with_next_id(&mut self.cell_formats, created)) } @@ -500,8 +513,12 @@ impl SpreadsheetArtifact { self.cell_formats.get(&format_id) } - pub fn create_differential_format(&mut self, format: SpreadsheetDifferentialFormat) -> u32 { - insert_with_next_id(&mut self.differential_formats, format) + pub fn create_differential_format( + &mut self, + format: SpreadsheetDifferentialFormat, + ) -> Result { + self.validate_differential_format_references(&format, "create_differential_format")?; + Ok(insert_with_next_id(&mut self.differential_formats, format)) } pub fn get_differential_format( @@ -534,6 +551,70 @@ impl SpreadsheetArtifact { wrap_text: resolved.wrap_text, }) } + + pub fn validate_style_index( + &self, + style_index: u32, + action: &str, + ) -> Result<(), SpreadsheetArtifactError> { + if style_index == 0 || self.cell_formats.contains_key(&style_index) { + return Ok(()); + } + Err(SpreadsheetArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("style index `{style_index}` was not found"), + }) + } + + fn validate_cell_format_references( + &self, + format: &SpreadsheetCellFormat, + action: &str, + ) -> Result<(), SpreadsheetArtifactError> { + validate_optional_reference( + format.text_style_id, + &self.text_styles, + "text style", + action, + )?; + validate_optional_reference(format.fill_id, &self.fills, "fill", action)?; + validate_optional_reference(format.border_id, &self.borders, "border", action)?; + validate_optional_reference( + format.number_format_id, + &self.number_formats, + "number format", + action, + )?; + validate_optional_reference( + format.base_cell_style_format_id, + &self.cell_formats, + "base cell format", + action, + )?; + Ok(()) + } + + fn validate_differential_format_references( + &self, + format: &SpreadsheetDifferentialFormat, + action: &str, + ) -> Result<(), SpreadsheetArtifactError> { + validate_optional_reference( + format.text_style_id, + &self.text_styles, + "text style", + action, + )?; + validate_optional_reference(format.fill_id, &self.fills, "fill", action)?; + validate_optional_reference(format.border_id, &self.borders, "border", action)?; + validate_optional_reference( + format.number_format_id, + &self.number_formats, + "number format", + action, + )?; + Ok(()) + } } impl SpreadsheetSheet { @@ -566,6 +647,23 @@ fn resolve_cell_format_recursive( }) } +fn validate_optional_reference( + id: Option, + map: &BTreeMap, + kind: &str, + action: &str, +) -> Result<(), SpreadsheetArtifactError> { + if let Some(id) = id + && !map.contains_key(&id) + { + return Err(SpreadsheetArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("{kind} `{id}` was not found"), + }); + } + Ok(()) +} + fn builtin_number_format_code(format_id: u32) -> Option { match format_id { 0 => Some("General".to_string()), @@ -578,3 +676,16 @@ fn builtin_number_format_code(format_id: u32) -> Option { _ => None, } } + +fn builtin_number_format_id(format_code: &str) -> Option { + match format_code { + "General" => Some(0), + "0" => Some(1), + "0.00" => Some(2), + "#,##0" => Some(3), + "#,##0.00" => Some(4), + "0%" => Some(9), + "0.00%" => Some(10), + _ => None, + } +} diff --git a/codex-rs/artifact-spreadsheet/src/tests.rs b/codex-rs/artifact-spreadsheet/src/tests.rs index 608ac7b392..5c7499ddf2 100644 --- a/codex-rs/artifact-spreadsheet/src/tests.rs +++ b/codex-rs/artifact-spreadsheet/src/tests.rs @@ -505,6 +505,1100 @@ fn xlsx_roundtrip_preserves_row_and_column_sizes() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("styled-roundtrip.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Styled Roundtrip".to_string())); + let style_index = { + let text_style_id = artifact.create_text_style( + SpreadsheetTextStyle { + bold: Some(true), + italic: Some(true), + underline: Some(true), + font_size: Some(14.0), + font_color: Some("#112233".to_string()), + font_family: Some("Aptos".to_string()), + typeface: Some("Aptos".to_string()), + ..Default::default() + }, + None, + false, + )?; + let fill_id = artifact.create_fill( + SpreadsheetFill { + solid_fill_color: Some("#FFEEAA".to_string()), + pattern_type: Some("solid".to_string()), + pattern_foreground_color: Some("#FFEEAA".to_string()), + pattern_background_color: Some("#221100".to_string()), + ..Default::default() + }, + None, + false, + )?; + let border_id = artifact.create_border( + crate::SpreadsheetBorder { + top: Some(crate::SpreadsheetBorderLine { + style: Some("thin".to_string()), + color: Some("#111111".to_string()), + }), + right: Some(crate::SpreadsheetBorderLine { + style: Some("dashed".to_string()), + color: Some("#222222".to_string()), + }), + bottom: Some(crate::SpreadsheetBorderLine { + style: Some("double".to_string()), + color: Some("#333333".to_string()), + }), + left: Some(crate::SpreadsheetBorderLine { + style: Some("hair".to_string()), + color: Some("#444444".to_string()), + }), + }, + None, + false, + )?; + let number_format_id = artifact.create_number_format( + SpreadsheetNumberFormat { + format_id: Some(4), + format_code: Some("#,##0.00".to_string()), + }, + None, + false, + )?; + artifact.create_cell_format( + SpreadsheetCellFormat { + text_style_id: Some(text_style_id), + fill_id: Some(fill_id), + border_id: Some(border_id), + alignment: Some(crate::SpreadsheetAlignment { + horizontal: Some("center".to_string()), + vertical: Some("bottom".to_string()), + }), + number_format_id: Some(number_format_id), + wrap_text: Some(true), + base_cell_style_format_id: None, + }, + None, + false, + )? + }; + + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::Float(42.5)), + )?; + sheet.set_style_index(&CellRange::parse("A1")?, style_index)?; + sheet.set_style_index(&CellRange::parse("B2")?, style_index)?; + artifact.export(&path)?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + + assert_eq!( + restored_sheet + .get_raw_cell(CellAddress::parse("B2")?) + .map(|cell| cell.style_index), + Some(style_index) + ); + assert_eq!( + restored.cell_format_summary(style_index), + Some(SpreadsheetCellFormatSummary { + style_index, + text_style: Some(SpreadsheetTextStyle { + bold: Some(true), + italic: Some(true), + underline: Some(true), + font_size: Some(14.0), + font_color: Some("#112233".to_string()), + text_alignment: None, + anchor: None, + vertical_text_orientation: None, + text_rotation: None, + paragraph_spacing: None, + bottom_inset: None, + left_inset: None, + right_inset: None, + top_inset: None, + font_family: Some("Aptos".to_string()), + font_scheme: None, + typeface: Some("Aptos".to_string()), + font_face: None, + }), + fill: Some(SpreadsheetFill { + solid_fill_color: Some("#FFEEAA".to_string()), + pattern_type: Some("solid".to_string()), + pattern_foreground_color: Some("#FFEEAA".to_string()), + pattern_background_color: Some("#221100".to_string()), + color_transforms: Vec::new(), + gradient_fill_type: None, + gradient_stops: Vec::new(), + gradient_kind: None, + angle: None, + scaled: None, + path_type: None, + fill_rectangle: None, + image_reference: None, + }), + border: Some(crate::SpreadsheetBorder { + top: Some(crate::SpreadsheetBorderLine { + style: Some("thin".to_string()), + color: Some("#111111".to_string()), + }), + right: Some(crate::SpreadsheetBorderLine { + style: Some("dashed".to_string()), + color: Some("#222222".to_string()), + }), + bottom: Some(crate::SpreadsheetBorderLine { + style: Some("double".to_string()), + color: Some("#333333".to_string()), + }), + left: Some(crate::SpreadsheetBorderLine { + style: Some("hair".to_string()), + color: Some("#444444".to_string()), + }), + }), + alignment: Some(crate::SpreadsheetAlignment { + horizontal: Some("center".to_string()), + vertical: Some("bottom".to_string()), + }), + number_format: Some(SpreadsheetNumberFormat { + format_id: Some(4), + format_code: Some("#,##0.00".to_string()), + }), + wrap_text: Some(true), + }) + ); + Ok(()) +} + +#[test] +fn xlsx_roundtrip_preserves_custom_sheet_metadata() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("metadata-roundtrip.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Metadata".to_string())); + { + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::String("Quarter".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B1")?, + Some(SpreadsheetCellValue::String("Revenue".to_string())), + )?; + sheet.set_value( + CellAddress::parse("C1")?, + Some(SpreadsheetCellValue::String("Forecast".to_string())), + )?; + sheet.set_value( + CellAddress::parse("A2")?, + Some(SpreadsheetCellValue::String("Q1".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B2")?, + Some(SpreadsheetCellValue::Integer(120)), + )?; + sheet.set_value( + CellAddress::parse("C2")?, + Some(SpreadsheetCellValue::Integer(118)), + )?; + sheet.create_table( + "test_create_table", + &CellRange::parse("A1:C2")?, + crate::SpreadsheetCreateTableOptions { + name: Some("Metrics".to_string()), + display_name: Some("Metrics".to_string()), + header_row_count: 1, + totals_row_count: 0, + style_name: Some("TableStyleMedium2".to_string()), + show_row_stripes: true, + ..Default::default() + }, + )?; + sheet.create_chart( + "test_create_chart", + crate::SpreadsheetChartType::Line, + Some("Sheet1".to_string()), + &CellRange::parse("A1:C2")?, + crate::SpreadsheetChartCreateOptions { + title: Some("Revenue vs Forecast".to_string()), + legend_position: Some(crate::SpreadsheetChartLegendPosition::Right), + ..Default::default() + }, + )?; + sheet.pivot_tables.push(crate::SpreadsheetPivotTable { + name: "Pivot1".to_string(), + cache_id: 7, + address: Some("E1:G5".to_string()), + row_fields: vec![crate::SpreadsheetPivotFieldReference { + field_index: 0, + field_name: Some("Quarter".to_string()), + }], + column_fields: Vec::new(), + page_fields: Vec::new(), + data_fields: vec![crate::SpreadsheetPivotDataField { + field_index: 1, + field_name: Some("Revenue".to_string()), + name: Some("Sum of Revenue".to_string()), + subtotal: Some("sum".to_string()), + }], + filters: Vec::new(), + pivot_fields: vec![crate::SpreadsheetPivotField { + index: 0, + name: Some("Quarter".to_string()), + axis: Some("axisRow".to_string()), + items: vec![crate::SpreadsheetPivotFieldItem { + item_type: Some("default".to_string()), + index: Some(0), + hidden: false, + }], + }], + style_name: Some("PivotStyleLight16".to_string()), + part_path: Some("xl/pivotTables/pivotTable1.xml".to_string()), + }); + } + artifact.add_conditional_format( + "test_add_conditional_format", + "Sheet1", + crate::SpreadsheetConditionalFormat { + id: 0, + range: "B2:C2".to_string(), + rule_type: crate::SpreadsheetConditionalFormatType::ColorScale, + operator: None, + formulas: Vec::new(), + text: None, + dxf_id: None, + stop_if_true: false, + priority: 0, + rank: None, + percent: None, + time_period: None, + above_average: None, + equal_average: None, + color_scale: Some(crate::SpreadsheetColorScale { + min_type: Some("min".to_string()), + mid_type: Some("percentile".to_string()), + max_type: Some("max".to_string()), + min_value: None, + mid_value: Some("50".to_string()), + max_value: None, + min_color: "#FFF2CC".to_string(), + mid_color: Some("#FFD966".to_string()), + max_color: "#BF9000".to_string(), + }), + data_bar: None, + icon_set: None, + }, + )?; + + artifact.export(&path)?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!(restored_sheet.tables.len(), 1); + assert_eq!(restored_sheet.tables[0].name, "Metrics".to_string()); + assert_eq!(restored_sheet.charts.len(), 1); + assert_eq!( + restored_sheet.charts[0].title, + Some("Revenue vs Forecast".to_string()) + ); + assert_eq!(restored_sheet.conditional_formats.len(), 1); + assert_eq!( + restored_sheet.conditional_formats[0].rule_type, + crate::SpreadsheetConditionalFormatType::ColorScale + ); + assert_eq!(restored_sheet.pivot_tables.len(), 1); + assert_eq!(restored_sheet.pivot_tables[0].cache_id, 7); + Ok(()) +} + +#[test] +fn xlsx_roundtrip_preserves_conditional_format_differential_styles() +-> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("conditional-dxf-roundtrip.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Conditional DXF".to_string())); + let fill_id = artifact.create_fill( + SpreadsheetFill { + solid_fill_color: Some("#FFCCCC".to_string()), + pattern_type: Some("solid".to_string()), + pattern_foreground_color: Some("#FFCCCC".to_string()), + ..Default::default() + }, + None, + false, + )?; + let dxf_id = artifact.create_differential_format(crate::SpreadsheetDifferentialFormat { + text_style_id: None, + fill_id: Some(fill_id), + border_id: None, + alignment: None, + number_format_id: None, + wrap_text: None, + })?; + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::Integer(12)), + )?; + artifact.add_conditional_format( + "test_add_conditional_format", + "Sheet1", + crate::SpreadsheetConditionalFormat { + id: 0, + range: "A1:A1".to_string(), + rule_type: crate::SpreadsheetConditionalFormatType::Expression, + operator: None, + formulas: vec!["A1>10".to_string()], + text: None, + dxf_id: Some(dxf_id), + stop_if_true: true, + priority: 0, + rank: None, + percent: None, + time_period: None, + above_average: None, + equal_average: None, + color_scale: None, + data_bar: None, + icon_set: None, + }, + )?; + + artifact.export(&path)?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!(restored_sheet.conditional_formats.len(), 1); + assert_eq!( + restored_sheet.conditional_formats[0], + crate::SpreadsheetConditionalFormat { + id: 1, + range: "A1:A1".to_string(), + rule_type: crate::SpreadsheetConditionalFormatType::Expression, + operator: None, + formulas: vec!["A1>10".to_string()], + text: None, + dxf_id: Some(dxf_id), + stop_if_true: true, + priority: 1, + rank: None, + percent: None, + time_period: None, + above_average: None, + equal_average: None, + color_scale: None, + data_bar: None, + icon_set: None, + } + ); + assert_eq!( + restored.get_differential_format(dxf_id), + Some(&crate::SpreadsheetDifferentialFormat { + text_style_id: None, + fill_id: Some(fill_id), + border_id: None, + alignment: None, + number_format_id: None, + wrap_text: None, + }) + ); + Ok(()) +} + +#[test] +fn native_xlsx_table_import_reconstructs_table_metadata() -> Result<(), Box> +{ + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("native-table.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Native Table".to_string())); + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::String("Quarter".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B1")?, + Some(SpreadsheetCellValue::String("Revenue".to_string())), + )?; + sheet.set_value( + CellAddress::parse("A2")?, + Some(SpreadsheetCellValue::String("Q1".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B2")?, + Some(SpreadsheetCellValue::Integer(120)), + )?; + artifact.export(&path)?; + + let sheet_xml = read_zip_entry_for_test(&path, "xl/worksheets/sheet1.xml")?; + rewrite_xlsx_entries( + &path, + &BTreeMap::from([ + ( + "[Content_Types].xml".to_string(), + inject_before( + &read_zip_entry_for_test(&path, "[Content_Types].xml")?, + "", + r#""#, + ), + ), + ( + "xl/worksheets/sheet1.xml".to_string(), + inject_before( + &sheet_xml, + "", + r#""#, + ), + ), + ( + "xl/worksheets/_rels/sheet1.xml.rels".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/tables/table1.xml".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#"
"# + ) + .to_string(), + ), + ]), + )?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!(restored_sheet.tables.len(), 1); + assert_eq!(restored_sheet.tables[0].name, "SalesTable".to_string()); + assert_eq!( + restored_sheet.tables[0].style_name, + Some("TableStyleMedium2".to_string()) + ); + assert_eq!( + restored_sheet.tables[0].columns, + vec![ + crate::SpreadsheetTableColumn { + id: 1, + name: "Quarter".to_string(), + totals_row_label: None, + totals_row_function: None, + }, + crate::SpreadsheetTableColumn { + id: 2, + name: "Revenue".to_string(), + totals_row_label: None, + totals_row_function: Some("sum".to_string()), + }, + ] + ); + Ok(()) +} + +#[test] +fn native_xlsx_conditional_format_import_reconstructs_rules_and_differential_styles() +-> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("native-conditional-format.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Native Conditional".to_string())); + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::Integer(5)), + )?; + sheet.set_value( + CellAddress::parse("A2")?, + Some(SpreadsheetCellValue::Integer(15)), + )?; + sheet.set_value( + CellAddress::parse("B1")?, + Some(SpreadsheetCellValue::Integer(1)), + )?; + sheet.set_value( + CellAddress::parse("B2")?, + Some(SpreadsheetCellValue::Integer(10)), + )?; + artifact.export(&path)?; + + let styles_xml = read_zip_entry_for_test(&path, "xl/styles.xml")?; + let sheet_xml = read_zip_entry_for_test(&path, "xl/worksheets/sheet1.xml")?; + rewrite_xlsx_entries( + &path, + &BTreeMap::from([ + ( + "xl/styles.xml".to_string(), + styles_xml.replacen( + r#""#, + concat!( + r#""#, + r#""#, + r#""# + ), + 1, + ), + ), + ( + "xl/worksheets/sheet1.xml".to_string(), + inject_before( + &sheet_xml, + "", + concat!( + r#""#, + r#"A1>10"#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""# + ), + ), + ), + ]), + )?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!( + restored_sheet.conditional_formats, + vec![ + crate::SpreadsheetConditionalFormat { + id: 1, + range: "A1:A2".to_string(), + rule_type: crate::SpreadsheetConditionalFormatType::Expression, + operator: None, + formulas: vec!["A1>10".to_string()], + text: None, + dxf_id: Some(1), + stop_if_true: true, + priority: 1, + rank: None, + percent: None, + time_period: None, + above_average: None, + equal_average: None, + color_scale: None, + data_bar: None, + icon_set: None, + }, + crate::SpreadsheetConditionalFormat { + id: 2, + range: "B1:B2".to_string(), + rule_type: crate::SpreadsheetConditionalFormatType::ColorScale, + operator: None, + formulas: Vec::new(), + text: None, + dxf_id: None, + stop_if_true: false, + priority: 2, + rank: None, + percent: None, + time_period: None, + above_average: None, + equal_average: None, + color_scale: Some(crate::SpreadsheetColorScale { + min_type: Some("min".to_string()), + mid_type: None, + max_type: Some("max".to_string()), + min_value: None, + mid_value: None, + max_value: None, + min_color: "#FF7128".to_string(), + mid_color: None, + max_color: "#63BE7B".to_string(), + }), + data_bar: None, + icon_set: None, + }, + ] + ); + assert_eq!( + restored.get_differential_format(1), + Some(&crate::SpreadsheetDifferentialFormat { + text_style_id: None, + fill_id: Some(1), + border_id: None, + alignment: None, + number_format_id: None, + wrap_text: None, + }) + ); + Ok(()) +} + +#[test] +fn native_xlsx_chart_import_reconstructs_chart_metadata() -> Result<(), Box> +{ + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("native-chart.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Native Chart".to_string())); + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::String("Quarter".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B1")?, + Some(SpreadsheetCellValue::String("Revenue".to_string())), + )?; + sheet.set_value( + CellAddress::parse("C1")?, + Some(SpreadsheetCellValue::String("Forecast".to_string())), + )?; + sheet.set_value( + CellAddress::parse("A2")?, + Some(SpreadsheetCellValue::String("Q1".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B2")?, + Some(SpreadsheetCellValue::Integer(120)), + )?; + sheet.set_value( + CellAddress::parse("C2")?, + Some(SpreadsheetCellValue::Integer(118)), + )?; + sheet.set_value( + CellAddress::parse("A3")?, + Some(SpreadsheetCellValue::String("Q2".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B3")?, + Some(SpreadsheetCellValue::Integer(134)), + )?; + sheet.set_value( + CellAddress::parse("C3")?, + Some(SpreadsheetCellValue::Integer(130)), + )?; + artifact.export(&path)?; + + rewrite_xlsx_entries( + &path, + &BTreeMap::from([ + ( + "xl/worksheets/sheet1.xml".to_string(), + inject_before( + &read_zip_entry_for_test(&path, "xl/worksheets/sheet1.xml")?, + "", + r#""#, + ), + ), + ( + "xl/worksheets/_rels/sheet1.xml.rels".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/drawings/drawing1.xml".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/drawings/_rels/drawing1.xml.rels".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/charts/chart1.xml".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""#, + r#"Quarterly Revenue"#, + r#""#, + r#"RevenueSheet1!$A$1:$A$3Sheet1!$B$1:$B$3"#, + r#"ForecastSheet1!$A$1:$A$3Sheet1!$C$1:$C$3"#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ]), + )?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!(restored_sheet.charts.len(), 1); + let chart = &restored_sheet.charts[0]; + assert_eq!(chart.chart_type, crate::SpreadsheetChartType::Line); + assert_eq!(chart.title, Some("Quarterly Revenue".to_string())); + assert_eq!(chart.source_sheet_name, Some("Sheet1".to_string())); + assert_eq!(chart.source_range, Some("A1:C3".to_string())); + assert_eq!( + chart.legend.position, + crate::SpreadsheetChartLegendPosition::Right + ); + assert_eq!(chart.series.len(), 2); + assert_eq!(chart.series[0].name, Some("Revenue".to_string())); + assert_eq!(chart.series[1].value_range, "C1:C3".to_string()); + Ok(()) +} + +#[test] +fn native_xlsx_pivot_import_reconstructs_pivot_metadata() -> Result<(), Box> +{ + let temp_dir = tempfile::tempdir()?; + let path = temp_dir.path().join("native-pivot.xlsx"); + + let mut artifact = SpreadsheetArtifact::new(Some("Native Pivot".to_string())); + let sheet = artifact.create_sheet("Sheet1".to_string())?; + sheet.set_value( + CellAddress::parse("A1")?, + Some(SpreadsheetCellValue::String("Quarter".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B1")?, + Some(SpreadsheetCellValue::String("Revenue".to_string())), + )?; + sheet.set_value( + CellAddress::parse("A2")?, + Some(SpreadsheetCellValue::String("Q1".to_string())), + )?; + sheet.set_value( + CellAddress::parse("B2")?, + Some(SpreadsheetCellValue::Integer(120)), + )?; + artifact.export(&path)?; + + rewrite_xlsx_entries( + &path, + &BTreeMap::from([ + ( + "xl/workbook.xml".to_string(), + inject_before( + &read_zip_entry_for_test(&path, "xl/workbook.xml")?, + "", + r#""#, + ), + ), + ( + "xl/_rels/workbook.xml.rels".to_string(), + inject_before( + &read_zip_entry_for_test(&path, "xl/_rels/workbook.xml.rels")?, + "", + r#""#, + ), + ), + ( + "xl/worksheets/sheet1.xml".to_string(), + inject_before( + &read_zip_entry_for_test(&path, "xl/worksheets/sheet1.xml")?, + "", + r#""#, + ), + ), + ( + "xl/worksheets/_rels/sheet1.xml.rels".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/pivotCache/pivotCacheDefinition1.xml".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ( + "xl/pivotTables/pivotTable1.xml".to_string(), + concat!( + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""#, + r#""# + ) + .to_string(), + ), + ]), + )?; + + let restored = SpreadsheetArtifact::from_source_file(&path, None)?; + let restored_sheet = restored.get_sheet(Some("Sheet1"), None).expect("sheet"); + assert_eq!(restored_sheet.pivot_tables.len(), 1); + let pivot = &restored_sheet.pivot_tables[0]; + assert_eq!(pivot.name, "SalesPivot".to_string()); + assert_eq!(pivot.cache_id, 7); + assert_eq!(pivot.address, Some("E3:G8".to_string())); + assert_eq!( + pivot.row_fields, + vec![crate::SpreadsheetPivotFieldReference { + field_index: 0, + field_name: Some("Quarter".to_string()), + }] + ); + assert_eq!( + pivot.data_fields, + vec![crate::SpreadsheetPivotDataField { + field_index: 1, + field_name: Some("Revenue".to_string()), + name: Some("Sum of Revenue".to_string()), + subtotal: Some("sum".to_string()), + }] + ); + assert_eq!(pivot.style_name, Some("PivotStyleLight16".to_string())); + Ok(()) +} + +#[test] +fn clearing_range_formulas_removes_stale_cached_values() -> Result<(), Box> { + let mut sheet = SpreadsheetSheet::new("Sheet1".to_string()); + let range = CellRange::parse("A1:B1")?; + + sheet.set_values_matrix( + &range, + &[vec![ + Some(SpreadsheetCellValue::Integer(1)), + Some(SpreadsheetCellValue::Integer(2)), + ]], + )?; + sheet.set_formulas_matrix(&range, &[vec![None, None]])?; + assert_eq!(sheet.cells, BTreeMap::new()); + + sheet.set_range_to_value(&range, Some(SpreadsheetCellValue::Integer(9)))?; + sheet.set_range_to_formula(&range, None)?; + assert_eq!(sheet.cells, BTreeMap::new()); + Ok(()) +} + +#[test] +fn style_validation_rejects_missing_references_and_unknown_style_indices() +-> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = SpreadsheetArtifactManager::default(); + let created = manager.execute( + SpreadsheetArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Style Validation" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + + manager.execute( + SpreadsheetArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "create_sheet".to_string(), + args: serde_json::json!({ "name": "Sheet1" }), + }, + temp_dir.path(), + )?; + + let invalid_cell_format = manager.execute( + SpreadsheetArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "create_cell_format".to_string(), + args: serde_json::json!({ + "format": { + "text_style_id": 999 + } + }), + }, + temp_dir.path(), + ); + assert_eq!( + invalid_cell_format.unwrap_err().to_string(), + "invalid args for action `create_cell_format`: text style `999` was not found" + ); + + let invalid_dxf = manager.execute( + SpreadsheetArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "create_differential_format".to_string(), + args: serde_json::json!({ + "format": { + "fill_id": 999 + } + }), + }, + temp_dir.path(), + ); + assert_eq!( + invalid_dxf.unwrap_err().to_string(), + "invalid args for action `create_differential_format`: fill `999` was not found" + ); + + let invalid_style_assignment = manager.execute( + SpreadsheetArtifactRequest { + artifact_id: Some(artifact_id), + action: "set_cell_style".to_string(), + args: serde_json::json!({ + "sheet_name": "Sheet1", + "address": "A1", + "style_index": 999 + }), + }, + temp_dir.path(), + ); + assert_eq!( + invalid_style_assignment.unwrap_err().to_string(), + "invalid args for action `set_cell_style`: style index `999` was not found" + ); + Ok(()) +} + +#[test] +fn builtin_number_formats_are_normalized_consistently() -> Result<(), Box> { + let mut artifact = SpreadsheetArtifact::new(Some("Number Formats".to_string())); + + let by_id = artifact.create_number_format( + SpreadsheetNumberFormat { + format_id: Some(4), + format_code: Some("not-the-builtin-code".to_string()), + }, + None, + false, + )?; + assert_eq!( + artifact.get_number_format(by_id), + Some(&SpreadsheetNumberFormat { + format_id: Some(4), + format_code: Some("#,##0.00".to_string()), + }) + ); + + let by_code = artifact.create_number_format( + SpreadsheetNumberFormat { + format_id: None, + format_code: Some("0.00%".to_string()), + }, + None, + false, + )?; + assert_eq!( + artifact.get_number_format(by_code), + Some(&SpreadsheetNumberFormat { + format_id: Some(10), + format_code: Some("0.00%".to_string()), + }) + ); + Ok(()) +} + +fn read_zip_entry_for_test( + path: &std::path::Path, + entry_name: &str, +) -> Result> { + let file = std::fs::File::open(path)?; + let mut archive = zip::ZipArchive::new(file)?; + let mut entry = archive.by_name(entry_name)?; + let mut contents = String::new(); + use std::io::Read as _; + entry.read_to_string(&mut contents)?; + Ok(contents) +} + +fn rewrite_xlsx_entries( + path: &std::path::Path, + replacements: &BTreeMap, +) -> Result<(), Box> { + let temp_path = path.with_extension("rewritten.xlsx"); + let source = std::fs::File::open(path)?; + let mut archive = zip::ZipArchive::new(source)?; + let target = std::fs::File::create(&temp_path)?; + let mut writer = zip::ZipWriter::new(target); + let options = zip::write::SimpleFileOptions::default(); + let mut seen = std::collections::BTreeSet::new(); + + for index in 0..archive.len() { + let mut entry = archive.by_index(index)?; + let name = entry.name().to_string(); + seen.insert(name.clone()); + writer.start_file(&name, options)?; + if let Some(replacement) = replacements.get(&name) { + use std::io::Write as _; + writer.write_all(replacement.as_bytes())?; + } else { + std::io::copy(&mut entry, &mut writer)?; + } + } + + for (name, replacement) in replacements { + if seen.contains(name) { + continue; + } + writer.start_file(name, options)?; + use std::io::Write as _; + writer.write_all(replacement.as_bytes())?; + } + + writer.finish()?; + std::fs::rename(temp_path, path)?; + Ok(()) +} + +fn inject_before(original: &str, marker: &str, addition: &str) -> String { + original.replacen(marker, &format!("{addition}{marker}"), 1) +} + #[test] fn manager_supports_bulk_sizes_and_row_heights() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; diff --git a/codex-rs/artifact-spreadsheet/src/xlsx.rs b/codex-rs/artifact-spreadsheet/src/xlsx.rs index 5175f3b51e..1156501843 100644 --- a/codex-rs/artifact-spreadsheet/src/xlsx.rs +++ b/codex-rs/artifact-spreadsheet/src/xlsx.rs @@ -12,11 +12,22 @@ use zip::write::SimpleFileOptions; use crate::CellAddress; use crate::CellRange; +use crate::SpreadsheetAlignment; use crate::SpreadsheetArtifact; use crate::SpreadsheetArtifactError; +use crate::SpreadsheetBorder; +use crate::SpreadsheetBorderLine; use crate::SpreadsheetCell; +use crate::SpreadsheetCellFormat; use crate::SpreadsheetCellValue; +use crate::SpreadsheetFill; +use crate::SpreadsheetFillRectangle; +use crate::SpreadsheetGradientStop; +use crate::SpreadsheetNumberFormat; use crate::SpreadsheetSheet; +use crate::SpreadsheetTextStyle; + +const CODEX_METADATA_PATH: &str = "customXml/item1.xml"; pub(crate) fn write_xlsx( artifact: &mut SpreadsheetArtifact, @@ -42,11 +53,13 @@ pub(crate) fn write_xlsx( path: path.to_path_buf(), message: error.to_string(), })?; - zip.write_all(content_types_xml(sheet_count).as_bytes()) - .map_err(|error| SpreadsheetArtifactError::ExportFailed { - path: path.to_path_buf(), - message: error.to_string(), - })?; + zip.write_all( + content_types_xml(sheet_count, artifact_has_custom_metadata(artifact)).as_bytes(), + ) + .map_err(|error| SpreadsheetArtifactError::ExportFailed { + path: path.to_path_buf(), + message: error.to_string(), + })?; zip.add_directory("_rels/", options).map_err(|error| { SpreadsheetArtifactError::ExportFailed { @@ -139,6 +152,25 @@ pub(crate) fn write_xlsx( message: error.to_string(), })?; + if artifact_has_custom_metadata(artifact) { + zip.add_directory("customXml/", options).map_err(|error| { + SpreadsheetArtifactError::ExportFailed { + path: path.to_path_buf(), + message: error.to_string(), + } + })?; + zip.start_file(CODEX_METADATA_PATH, options) + .map_err(|error| SpreadsheetArtifactError::ExportFailed { + path: path.to_path_buf(), + message: error.to_string(), + })?; + zip.write_all(codex_metadata_xml(artifact)?.as_bytes()) + .map_err(|error| SpreadsheetArtifactError::ExportFailed { + path: path.to_path_buf(), + message: error.to_string(), + })?; + } + zip.add_directory("xl/worksheets/", options) .map_err(|error| SpreadsheetArtifactError::ExportFailed { path: path.to_path_buf(), @@ -211,6 +243,21 @@ pub(crate) fn import_xlsx( } else { None }; + let parsed_styles = if archive.by_name("xl/styles.xml").is_ok() { + parse_styles(&read_zip_entry(&mut archive, "xl/styles.xml", path)?)? + } else { + ParsedStyles::default() + }; + let pivot_caches = parse_pivot_caches(&mut archive, path, &workbook_xml, &workbook_rels)?; + let custom_metadata = if archive.by_name(CODEX_METADATA_PATH).is_ok() { + Some(parse_codex_metadata(&read_zip_entry( + &mut archive, + CODEX_METADATA_PATH, + path, + )?)?) + } else { + None + }; let relationships = parse_relationships(&workbook_rels)?; let sheets = parse_sheet_definitions(&workbook_xml)? @@ -242,10 +289,37 @@ pub(crate) fn import_xlsx( artifact.artifact_id = artifact_id; } artifact.sheets.clear(); + artifact.text_styles = parsed_styles.text_styles; + artifact.fills = parsed_styles.fills; + artifact.borders = parsed_styles.borders; + artifact.number_formats = parsed_styles.number_formats; + artifact.cell_formats = parsed_styles.cell_formats; + artifact.differential_formats = parsed_styles.differential_formats; + let dxf_id_map = parsed_styles.dxf_id_map; + + if let Some(metadata) = custom_metadata.as_ref() + && !metadata.differential_formats.is_empty() + { + artifact.differential_formats = metadata.differential_formats.clone(); + } for (name, target) in sheets { let xml = read_zip_entry(&mut archive, &target, path)?; - let sheet = parse_sheet(&name, &xml, shared_strings.as_deref())?; + let mut sheet = parse_sheet(&name, &xml, shared_strings.as_deref())?; + sheet.charts = parse_native_charts(&mut archive, path, &target, &xml, &name)?; + sheet.tables = parse_native_tables(&mut archive, path, &target, &xml)?; + sheet.conditional_formats = parse_native_conditional_formats(&xml, &dxf_id_map)?; + sheet.pivot_tables = + parse_native_pivot_tables(&mut archive, path, &target, &xml, &pivot_caches)?; + if let Some(metadata) = custom_metadata + .as_ref() + .and_then(|entry| entry.sheets.get(&name)) + { + sheet.charts = metadata.charts.clone(); + sheet.tables = metadata.tables.clone(); + sheet.conditional_formats = metadata.conditional_formats.clone(); + sheet.pivot_tables = metadata.pivot_tables.clone(); + } artifact.sheets.push(sheet); } @@ -375,50 +449,27 @@ fn parse_sheet( } } - let row_regex = Regex::new(r#"(?s)]*)>(.*?)"#).map_err(|error| { - SpreadsheetArtifactError::Serialization { - message: error.to_string(), - } - })?; - let cell_regex = Regex::new(r#"(?s)]*)>(.*?)"#).map_err(|error| { - SpreadsheetArtifactError::Serialization { - message: error.to_string(), - } - })?; - for row_captures in row_regex.captures_iter(xml) { - let row_attributes = row_captures - .get(1) - .map(|value| value.as_str()) - .unwrap_or_default(); + for (row_attributes, row_body) in child_tags(xml, "row")? { if let Some(row_index) = - extract_attribute(row_attributes, "r").and_then(|value| value.parse::().ok()) + extract_attribute(&row_attributes, "r").and_then(|value| value.parse::().ok()) && let Some(height) = - extract_attribute(row_attributes, "ht").and_then(|value| value.parse::().ok()) + extract_attribute(&row_attributes, "ht").and_then(|value| value.parse::().ok()) && row_index > 0 && height > 0.0 { sheet.row_heights.insert(row_index, height); } - let Some(row_body) = row_captures.get(2).map(|value| value.as_str()) else { - continue; - }; - for cell_captures in cell_regex.captures_iter(row_body) { - let Some(attributes) = cell_captures.get(1).map(|value| value.as_str()) else { - continue; - }; - let Some(body) = cell_captures.get(2).map(|value| value.as_str()) else { - continue; - }; - let Some(address) = extract_attribute(attributes, "r") else { + for (attributes, body) in child_tags(&row_body, "c")? { + let Some(address) = extract_attribute(&attributes, "r") else { continue; }; let address = CellAddress::parse(&address)?; - let style_index = extract_attribute(attributes, "s") + let style_index = extract_attribute(&attributes, "s") .and_then(|value| value.parse::().ok()) .unwrap_or(0); - let cell_type = extract_attribute(attributes, "t").unwrap_or_default(); - let formula = first_tag_text(body, "f").map(|value| format!("={value}")); - let value = parse_cell_value(body, &cell_type, shared_strings)?; + let cell_type = extract_attribute(&attributes, "t").unwrap_or_default(); + let formula = first_tag_text(&body, "f").map(|value| format!("={value}")); + let value = parse_cell_value(&body, &cell_type, shared_strings)?; let cell = SpreadsheetCell { value, @@ -454,7 +505,10 @@ fn parse_cell_value( cell_type: &str, shared_strings: Option<&[String]>, ) -> Result, SpreadsheetArtifactError> { - let inline_text = first_tag_text(body, "t").map(|value| xml_unescape(&value)); + let inline_text = (!body.is_empty()) + .then(|| all_text_nodes(body)) + .transpose()? + .filter(|value| !value.is_empty()); let raw_value = first_tag_text(body, "v").map(|value| xml_unescape(&value)); let parsed = match cell_type { @@ -482,13 +536,1385 @@ fn parse_cell_value( Ok(parsed) } -fn content_types_xml(sheet_count: usize) -> String { +fn parse_native_tables( + archive: &mut ZipArchive, + workbook_path: &Path, + sheet_part: &str, + sheet_xml: &str, +) -> Result, SpreadsheetArtifactError> { + let rels_path = sheet_relationships_path(sheet_part)?; + if archive.by_name(&rels_path).is_err() { + return Ok(Vec::new()); + } + + let relationships = parse_relationships(&read_zip_entry(archive, &rels_path, workbook_path)?)?; + child_tags(sheet_xml, "tablePart")? + .into_iter() + .filter_map(|(attributes, _)| { + extract_attribute(&attributes, "r:id").or_else(|| extract_attribute(&attributes, "id")) + }) + .filter_map(|relationship_id| { + relationships.get(&relationship_id).map(|target| { + normalize_relationship_target(sheet_part, target) + .map(|normalized| (relationship_id, normalized)) + }) + }) + .map(|entry| { + let (_, table_path) = entry?; + let xml = read_zip_entry(archive, &table_path, workbook_path)?; + parse_native_table(&xml) + }) + .collect() +} + +fn parse_native_conditional_formats( + sheet_xml: &str, + dxf_id_map: &BTreeMap, +) -> Result, SpreadsheetArtifactError> { + let mut formats = Vec::new(); + let mut next_id = 1; + + for (attributes, body) in child_tags(sheet_xml, "conditionalFormatting")? { + let ranges = extract_attribute(&attributes, "sqref") + .unwrap_or_default() + .split_whitespace() + .map(normalize_a1_reference) + .collect::>(); + if ranges.is_empty() { + continue; + } + + for (rule_attributes, rule_body) in child_tags(&body, "cfRule")? { + let Some(rule_type) = extract_attribute(&rule_attributes, "type") + .as_deref() + .and_then(parse_conditional_format_type) + else { + continue; + }; + let formulas = child_tags(&rule_body, "formula")? + .into_iter() + .map(|(_, formula)| xml_unescape(formula.trim())) + .filter(|formula| !formula.is_empty()) + .collect::>(); + let color_scale = (rule_type == crate::SpreadsheetConditionalFormatType::ColorScale) + .then(|| parse_color_scale_rule(&rule_body)) + .transpose()? + .flatten(); + let data_bar = (rule_type == crate::SpreadsheetConditionalFormatType::DataBar) + .then(|| parse_data_bar_rule(&rule_body)) + .transpose()? + .flatten(); + let icon_set = (rule_type == crate::SpreadsheetConditionalFormatType::IconSet) + .then(|| parse_icon_set_rule(&rule_body)) + .transpose()? + .flatten(); + let dxf_id = extract_attribute(&rule_attributes, "dxfId") + .and_then(|value| value.parse::().ok()) + .and_then(|value| dxf_id_map.get(&value).copied()); + if dxf_id.is_none() && color_scale.is_none() && data_bar.is_none() && icon_set.is_none() + { + continue; + } + let stop_if_true = extract_attribute(&rule_attributes, "stopIfTrue") + .and_then(|value| parse_xlsx_bool(&value)) + .unwrap_or(false); + let priority = extract_attribute(&rule_attributes, "priority") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0); + let rank = extract_attribute(&rule_attributes, "rank") + .and_then(|value| value.parse::().ok()); + let percent = extract_attribute(&rule_attributes, "percent") + .and_then(|value| parse_xlsx_bool(&value)); + let time_period = extract_attribute(&rule_attributes, "timePeriod"); + let above_average = extract_attribute(&rule_attributes, "aboveAverage") + .and_then(|value| parse_xlsx_bool(&value)); + let equal_average = extract_attribute(&rule_attributes, "equalAverage") + .and_then(|value| parse_xlsx_bool(&value)); + let text = extract_attribute(&rule_attributes, "text"); + + for range in &ranges { + formats.push(crate::SpreadsheetConditionalFormat { + id: next_id, + range: range.clone(), + rule_type, + operator: extract_attribute(&rule_attributes, "operator"), + formulas: formulas.clone(), + text: text.clone(), + dxf_id, + stop_if_true, + priority: if priority == 0 { next_id } else { priority }, + rank, + percent, + time_period: time_period.clone(), + above_average, + equal_average, + color_scale: color_scale.clone(), + data_bar: data_bar.clone(), + icon_set: icon_set.clone(), + }); + next_id += 1; + } + } + } + + Ok(formats) +} + +fn parse_conditional_format_type(value: &str) -> Option { + match value { + "expression" => Some(crate::SpreadsheetConditionalFormatType::Expression), + "cellIs" => Some(crate::SpreadsheetConditionalFormatType::CellIs), + "colorScale" => Some(crate::SpreadsheetConditionalFormatType::ColorScale), + "dataBar" => Some(crate::SpreadsheetConditionalFormatType::DataBar), + "iconSet" => Some(crate::SpreadsheetConditionalFormatType::IconSet), + "top10" => Some(crate::SpreadsheetConditionalFormatType::Top10), + "uniqueValues" => Some(crate::SpreadsheetConditionalFormatType::UniqueValues), + "duplicateValues" => Some(crate::SpreadsheetConditionalFormatType::DuplicateValues), + "containsText" => Some(crate::SpreadsheetConditionalFormatType::ContainsText), + "notContainsText" => Some(crate::SpreadsheetConditionalFormatType::NotContainsText), + "beginsWith" => Some(crate::SpreadsheetConditionalFormatType::BeginsWith), + "endsWith" => Some(crate::SpreadsheetConditionalFormatType::EndsWith), + "containsBlanks" => Some(crate::SpreadsheetConditionalFormatType::ContainsBlanks), + "notContainsBlanks" => Some(crate::SpreadsheetConditionalFormatType::NotContainsBlanks), + "containsErrors" => Some(crate::SpreadsheetConditionalFormatType::ContainsErrors), + "notContainsErrors" => Some(crate::SpreadsheetConditionalFormatType::NotContainsErrors), + "timePeriod" => Some(crate::SpreadsheetConditionalFormatType::TimePeriod), + "aboveAverage" => Some(crate::SpreadsheetConditionalFormatType::AboveAverage), + _ => None, + } +} + +fn parse_color_scale_rule( + rule_body: &str, +) -> Result, SpreadsheetArtifactError> { + let Some(color_scale) = first_tag_text(rule_body, "colorScale") else { + return Ok(None); + }; + let thresholds = child_tags(&color_scale, "cfvo")? + .into_iter() + .map(|(attributes, _)| { + ( + extract_attribute(&attributes, "type"), + extract_attribute(&attributes, "val"), + ) + }) + .collect::>(); + let colors = child_tags(&color_scale, "color")? + .into_iter() + .filter_map(|(attributes, _)| parse_color_value(&attributes)) + .collect::>(); + if thresholds.len() < 2 || colors.len() < 2 { + return Ok(None); + } + let mid = (thresholds.len() == 3 && colors.len() == 3).then_some(1); + Ok(Some(crate::SpreadsheetColorScale { + min_type: thresholds.first().and_then(|(kind, _)| kind.clone()), + mid_type: mid.and_then(|index| thresholds.get(index).and_then(|(kind, _)| kind.clone())), + max_type: thresholds.last().and_then(|(kind, _)| kind.clone()), + min_value: thresholds.first().and_then(|(_, value)| value.clone()), + mid_value: mid.and_then(|index| thresholds.get(index).and_then(|(_, value)| value.clone())), + max_value: thresholds.last().and_then(|(_, value)| value.clone()), + min_color: colors.first().cloned().unwrap_or_default(), + mid_color: mid.and_then(|index| colors.get(index).cloned()), + max_color: colors.last().cloned().unwrap_or_default(), + })) +} + +fn parse_data_bar_rule( + rule_body: &str, +) -> Result, SpreadsheetArtifactError> { + let Some(data_bar_body) = first_tag_text(rule_body, "dataBar") else { + return Ok(None); + }; + let Some(data_bar_attributes) = first_tag_attributes(rule_body, "dataBar") else { + return Ok(None); + }; + let Some(color) = first_tag_attributes(&data_bar_body, "color") + .and_then(|attributes| parse_color_value(&attributes)) + else { + return Ok(None); + }; + Ok(Some(crate::SpreadsheetDataBar { + color, + min_length: extract_attribute(&data_bar_attributes, "minLength") + .and_then(|value| value.parse::().ok()), + max_length: extract_attribute(&data_bar_attributes, "maxLength") + .and_then(|value| value.parse::().ok()), + show_value: extract_attribute(&data_bar_attributes, "showValue") + .and_then(|value| parse_xlsx_bool(&value)), + })) +} + +fn parse_icon_set_rule( + rule_body: &str, +) -> Result, SpreadsheetArtifactError> { + let Some(icon_set_attributes) = first_tag_attributes(rule_body, "iconSet") else { + return Ok(None); + }; + let Some(style) = extract_attribute(&icon_set_attributes, "iconSet") else { + return Ok(None); + }; + Ok(Some(crate::SpreadsheetIconSet { + style, + show_value: extract_attribute(&icon_set_attributes, "showValue") + .and_then(|value| parse_xlsx_bool(&value)), + reverse_order: extract_attribute(&icon_set_attributes, "reverse") + .and_then(|value| parse_xlsx_bool(&value)), + })) +} + +fn parse_native_charts( + archive: &mut ZipArchive, + workbook_path: &Path, + sheet_part: &str, + _sheet_xml: &str, + sheet_name: &str, +) -> Result, SpreadsheetArtifactError> { + let rels_path = sheet_relationships_path(sheet_part)?; + if archive.by_name(&rels_path).is_err() { + return Ok(Vec::new()); + } + + let relationships = parse_relationships(&read_zip_entry(archive, &rels_path, workbook_path)?)?; + let drawing_targets = relationships + .values() + .filter(|target| target.contains("drawing")) + .map(|target| normalize_relationship_target(sheet_part, target)) + .collect::, _>>()?; + + let mut charts = Vec::new(); + for drawing_target in drawing_targets { + let drawing_xml = read_zip_entry(archive, &drawing_target, workbook_path)?; + let drawing_rels_path = sheet_relationships_path(&drawing_target)?; + let drawing_relationships = if archive.by_name(&drawing_rels_path).is_ok() { + parse_relationships(&read_zip_entry(archive, &drawing_rels_path, workbook_path)?)? + } else { + BTreeMap::new() + }; + + for (relationship_id, _) in child_tags_ns(&drawing_xml, "chart")? { + let Some(chart_rel_id) = extract_attribute(&relationship_id, "r:id") + .or_else(|| extract_attribute(&relationship_id, "id")) + else { + continue; + }; + let Some(chart_target) = drawing_relationships.get(&chart_rel_id) else { + continue; + }; + let chart_path = normalize_relationship_target(&drawing_target, chart_target)?; + let chart_xml = read_zip_entry(archive, &chart_path, workbook_path)?; + charts.push(parse_native_chart( + &chart_xml, + &chart_path, + sheet_name, + charts.len() as u32 + 1, + )?); + } + } + Ok(charts) +} + +fn parse_native_pivot_tables( + archive: &mut ZipArchive, + workbook_path: &Path, + sheet_part: &str, + sheet_xml: &str, + pivot_caches: &BTreeMap, +) -> Result, SpreadsheetArtifactError> { + let rels_path = sheet_relationships_path(sheet_part)?; + if archive.by_name(&rels_path).is_err() { + return Ok(Vec::new()); + } + + let relationships = parse_relationships(&read_zip_entry(archive, &rels_path, workbook_path)?)?; + child_tags_ns(sheet_xml, "pivotTablePart")? + .into_iter() + .filter_map(|(attributes, _)| { + extract_attribute(&attributes, "r:id").or_else(|| extract_attribute(&attributes, "id")) + }) + .filter_map(|relationship_id| { + relationships.get(&relationship_id).map(|target| { + normalize_relationship_target(sheet_part, target) + .map(|normalized| (relationship_id, normalized)) + }) + }) + .map(|entry| { + let (_, pivot_path) = entry?; + let xml = read_zip_entry(archive, &pivot_path, workbook_path)?; + parse_native_pivot_table(&xml, &pivot_path, pivot_caches) + }) + .collect() +} + +fn parse_pivot_caches( + archive: &mut ZipArchive, + workbook_path: &Path, + workbook_xml: &str, + workbook_rels: &str, +) -> Result, SpreadsheetArtifactError> { + let relationships = parse_relationships(workbook_rels)?; + let mut caches = BTreeMap::new(); + for (attributes, _) in child_tags_ns(workbook_xml, "pivotCache")? { + let Some(cache_id) = + extract_attribute(&attributes, "cacheId").and_then(|value| value.parse::().ok()) + else { + continue; + }; + let Some(rel_id) = + extract_attribute(&attributes, "r:id").or_else(|| extract_attribute(&attributes, "id")) + else { + continue; + }; + let Some(target) = relationships.get(&rel_id) else { + continue; + }; + let cache_path = normalize_relationship_target("xl/workbook.xml", target)?; + let cache_xml = read_zip_entry(archive, &cache_path, workbook_path)?; + let field_names = first_tag_text_ns(&cache_xml, "cacheFields") + .map(|section| { + child_tags_ns(§ion, "cacheField").map(|entries| { + entries + .into_iter() + .map(|(field_attributes, _)| extract_attribute(&field_attributes, "name")) + .collect::>() + }) + }) + .transpose()? + .unwrap_or_default(); + caches.insert( + cache_id, + crate::SpreadsheetPivotCacheDefinition { + definition_path: cache_path, + field_names, + }, + ); + } + Ok(caches) +} + +fn sheet_relationships_path(sheet_part: &str) -> Result { + let Some(parent) = Path::new(sheet_part).parent() else { + return Err(SpreadsheetArtifactError::Serialization { + message: format!("sheet part `{sheet_part}` has no parent"), + }); + }; + let Some(file_name) = Path::new(sheet_part) + .file_name() + .and_then(|value| value.to_str()) + else { + return Err(SpreadsheetArtifactError::Serialization { + message: format!("sheet part `{sheet_part}` has no file name"), + }); + }; + Ok(format!("{}/_rels/{file_name}.rels", parent.display())) +} + +fn normalize_relationship_target( + source_part: &str, + target: &str, +) -> Result { + if target.starts_with('/') { + return Ok(target.trim_start_matches('/').to_string()); + } + + let base = + Path::new(source_part) + .parent() + .ok_or_else(|| SpreadsheetArtifactError::Serialization { + message: format!("source part `{source_part}` has no parent"), + })?; + let joined = base.join(target); + let mut normalized = PathBuf::new(); + for component in joined.components() { + match component { + std::path::Component::Normal(value) => normalized.push(value), + std::path::Component::ParentDir => { + normalized.pop(); + } + std::path::Component::CurDir => {} + std::path::Component::RootDir | std::path::Component::Prefix(_) => {} + } + } + Ok(normalized.to_string_lossy().into_owned()) +} + +fn parse_native_table(xml: &str) -> Result { + let table_attributes = first_tag_attributes(xml, "table").ok_or_else(|| { + SpreadsheetArtifactError::Serialization { + message: "table xml missing root table tag".to_string(), + } + })?; + let id = extract_attribute(&table_attributes, "id") + .and_then(|value| value.parse::().ok()) + .ok_or_else(|| SpreadsheetArtifactError::Serialization { + message: "table xml missing numeric id".to_string(), + })?; + let name = extract_attribute(&table_attributes, "name").ok_or_else(|| { + SpreadsheetArtifactError::Serialization { + message: "table xml missing name".to_string(), + } + })?; + let display_name = + extract_attribute(&table_attributes, "displayName").unwrap_or_else(|| name.clone()); + let range = extract_attribute(&table_attributes, "ref").ok_or_else(|| { + SpreadsheetArtifactError::Serialization { + message: "table xml missing range".to_string(), + } + })?; + let header_row_count = extract_attribute(&table_attributes, "headerRowCount") + .and_then(|value| value.parse::().ok()) + .unwrap_or(1); + let totals_row_count = extract_attribute(&table_attributes, "totalsRowCount") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0); + + let columns = first_tag_text(xml, "tableColumns") + .map(|section| { + child_tags(§ion, "tableColumn").map(|entries| { + entries + .into_iter() + .enumerate() + .map(|(index, (attributes, _))| crate::SpreadsheetTableColumn { + id: extract_attribute(&attributes, "id") + .and_then(|value| value.parse::().ok()) + .unwrap_or(index as u32 + 1), + name: extract_attribute(&attributes, "name") + .unwrap_or_else(|| format!("Column{}", index + 1)), + totals_row_label: extract_attribute(&attributes, "totalsRowLabel"), + totals_row_function: extract_attribute(&attributes, "totalsRowFunction"), + }) + .collect() + }) + }) + .transpose()? + .unwrap_or_default(); + + let style_attributes = first_tag_attributes(xml, "tableStyleInfo"); + Ok(crate::SpreadsheetTable { + id, + name, + display_name, + range, + header_row_count, + totals_row_count, + style_name: style_attributes + .as_ref() + .and_then(|attributes| extract_attribute(attributes, "name")), + show_first_column: style_attributes + .as_ref() + .and_then(|attributes| extract_attribute(attributes, "showFirstColumn")) + .is_some_and(|value| value == "1"), + show_last_column: style_attributes + .as_ref() + .and_then(|attributes| extract_attribute(attributes, "showLastColumn")) + .is_some_and(|value| value == "1"), + show_row_stripes: style_attributes + .as_ref() + .and_then(|attributes| extract_attribute(attributes, "showRowStripes")) + .is_some_and(|value| value == "1"), + show_column_stripes: style_attributes + .as_ref() + .and_then(|attributes| extract_attribute(attributes, "showColumnStripes")) + .is_some_and(|value| value == "1"), + columns, + filters: BTreeMap::new(), + }) +} + +fn parse_native_chart( + xml: &str, + chart_path: &str, + sheet_name: &str, + fallback_id: u32, +) -> Result { + let style_index = first_tag_attributes_ns(xml, "style") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .and_then(|value| value.parse::().ok()) + .unwrap_or(102); + let display_blanks_as = first_tag_attributes_ns(xml, "dispBlanksAs") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .unwrap_or_else(|| "gap".to_string()); + let title = + first_tag_text_ns(xml, "title").and_then(|title_xml| chart_title_from_xml(&title_xml)); + let legend_visible = first_tag_attributes_ns(xml, "delete") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .is_none_or(|value| value != "1"); + let legend_position = first_tag_attributes_ns(xml, "legendPos") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .as_deref() + .map(parse_chart_legend_position) + .unwrap_or(crate::SpreadsheetChartLegendPosition::Bottom); + let legend_overlay = first_tag_attributes_ns(xml, "overlay") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .is_some_and(|value| value == "1"); + + let (chart_type, chart_section) = chart_section(xml)?; + let series = child_tags_ns(&chart_section, "ser")? + .into_iter() + .enumerate() + .filter_map(|(index, (_, series_xml))| { + parse_native_chart_series(&series_xml, sheet_name, index as u32 + 1).transpose() + }) + .collect::, _>>()?; + let source_range = infer_chart_source_range(&series, sheet_name); + + Ok(crate::SpreadsheetChart { + id: chart_id_from_path(chart_path).unwrap_or(fallback_id), + chart_type, + source_sheet_name: source_range.as_ref().map(|_| sheet_name.to_string()), + source_range, + title, + style_index, + display_blanks_as, + legend: crate::SpreadsheetChartLegend { + visible: legend_visible, + position: legend_position, + overlay: legend_overlay, + }, + category_axis: crate::SpreadsheetChartAxis { + linked_number_format: axis_linked_number_format(xml, "catAx"), + }, + value_axis: crate::SpreadsheetChartAxis { + linked_number_format: axis_linked_number_format(xml, "valAx"), + }, + series, + }) +} + +fn parse_native_chart_series( + xml: &str, + default_sheet_name: &str, + fallback_id: u32, +) -> Result, SpreadsheetArtifactError> { + let category_formula = series_formula(xml, "cat"); + let value_formula = series_formula(xml, "val"); + let Some(value_formula) = value_formula else { + return Ok(None); + }; + let (value_sheet_name, value_range) = + parse_formula_reference(&value_formula, Some(default_sheet_name))?; + let (category_sheet_name, category_range) = if let Some(category_formula) = category_formula { + parse_formula_reference(&category_formula, Some(default_sheet_name))? + } else { + ( + Some(default_sheet_name.to_string()), + CellRange::from_start_end( + CellAddress { column: 1, row: 1 }, + CellAddress { column: 1, row: 1 }, + ) + .to_a1(), + ) + }; + + Ok(Some(crate::SpreadsheetChartSeries { + id: first_tag_attributes_ns(xml, "idx") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .and_then(|value| value.parse::().ok()) + .unwrap_or(fallback_id), + name: chart_series_name(xml), + category_sheet_name, + category_range, + value_sheet_name, + value_range, + })) +} + +fn parse_native_pivot_table( + xml: &str, + pivot_path: &str, + pivot_caches: &BTreeMap, +) -> Result { + let attributes = first_tag_attributes_ns(xml, "pivotTableDefinition").ok_or_else(|| { + SpreadsheetArtifactError::Serialization { + message: "pivot table definition missing root tag".to_string(), + } + })?; + let name = extract_attribute(&attributes, "name").ok_or_else(|| { + SpreadsheetArtifactError::Serialization { + message: "pivot table definition missing name".to_string(), + } + })?; + let cache_id = extract_attribute(&attributes, "cacheId") + .and_then(|value| value.parse::().ok()) + .ok_or_else(|| SpreadsheetArtifactError::Serialization { + message: "pivot table definition missing cacheId".to_string(), + })?; + let cache_names = pivot_caches + .get(&cache_id) + .map(|cache| cache.field_names.clone()) + .unwrap_or_default(); + + let pivot_fields = first_tag_text_ns(xml, "pivotFields") + .map(|section| { + child_tags_ns(§ion, "pivotField").map(|entries| { + entries + .into_iter() + .enumerate() + .map( + |(index, (field_attributes, field_xml))| crate::SpreadsheetPivotField { + index: index as u32, + name: cache_names.get(index).cloned().flatten(), + axis: extract_attribute(&field_attributes, "axis"), + items: first_tag_text_ns(&field_xml, "items") + .map(|items_xml| { + child_tags_ns(&items_xml, "item").map(|items| { + items + .into_iter() + .map(|(item_attributes, _)| { + crate::SpreadsheetPivotFieldItem { + item_type: extract_attribute( + &item_attributes, + "t", + ), + index: extract_attribute(&item_attributes, "x") + .and_then(|value| { + value.parse::().ok() + }), + hidden: extract_attribute( + &item_attributes, + "h", + ) + .is_some_and(|value| value == "1"), + } + }) + .collect() + }) + }) + .transpose() + .unwrap_or_default() + .unwrap_or_default(), + }, + ) + .collect() + }) + }) + .transpose()? + .unwrap_or_default(); + + Ok(crate::SpreadsheetPivotTable { + name, + cache_id, + address: first_tag_attributes_ns(xml, "location") + .and_then(|location_attributes| extract_attribute(&location_attributes, "ref")), + row_fields: field_references_from_section( + first_tag_text_ns(xml, "rowFields").as_deref(), + "field", + "x", + &cache_names, + )?, + column_fields: field_references_from_section( + first_tag_text_ns(xml, "colFields").as_deref(), + "field", + "x", + &cache_names, + )?, + page_fields: page_fields_from_section( + first_tag_text_ns(xml, "pageFields").as_deref(), + &cache_names, + )?, + data_fields: data_fields_from_section( + first_tag_text_ns(xml, "dataFields").as_deref(), + &cache_names, + )?, + filters: pivot_filters_from_section( + first_tag_text_ns(xml, "filters").as_deref(), + &cache_names, + )?, + pivot_fields, + style_name: first_tag_attributes_ns(xml, "pivotTableStyleInfo") + .and_then(|style_attributes| extract_attribute(&style_attributes, "name")), + part_path: Some(pivot_path.to_string()), + }) +} + +fn chart_section( + xml: &str, +) -> Result<(crate::SpreadsheetChartType, String), SpreadsheetArtifactError> { + for (tag, chart_type) in [ + ("areaChart", crate::SpreadsheetChartType::Area), + ("barChart", crate::SpreadsheetChartType::Bar), + ("doughnutChart", crate::SpreadsheetChartType::Doughnut), + ("lineChart", crate::SpreadsheetChartType::Line), + ("pieChart", crate::SpreadsheetChartType::Pie), + ] { + if let Some(section) = first_tag_text_ns(xml, tag) { + return Ok((chart_type, section)); + } + } + Err(SpreadsheetArtifactError::Serialization { + message: "chart xml did not contain a supported chart type".to_string(), + }) +} + +fn chart_series_name(xml: &str) -> Option { + let tx = first_tag_text_ns(xml, "tx")?; + first_tag_text_ns(&tx, "v") + .map(|value| xml_unescape(&value)) + .or_else(|| { + first_tag_text_ns(&tx, "f").map(|formula| { + parse_formula_reference(&formula, None) + .map(|(_, range)| range) + .unwrap_or_else(|_| formula) + }) + }) + .or_else(|| { + all_text_nodes_any_ns(&tx) + .ok() + .filter(|value| !value.is_empty()) + }) +} + +fn series_formula(xml: &str, axis_tag: &str) -> Option { + let axis_xml = first_tag_text_ns(xml, axis_tag)?; + ["strRef", "numRef", "multiLvlStrRef"] + .into_iter() + .find_map(|ref_tag| first_tag_text_ns(&axis_xml, ref_tag)) + .and_then(|reference_xml| first_tag_text_ns(&reference_xml, "f")) +} + +fn chart_title_from_xml(xml: &str) -> Option { + first_tag_text_ns(xml, "v") + .map(|value| xml_unescape(&value)) + .or_else(|| { + all_text_nodes_any_ns(xml) + .ok() + .filter(|value| !value.is_empty()) + }) +} + +fn parse_chart_legend_position(value: &str) -> crate::SpreadsheetChartLegendPosition { + match value { + "t" => crate::SpreadsheetChartLegendPosition::Top, + "l" => crate::SpreadsheetChartLegendPosition::Left, + "r" => crate::SpreadsheetChartLegendPosition::Right, + _ => crate::SpreadsheetChartLegendPosition::Bottom, + } +} + +fn axis_linked_number_format(xml: &str, axis_tag: &str) -> bool { + first_tag_text_ns(xml, axis_tag) + .and_then(|axis_xml| first_tag_attributes_ns(&axis_xml, "numFmt")) + .and_then(|attributes| extract_attribute(&attributes, "sourceLinked")) + .is_none_or(|value| value != "0") +} + +fn chart_id_from_path(path: &str) -> Option { + Path::new(path) + .file_stem() + .and_then(|value| value.to_str()) + .and_then(|value| value.trim_start_matches("chart").parse::().ok()) +} + +fn infer_chart_source_range( + series: &[crate::SpreadsheetChartSeries], + sheet_name: &str, +) -> Option { + let first = series.first()?; + let category_sheet_name = first.category_sheet_name.as_deref()?; + if category_sheet_name != sheet_name { + return None; + } + let category_range = CellRange::parse(&first.category_range).ok()?; + let mut columns = vec![category_range.start.column]; + let mut row_start = category_range.start.row; + let mut row_end = category_range.end.row; + + for entry in series { + if entry.category_sheet_name.as_deref() != Some(sheet_name) + || entry.value_sheet_name.as_deref() != Some(sheet_name) + { + return None; + } + let category = CellRange::parse(&entry.category_range).ok()?; + let value = CellRange::parse(&entry.value_range).ok()?; + if category != category_range + || !value.is_single_column() + || value.start.row != row_start + || value.end.row != row_end + { + return None; + } + columns.push(value.start.column); + row_start = row_start.min(value.start.row); + row_end = row_end.max(value.end.row); + } + + columns.sort_unstable(); + columns.dedup(); + if columns.len() != series.len() + 1 { + return None; + } + let start = *columns.first()?; + let end = *columns.last()?; + if columns != (start..=end).collect::>() { + return None; + } + Some( + CellRange::from_start_end( + CellAddress { + column: start, + row: row_start, + }, + CellAddress { + column: end, + row: row_end, + }, + ) + .to_a1(), + ) +} + +fn parse_formula_reference( + formula: &str, + default_sheet_name: Option<&str>, +) -> Result<(Option, String), SpreadsheetArtifactError> { + let trimmed = formula.trim().trim_start_matches('='); + if let Some((sheet_name, range)) = trimmed.split_once('!') { + return Ok(( + Some(sheet_name.trim_matches('\'').replace("''", "'")), + normalize_a1_reference(range), + )); + } + Ok(( + default_sheet_name.map(str::to_string), + normalize_a1_reference(trimmed), + )) +} + +fn normalize_a1_reference(reference: &str) -> String { + reference.replace('$', "") +} + +fn field_references_from_section( + section: Option<&str>, + child_tag: &str, + field_attr: &str, + cache_names: &[Option], +) -> Result, SpreadsheetArtifactError> { + let Some(section) = section else { + return Ok(Vec::new()); + }; + Ok(child_tags_ns(section, child_tag)? + .into_iter() + .filter_map(|(attributes, _)| { + extract_attribute(&attributes, field_attr) + .and_then(|value| value.parse::().ok()) + .map(|field_index| crate::SpreadsheetPivotFieldReference { + field_index, + field_name: cache_names.get(field_index as usize).cloned().flatten(), + }) + }) + .collect::>()) +} + +fn page_fields_from_section( + section: Option<&str>, + cache_names: &[Option], +) -> Result, SpreadsheetArtifactError> { + let Some(section) = section else { + return Ok(Vec::new()); + }; + Ok(child_tags_ns(section, "pageField")? + .into_iter() + .filter_map(|(attributes, _)| { + extract_attribute(&attributes, "fld") + .and_then(|value| value.parse::().ok()) + .map(|field_index| crate::SpreadsheetPivotPageField { + field_index, + field_name: cache_names.get(field_index as usize).cloned().flatten(), + selected_item: extract_attribute(&attributes, "item") + .and_then(|value| value.parse::().ok()), + }) + }) + .collect::>()) +} + +fn data_fields_from_section( + section: Option<&str>, + cache_names: &[Option], +) -> Result, SpreadsheetArtifactError> { + let Some(section) = section else { + return Ok(Vec::new()); + }; + Ok(child_tags_ns(section, "dataField")? + .into_iter() + .filter_map(|(attributes, _)| { + extract_attribute(&attributes, "fld") + .and_then(|value| value.parse::().ok()) + .map(|field_index| crate::SpreadsheetPivotDataField { + field_index, + field_name: cache_names.get(field_index as usize).cloned().flatten(), + name: extract_attribute(&attributes, "name"), + subtotal: extract_attribute(&attributes, "subtotal"), + }) + }) + .collect::>()) +} + +fn pivot_filters_from_section( + section: Option<&str>, + cache_names: &[Option], +) -> Result, SpreadsheetArtifactError> { + let Some(section) = section else { + return Ok(Vec::new()); + }; + Ok(child_tags_ns(section, "filter")? + .into_iter() + .map(|(attributes, _)| { + let field_index = + extract_attribute(&attributes, "fld").and_then(|value| value.parse::().ok()); + crate::SpreadsheetPivotFilter { + field_index, + field_name: field_index + .and_then(|index| cache_names.get(index as usize).cloned().flatten()), + filter_type: extract_attribute(&attributes, "type"), + } + }) + .collect::>()) +} + +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +struct CodexSpreadsheetMetadata { + #[serde(default)] + sheets: BTreeMap, + #[serde(default)] + differential_formats: BTreeMap, +} + +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +struct CodexSheetMetadata { + #[serde(default)] + charts: Vec, + #[serde(default)] + tables: Vec, + #[serde(default)] + conditional_formats: Vec, + #[serde(default)] + pivot_tables: Vec, +} + +#[derive(Default)] +struct ParsedStyles { + text_styles: BTreeMap, + fills: BTreeMap, + borders: BTreeMap, + number_formats: BTreeMap, + cell_formats: BTreeMap, + differential_formats: BTreeMap, + dxf_id_map: BTreeMap, +} + +#[derive(Default)] +struct ParsedCellFormat { + font_index: Option, + fill_index: Option, + border_index: Option, + number_format_index: Option, + alignment: Option, + wrap_text: Option, +} + +type ParsedDifferentialFormats = ( + BTreeMap, + BTreeMap, +); + +fn parse_styles(xml: &str) -> Result { + let custom_number_formats = parse_custom_number_formats(xml)?; + let fonts = parse_fonts(xml)?; + let fills = parse_fills(xml)?; + let borders = parse_borders(xml)?; + let xfs = parse_cell_formats(xml)?; + + let mut parsed = ParsedStyles::default(); + let mut font_id_map = BTreeMap::new(); + let mut fill_id_map = BTreeMap::new(); + let mut border_id_map = BTreeMap::new(); + let mut number_format_id_map = BTreeMap::new(); + + for (index, font) in fonts.into_iter().enumerate() { + if index == 0 || font == SpreadsheetTextStyle::default() { + continue; + } + let internal_id = next_style_component_id(&parsed.text_styles); + parsed.text_styles.insert(internal_id, font); + font_id_map.insert(index as u32, internal_id); + } + + for (index, fill) in fills.into_iter().enumerate() { + if index < 2 || fill == SpreadsheetFill::default() { + continue; + } + let internal_id = next_style_component_id(&parsed.fills); + parsed.fills.insert(internal_id, fill); + fill_id_map.insert(index as u32, internal_id); + } + + for (index, border) in borders.into_iter().enumerate() { + if index == 0 || border == SpreadsheetBorder::default() { + continue; + } + let internal_id = next_style_component_id(&parsed.borders); + parsed.borders.insert(internal_id, border); + border_id_map.insert(index as u32, internal_id); + } + + for (style_index, xf) in xfs.into_iter().enumerate() { + let number_format_id = xf.number_format_index.and_then(|num_fmt_id| { + resolve_number_format_id( + num_fmt_id, + &custom_number_formats, + &mut parsed.number_formats, + &mut number_format_id_map, + ) + }); + + let format = SpreadsheetCellFormat { + text_style_id: xf + .font_index + .and_then(|value| font_id_map.get(&value).copied()), + fill_id: xf + .fill_index + .and_then(|value| fill_id_map.get(&value).copied()), + border_id: xf + .border_index + .and_then(|value| border_id_map.get(&value).copied()), + alignment: xf.alignment, + number_format_id, + wrap_text: xf.wrap_text, + base_cell_style_format_id: None, + }; + + if style_index != 0 || format != SpreadsheetCellFormat::default() { + parsed.cell_formats.insert(style_index as u32, format); + } + } + + let (differential_formats, dxf_id_map) = parse_differential_formats( + xml, + &mut parsed.text_styles, + &mut parsed.fills, + &mut parsed.borders, + &mut parsed.number_formats, + &custom_number_formats, + &mut number_format_id_map, + )?; + parsed.differential_formats = differential_formats; + parsed.dxf_id_map = dxf_id_map; + + Ok(parsed) +} + +fn parse_custom_number_formats( + xml: &str, +) -> Result, SpreadsheetArtifactError> { + let Some(section) = first_tag_text(xml, "numFmts") else { + return Ok(BTreeMap::new()); + }; + + let mut number_formats = BTreeMap::new(); + for (attributes, _) in child_tags(§ion, "numFmt")? { + let Some(num_fmt_id) = + extract_attribute(&attributes, "numFmtId").and_then(|value| value.parse::().ok()) + else { + continue; + }; + let Some(format_code) = extract_attribute(&attributes, "formatCode") else { + continue; + }; + number_formats.insert(num_fmt_id, format_code); + } + Ok(number_formats) +} + +fn parse_fonts(xml: &str) -> Result, SpreadsheetArtifactError> { + let Some(section) = first_tag_text(xml, "fonts") else { + return Ok(Vec::new()); + }; + + child_tags(§ion, "font")? + .into_iter() + .map(|(_, body)| Ok(parse_font_body(&body))) + .collect() +} + +fn parse_fills(xml: &str) -> Result, SpreadsheetArtifactError> { + let Some(section) = first_tag_text(xml, "fills") else { + return Ok(Vec::new()); + }; + + child_tags(§ion, "fill")? + .into_iter() + .map(|(_, body)| parse_fill_body(&body)) + .collect() +} + +fn parse_borders(xml: &str) -> Result, SpreadsheetArtifactError> { + let Some(section) = first_tag_text(xml, "borders") else { + return Ok(Vec::new()); + }; + + child_tags(§ion, "border")? + .into_iter() + .map(|(_, body)| Ok(parse_border_body(&body))) + .collect() +} + +fn parse_font_body(body: &str) -> SpreadsheetTextStyle { + let name = first_tag_attributes(body, "name") + .and_then(|attributes| extract_attribute(&attributes, "val")); + SpreadsheetTextStyle { + bold: first_tag_attributes(body, "b").map(|_| true), + italic: first_tag_attributes(body, "i").map(|_| true), + underline: first_tag_attributes(body, "u").map(|_| true), + font_size: first_tag_attributes(body, "sz") + .and_then(|attributes| extract_attribute(&attributes, "val")) + .and_then(|value| value.parse::().ok()), + font_color: first_tag_attributes(body, "color") + .and_then(|attributes| parse_color_value(&attributes)), + font_family: name.clone(), + typeface: name, + font_scheme: first_tag_attributes(body, "scheme") + .and_then(|attributes| extract_attribute(&attributes, "val")), + ..Default::default() + } +} + +fn parse_fill_body(body: &str) -> Result { + let mut fill = SpreadsheetFill::default(); + if let Some(pattern_attributes) = first_tag_attributes(body, "patternFill") { + fill.pattern_type = extract_attribute(&pattern_attributes, "patternType"); + fill.pattern_foreground_color = first_tag_attributes(body, "fgColor") + .and_then(|attributes| parse_color_value(&attributes)); + fill.pattern_background_color = first_tag_attributes(body, "bgColor") + .and_then(|attributes| parse_color_value(&attributes)); + fill.solid_fill_color = fill.pattern_foreground_color.clone(); + } + if let Some(gradient_attributes) = first_tag_attributes(body, "gradientFill") { + fill.gradient_fill_type = Some( + extract_attribute(&gradient_attributes, "type").unwrap_or_else(|| "linear".to_string()), + ); + fill.gradient_kind = fill.gradient_fill_type.clone(); + fill.angle = extract_attribute(&gradient_attributes, "degree") + .and_then(|value| value.parse::().ok()); + let rectangle = SpreadsheetFillRectangle { + left: extract_attribute(&gradient_attributes, "left") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0.0), + right: extract_attribute(&gradient_attributes, "right") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0.0), + top: extract_attribute(&gradient_attributes, "top") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0.0), + bottom: extract_attribute(&gradient_attributes, "bottom") + .and_then(|value| value.parse::().ok()) + .unwrap_or(0.0), + }; + if rectangle.left != 0.0 + || rectangle.right != 0.0 + || rectangle.top != 0.0 + || rectangle.bottom != 0.0 + { + fill.fill_rectangle = Some(rectangle); + } + fill.gradient_stops = child_tags(body, "stop")? + .into_iter() + .filter_map(|(attributes, stop_body)| { + let position = extract_attribute(&attributes, "position") + .and_then(|value| value.parse::().ok())?; + let color = first_tag_attributes(&stop_body, "color") + .and_then(|stop_attributes| parse_color_value(&stop_attributes))?; + Some(SpreadsheetGradientStop { position, color }) + }) + .collect(); + } + Ok(fill) +} + +fn parse_border_body(body: &str) -> SpreadsheetBorder { + SpreadsheetBorder { + top: parse_border_line(body, "top"), + right: parse_border_line(body, "right"), + bottom: parse_border_line(body, "bottom"), + left: parse_border_line(body, "left"), + } +} + +fn parse_border_line(body: &str, edge: &str) -> Option { + let mut matches = child_tags(body, edge).ok()?; + let (attributes, edge_body) = matches.pop()?; + let style = extract_attribute(&attributes, "style"); + let color = first_tag_attributes(&edge_body, "color") + .and_then(|color_attributes| parse_color_value(&color_attributes)); + if style.is_none() && color.is_none() { + None + } else { + Some(SpreadsheetBorderLine { style, color }) + } +} + +fn parse_alignment_attributes(attributes: &str) -> (Option, Option) { + let alignment = SpreadsheetAlignment { + horizontal: extract_attribute(attributes, "horizontal"), + vertical: extract_attribute(attributes, "vertical"), + }; + let wrap_text = + extract_attribute(attributes, "wrapText").and_then(|value| parse_xlsx_bool(&value)); + ( + (alignment != SpreadsheetAlignment::default()).then_some(alignment), + wrap_text, + ) +} + +fn parse_xlsx_bool(value: &str) -> Option { + match value { + "1" | "true" | "TRUE" => Some(true), + "0" | "false" | "FALSE" => Some(false), + _ => None, + } +} + +fn parse_cell_formats(xml: &str) -> Result, SpreadsheetArtifactError> { + let Some(section) = first_tag_text(xml, "cellXfs") else { + return Ok(Vec::new()); + }; + + child_tags(§ion, "xf")? + .into_iter() + .map(|(attributes, body)| { + let (alignment, wrap_text) = first_tag_attributes(&body, "alignment") + .map(|attributes| parse_alignment_attributes(&attributes)) + .unwrap_or_default(); + + Ok(ParsedCellFormat { + font_index: extract_attribute(&attributes, "fontId") + .and_then(|value| value.parse::().ok()), + fill_index: extract_attribute(&attributes, "fillId") + .and_then(|value| value.parse::().ok()), + border_index: extract_attribute(&attributes, "borderId") + .and_then(|value| value.parse::().ok()), + number_format_index: extract_attribute(&attributes, "numFmtId") + .and_then(|value| value.parse::().ok()), + alignment, + wrap_text, + }) + }) + .collect() +} + +fn parse_differential_formats( + xml: &str, + text_styles: &mut BTreeMap, + fills: &mut BTreeMap, + borders: &mut BTreeMap, + number_formats: &mut BTreeMap, + custom_number_formats: &BTreeMap, + number_format_id_map: &mut BTreeMap, +) -> Result { + let Some(section) = first_tag_text(xml, "dxfs") else { + return Ok((BTreeMap::new(), BTreeMap::new())); + }; + + let mut differential_formats = BTreeMap::new(); + let mut dxf_id_map = BTreeMap::new(); + for (xlsx_dxf_index, (_, body)) in child_tags(§ion, "dxf")?.into_iter().enumerate() { + let text_style_id = first_tag_text(&body, "font") + .and_then(|font_body| insert_style_component(text_styles, parse_font_body(&font_body))); + let fill_id = first_tag_text(&body, "fill") + .map(|fill_body| parse_fill_body(&fill_body)) + .transpose()? + .and_then(|fill| insert_style_component(fills, fill)); + let border_id = first_tag_text(&body, "border") + .map(|border_body| parse_border_body(&border_body)) + .and_then(|border| insert_style_component(borders, border)); + let number_format_id = first_tag_attributes(&body, "numFmt") + .and_then(|attributes| extract_attribute(&attributes, "numFmtId")) + .and_then(|value| value.parse::().ok()) + .and_then(|num_fmt_id| { + resolve_number_format_id( + num_fmt_id, + custom_number_formats, + number_formats, + number_format_id_map, + ) + }); + let (alignment, wrap_text) = first_tag_attributes(&body, "alignment") + .map(|attributes| parse_alignment_attributes(&attributes)) + .unwrap_or_default(); + let format = crate::SpreadsheetDifferentialFormat { + text_style_id, + fill_id, + border_id, + alignment, + number_format_id, + wrap_text, + }; + if format == crate::SpreadsheetDifferentialFormat::default() { + continue; + } + let internal_id = next_style_component_id(&differential_formats); + differential_formats.insert(internal_id, format); + dxf_id_map.insert(xlsx_dxf_index as u32, internal_id); + } + Ok((differential_formats, dxf_id_map)) +} + +fn insert_style_component(map: &mut BTreeMap, value: T) -> Option +where + T: Default + PartialEq, +{ + if value == T::default() { + return None; + } + let internal_id = next_style_component_id(map); + map.insert(internal_id, value); + Some(internal_id) +} + +fn resolve_number_format_id( + num_fmt_id: u32, + custom_number_formats: &BTreeMap, + number_formats: &mut BTreeMap, + number_format_id_map: &mut BTreeMap, +) -> Option { + if num_fmt_id == 0 { + return None; + } + if let Some(existing) = number_format_id_map.get(&num_fmt_id).copied() { + return Some(existing); + } + let internal_id = next_style_component_id(number_formats); + number_formats.insert( + internal_id, + SpreadsheetNumberFormat { + format_id: Some(num_fmt_id), + format_code: custom_number_formats + .get(&num_fmt_id) + .cloned() + .or_else(|| builtin_number_format_code_for_xlsx(num_fmt_id)), + }, + ); + number_format_id_map.insert(num_fmt_id, internal_id); + Some(internal_id) +} + +fn next_style_component_id(map: &BTreeMap) -> u32 { + map.last_key_value().map(|(id, _)| id + 1).unwrap_or(1) +} + +fn content_types_xml(sheet_count: usize, include_custom_metadata: bool) -> String { let mut overrides = String::new(); for index in 1..=sheet_count { overrides.push_str(&format!( r#""# )); } + if include_custom_metadata { + overrides.push_str( + r#""#, + ); + } format!( "{}{}{}{}{}{}{}{}{}{}", r#""#, @@ -504,6 +1930,63 @@ fn content_types_xml(sheet_count: usize) -> String { ) } +fn artifact_has_custom_metadata(artifact: &SpreadsheetArtifact) -> bool { + artifact.sheets.iter().any(|sheet| { + !sheet.charts.is_empty() + || !sheet.tables.is_empty() + || !sheet.conditional_formats.is_empty() + || !sheet.pivot_tables.is_empty() + }) +} + +fn codex_metadata_xml(artifact: &SpreadsheetArtifact) -> Result { + let metadata = CodexSpreadsheetMetadata { + differential_formats: artifact.differential_formats.clone(), + sheets: artifact + .sheets + .iter() + .filter_map(|sheet| { + let sheet_metadata = CodexSheetMetadata { + charts: sheet.charts.clone(), + tables: sheet.tables.clone(), + conditional_formats: sheet.conditional_formats.clone(), + pivot_tables: sheet.pivot_tables.clone(), + }; + (!sheet_metadata.charts.is_empty() + || !sheet_metadata.tables.is_empty() + || !sheet_metadata.conditional_formats.is_empty() + || !sheet_metadata.pivot_tables.is_empty()) + .then_some((sheet.name.clone(), sheet_metadata)) + }) + .collect(), + }; + let json = serde_json::to_string(&metadata).map_err(|error| { + SpreadsheetArtifactError::Serialization { + message: error.to_string(), + } + })?; + Ok(format!( + concat!( + r#""#, + r#""#, + r#"{}"#, + r#""# + ), + xml_escape(&json) + )) +} + +fn parse_codex_metadata(xml: &str) -> Result { + let Some(json) = first_tag_text(xml, "json") else { + return Ok(CodexSpreadsheetMetadata::default()); + }; + serde_json::from_str(&xml_unescape(&json)).map_err(|error| { + SpreadsheetArtifactError::Serialization { + message: error.to_string(), + } + }) +} + fn root_relationships_xml() -> &'static str { concat!( r#""#, @@ -626,26 +2109,141 @@ fn styles_xml(artifact: &SpreadsheetArtifact) -> String { .sheets .iter() .flat_map(|sheet| sheet.cells.values().map(|cell| cell.style_index)) + .chain(artifact.cell_formats.keys().copied()) .max() .unwrap_or(0); + let font_ids = artifact.text_styles.keys().copied().collect::>(); + let fill_ids = artifact.fills.keys().copied().collect::>(); + let border_ids = artifact.borders.keys().copied().collect::>(); + let number_format_ids = artifact.number_formats.keys().copied().collect::>(); + let differential_format_ids = artifact + .differential_formats + .keys() + .copied() + .collect::>(); + + let font_indices = font_ids + .iter() + .enumerate() + .map(|(index, id)| (*id, index as u32 + 1)) + .collect::>(); + let fill_indices = fill_ids + .iter() + .enumerate() + .map(|(index, id)| (*id, index as u32 + 2)) + .collect::>(); + let border_indices = border_ids + .iter() + .enumerate() + .map(|(index, id)| (*id, index as u32 + 1)) + .collect::>(); + let number_format_indices = assign_number_format_ids(artifact, &number_format_ids); + + let fonts = std::iter::once("".to_string()) + .chain( + font_ids + .iter() + .filter_map(|id| artifact.text_styles.get(id).map(spreadsheet_font_xml)), + ) + .collect::>() + .join(""); + let fills = [ + r#""#.to_string(), + r#""#.to_string(), + ] + .into_iter() + .chain( + fill_ids + .iter() + .filter_map(|id| artifact.fills.get(id).map(spreadsheet_fill_xml)), + ) + .collect::>() + .join(""); + let borders = std::iter::once("".to_string()) + .chain( + border_ids + .iter() + .filter_map(|id| artifact.borders.get(id).map(spreadsheet_border_xml)), + ) + .collect::>() + .join(""); + let number_formats = number_format_ids + .iter() + .filter_map(|id| { + artifact.number_formats.get(id).and_then(|format| { + let excel_id = number_format_indices.get(id).copied()?; + format + .format_code + .as_ref() + .filter(|_| excel_id >= 164) + .map(|format_code| { + format!( + r#""#, + xml_escape(format_code) + ) + }) + }) + }) + .collect::>(); + let num_fmts_xml = if number_formats.is_empty() { + String::new() + } else { + format!( + r#"{}"#, + number_formats.len(), + number_formats.join("") + ) + }; + let differential_formats = differential_format_ids + .iter() + .filter_map(|id| { + artifact.differential_formats.get(id).map(|format| { + spreadsheet_differential_format_xml(artifact, format, &number_format_indices) + }) + }) + .collect::>() + .join(""); let cell_xfs = (0..=max_style_index) - .map(|_| r#""#) + .map(|style_index| { + let format = artifact + .resolve_cell_format(style_index) + .or_else(|| artifact.get_cell_format(style_index).cloned()) + .unwrap_or_default(); + spreadsheet_cell_format_xml( + &format, + &font_indices, + &fill_indices, + &border_indices, + &number_format_indices, + ) + }) .collect::>() .join(""); format!( concat!( r#""#, r#""#, - r#""#, - r#""#, - r#""#, + r#"{}"#, + r#"{}"#, + r#"{}"#, + r#"{}"#, r#""#, r#"{}"#, + r#"{}"#, r#""#, r#""# ), + num_fmts_xml, + font_ids.len() + 1, + fonts, + fill_ids.len() + 2, + fills, + border_ids.len() + 1, + borders, max_style_index + 1, cell_xfs, + differential_format_ids.len(), + differential_formats, ) } @@ -803,12 +2401,79 @@ fn first_tag_attributes(xml: &str, tag: &str) -> Option { captures.get(1).map(|value| value.as_str().to_string()) } +fn first_tag_attributes_ns(xml: &str, tag: &str) -> Option { + let regex = Regex::new(&format!(r#"<(?:[A-Za-z0-9_]+:)?{tag}\b([^>]*)/?>"#)).ok()?; + let captures = regex.captures(xml)?; + captures.get(1).map(|value| value.as_str().to_string()) +} + +fn child_tags(xml: &str, tag: &str) -> Result, SpreadsheetArtifactError> { + let regex = Regex::new(&format!( + r#"(?s)<{tag}\b([^>]*)/>|<{tag}\b([^>]*)>(.*?)"# + )) + .map_err(|error| SpreadsheetArtifactError::Serialization { + message: error.to_string(), + })?; + Ok(regex + .captures_iter(xml) + .map(|captures| { + let attributes = captures + .get(1) + .or_else(|| captures.get(2)) + .map(|value| value.as_str()) + .unwrap_or_default() + .to_string(); + let body = captures + .get(3) + .map(|value| value.as_str()) + .unwrap_or_default() + .to_string(); + (attributes, body) + }) + .collect()) +} + +fn child_tags_ns(xml: &str, tag: &str) -> Result, SpreadsheetArtifactError> { + let regex = Regex::new(&format!( + r#"(?s)<(?:[A-Za-z0-9_]+:)?{tag}\b([^>]*)/>|<(?:[A-Za-z0-9_]+:)?{tag}\b([^>]*)>(.*?)"# + )) + .map_err(|error| SpreadsheetArtifactError::Serialization { + message: error.to_string(), + })?; + Ok(regex + .captures_iter(xml) + .map(|captures| { + let attributes = captures + .get(1) + .or_else(|| captures.get(2)) + .map(|value| value.as_str()) + .unwrap_or_default() + .to_string(); + let body = captures + .get(3) + .map(|value| value.as_str()) + .unwrap_or_default() + .to_string(); + (attributes, body) + }) + .collect()) +} + fn first_tag_text(xml: &str, tag: &str) -> Option { let regex = Regex::new(&format!(r#"(?s)<{tag}\b[^>]*>(.*?)"#)).ok()?; let captures = regex.captures(xml)?; captures.get(1).map(|value| value.as_str().to_string()) } +fn first_tag_text_ns(xml: &str, tag: &str) -> Option { + let regex = Regex::new(&format!( + r#"(?s)<(?:[A-Za-z0-9_]+:)?{tag}\b[^>]*>(.*?)"# + )) + .ok()?; + let captures = regex.captures(xml)?; + captures.get(1).map(|value| value.as_str().to_string()) +} + fn extract_workbook_title(xml: &str) -> String { let Ok(regex) = Regex::new(r#"(?s)<(?:[A-Za-z0-9_]+:)?title\b[^>]*>(.*?)"#) @@ -834,6 +2499,18 @@ fn all_text_nodes(xml: &str) -> Result { .join("")) } +fn all_text_nodes_any_ns(xml: &str) -> Result { + let regex = Regex::new(r#"(?s)<(?:[A-Za-z0-9_]+:)?t\b[^>]*>(.*?)"#) + .map_err(|error| SpreadsheetArtifactError::Serialization { + message: error.to_string(), + })?; + Ok(regex + .captures_iter(xml) + .filter_map(|captures| captures.get(1).map(|value| xml_unescape(value.as_str()))) + .collect::>() + .join("")) +} + fn extract_attribute(attributes: &str, name: &str) -> Option { let pattern = format!(r#"{name}="([^"]*)""#); let regex = Regex::new(&pattern).ok()?; @@ -841,6 +2518,320 @@ fn extract_attribute(attributes: &str, name: &str) -> Option { captures.get(1).map(|value| xml_unescape(value.as_str())) } +fn parse_color_value(attributes: &str) -> Option { + let rgb = extract_attribute(attributes, "rgb")?; + let normalized = rgb.trim(); + if normalized.len() == 8 { + Some(format!("#{}", normalized[2..].to_ascii_uppercase())) + } else if normalized.len() == 6 { + Some(format!("#{}", normalized.to_ascii_uppercase())) + } else { + None + } +} + +fn color_xml(tag: &str, color: &str) -> Option { + let rgb = xlsx_rgb(color)?; + Some(format!(r#"<{tag} rgb="{rgb}"/>"#)) +} + +fn xlsx_rgb(color: &str) -> Option { + let hex = color.trim().trim_start_matches('#'); + if hex.len() == 6 && hex.chars().all(|ch| ch.is_ascii_hexdigit()) { + Some(format!("FF{}", hex.to_ascii_uppercase())) + } else if hex.len() == 8 && hex.chars().all(|ch| ch.is_ascii_hexdigit()) { + Some(hex.to_ascii_uppercase()) + } else { + None + } +} + +fn builtin_number_format_code_for_xlsx(format_id: u32) -> Option { + match format_id { + 0 => Some("General".to_string()), + 1 => Some("0".to_string()), + 2 => Some("0.00".to_string()), + 3 => Some("#,##0".to_string()), + 4 => Some("#,##0.00".to_string()), + 9 => Some("0%".to_string()), + 10 => Some("0.00%".to_string()), + _ => None, + } +} + +fn spreadsheet_font_xml(style: &SpreadsheetTextStyle) -> String { + let mut parts = Vec::new(); + if style.bold == Some(true) { + parts.push("".to_string()); + } + if style.italic == Some(true) { + parts.push("".to_string()); + } + if style.underline == Some(true) { + parts.push("".to_string()); + } + if let Some(font_size) = style.font_size { + parts.push(format!(r#""#)); + } + if let Some(color) = style + .font_color + .as_deref() + .and_then(|value| color_xml("color", value)) + { + parts.push(color); + } + let font_name = style + .typeface + .as_deref() + .or(style.font_family.as_deref()) + .or_else(|| { + style + .font_face + .as_ref() + .and_then(|face| face.typeface.as_deref()) + }) + .or_else(|| { + style + .font_face + .as_ref() + .and_then(|face| face.font_family.as_deref()) + }); + if let Some(font_name) = font_name { + parts.push(format!(r#""#, xml_escape(font_name))); + } + if let Some(font_scheme) = style.font_scheme.as_deref().or_else(|| { + style + .font_face + .as_ref() + .and_then(|face| face.font_scheme.as_deref()) + }) { + parts.push(format!(r#""#, xml_escape(font_scheme))); + } + format!("{}", parts.join("")) +} + +fn spreadsheet_fill_xml(fill: &SpreadsheetFill) -> String { + if !fill.gradient_stops.is_empty() + || fill.gradient_fill_type.is_some() + || fill.gradient_kind.is_some() + { + let gradient_type = fill + .gradient_kind + .as_deref() + .or(fill.gradient_fill_type.as_deref()) + .unwrap_or("linear"); + let gradient_attributes = if gradient_type.eq_ignore_ascii_case("path") { + let rectangle = fill.fill_rectangle.as_ref(); + format!( + r#" type="path" left="{}" right="{}" top="{}" bottom="{}""#, + rectangle.map(|entry| entry.left).unwrap_or(0.0), + rectangle.map(|entry| entry.right).unwrap_or(0.0), + rectangle.map(|entry| entry.top).unwrap_or(0.0), + rectangle.map(|entry| entry.bottom).unwrap_or(0.0) + ) + } else { + fill.angle + .map(|angle| format!(r#" degree="{angle}""#)) + .unwrap_or_default() + }; + let stops = fill + .gradient_stops + .iter() + .filter_map(|stop| { + color_xml("color", &stop.color) + .map(|color| format!(r#"{color}"#, stop.position)) + }) + .collect::>() + .join(""); + return format!( + r#"{stops}"# + ); + } + + let pattern_type = fill.pattern_type.as_deref().unwrap_or_else(|| { + if fill.solid_fill_color.is_some() { + "solid" + } else { + "none" + } + }); + let fg_color = fill + .pattern_foreground_color + .as_deref() + .or(fill.solid_fill_color.as_deref()) + .and_then(|value| color_xml("fgColor", value)) + .unwrap_or_default(); + let bg_color = fill + .pattern_background_color + .as_deref() + .and_then(|value| color_xml("bgColor", value)) + .unwrap_or_default(); + format!( + r#"{fg_color}{bg_color}"# + ) +} + +fn spreadsheet_border_xml(border: &SpreadsheetBorder) -> String { + format!( + "{}{}{}{}", + spreadsheet_border_line_xml("left", border.left.as_ref()), + spreadsheet_border_line_xml("right", border.right.as_ref()), + spreadsheet_border_line_xml("top", border.top.as_ref()), + spreadsheet_border_line_xml("bottom", border.bottom.as_ref()), + ) +} + +fn spreadsheet_border_line_xml(edge: &str, line: Option<&SpreadsheetBorderLine>) -> String { + let Some(line) = line else { + return format!("<{edge}/>"); + }; + let style = line + .style + .as_deref() + .map(|value| format!(r#" style="{}""#, xml_escape(value))) + .unwrap_or_default(); + let color = line + .color + .as_deref() + .and_then(|value| color_xml("color", value)) + .unwrap_or_default(); + format!(r#"<{edge}{style}>{color}"#) +} + +fn spreadsheet_alignment_xml( + alignment: Option<&SpreadsheetAlignment>, + wrap_text: Option, +) -> String { + let horizontal = alignment + .and_then(|alignment| alignment.horizontal.as_deref()) + .map(|value| format!(r#" horizontal="{}""#, xml_escape(value))) + .unwrap_or_default(); + let vertical = alignment + .and_then(|alignment| alignment.vertical.as_deref()) + .map(|value| format!(r#" vertical="{}""#, xml_escape(value))) + .unwrap_or_default(); + let wrap_text = wrap_text + .map(|value| format!(r#" wrapText="{}""#, usize::from(value))) + .unwrap_or_default(); + if horizontal.is_empty() && vertical.is_empty() && wrap_text.is_empty() { + String::new() + } else { + format!("") + } +} + +fn spreadsheet_differential_format_xml( + artifact: &SpreadsheetArtifact, + format: &crate::SpreadsheetDifferentialFormat, + number_format_indices: &BTreeMap, +) -> String { + let mut parts = Vec::new(); + if let Some(style) = format + .text_style_id + .and_then(|id| artifact.text_styles.get(&id)) + { + parts.push(spreadsheet_font_xml(style)); + } + if let Some(fill) = format.fill_id.and_then(|id| artifact.fills.get(&id)) { + parts.push(spreadsheet_fill_xml(fill)); + } + if let Some(border) = format.border_id.and_then(|id| artifact.borders.get(&id)) { + parts.push(spreadsheet_border_xml(border)); + } + if let Some(number_format) = format + .number_format_id + .and_then(|id| artifact.number_formats.get(&id)) + && let Some(num_fmt_id) = format + .number_format_id + .and_then(|id| number_format_indices.get(&id).copied()) + && let Some(format_code) = number_format.format_code.clone().or_else(|| { + number_format + .format_id + .and_then(builtin_number_format_code_for_xlsx) + }) + { + parts.push(format!( + r#""#, + xml_escape(&format_code) + )); + } + let alignment_xml = spreadsheet_alignment_xml(format.alignment.as_ref(), format.wrap_text); + if !alignment_xml.is_empty() { + parts.push(alignment_xml); + } + format!("{}", parts.join("")) +} + +fn assign_number_format_ids( + artifact: &SpreadsheetArtifact, + number_format_ids: &[u32], +) -> BTreeMap { + let mut assigned = BTreeMap::new(); + let mut next_custom_id = 164; + for id in number_format_ids { + let Some(format) = artifact.number_formats.get(id) else { + continue; + }; + let excel_id = format.format_id.unwrap_or_else(|| { + while artifact + .number_formats + .values() + .any(|entry| entry.format_id == Some(next_custom_id)) + { + next_custom_id += 1; + } + let assigned_id = next_custom_id; + next_custom_id += 1; + assigned_id + }); + assigned.insert(*id, excel_id); + } + assigned +} + +fn spreadsheet_cell_format_xml( + format: &SpreadsheetCellFormat, + font_indices: &BTreeMap, + fill_indices: &BTreeMap, + border_indices: &BTreeMap, + number_format_indices: &BTreeMap, +) -> String { + let font_id = format + .text_style_id + .and_then(|id| font_indices.get(&id).copied()) + .unwrap_or(0); + let fill_id = format + .fill_id + .and_then(|id| fill_indices.get(&id).copied()) + .unwrap_or(0); + let border_id = format + .border_id + .and_then(|id| border_indices.get(&id).copied()) + .unwrap_or(0); + let num_fmt_id = format + .number_format_id + .and_then(|id| number_format_indices.get(&id).copied()) + .unwrap_or(0); + + let alignment_xml = spreadsheet_alignment_xml(format.alignment.as_ref(), format.wrap_text); + + let apply_alignment = usize::from(!alignment_xml.is_empty()); + let apply_number_format = usize::from(num_fmt_id != 0); + let apply_fill = usize::from(fill_id != 0); + let apply_border = usize::from(border_id != 0); + let apply_font = usize::from(font_id != 0); + + if alignment_xml.is_empty() { + format!( + r#""# + ) + } else { + format!( + r#"{alignment_xml}"# + ) + } +} + fn xml_escape(value: &str) -> String { value .replace('&', "&") From f6288248f48425e971c7a0d3898bcd0685bfccc2 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 3 Mar 2026 10:13:14 -0800 Subject: [PATCH 03/17] realtime prompt changes (#13376) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- codex-rs/protocol/src/prompts/realtime/realtime_end.md | 4 +--- codex-rs/protocol/src/prompts/realtime/realtime_start.md | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/codex-rs/protocol/src/prompts/realtime/realtime_end.md b/codex-rs/protocol/src/prompts/realtime/realtime_end.md index 07ccae5286..abec16da03 100644 --- a/codex-rs/protocol/src/prompts/realtime/realtime_end.md +++ b/codex-rs/protocol/src/prompts/realtime/realtime_end.md @@ -1,5 +1,3 @@ Realtime conversation ended. -You are still operating behind an intermediary rather than speaking to the user directly. Use the conversation transcript and current context to decide whether backend help is actually needed, and avoid verbose responses that only add latency. - -Subsequent user input may return to typed text rather than transcript-style text. Do not assume recognition errors or missing punctuation once realtime has ended. Resume normal chat behavior. +Subsequent user input will return to typed text rather than transcript-style text. Do not assume recognition errors or missing punctuation once realtime has ended. Resume normal chat behavior. diff --git a/codex-rs/protocol/src/prompts/realtime/realtime_start.md b/codex-rs/protocol/src/prompts/realtime/realtime_start.md index c7d363eff7..3159a2b07f 100644 --- a/codex-rs/protocol/src/prompts/realtime/realtime_start.md +++ b/codex-rs/protocol/src/prompts/realtime/realtime_start.md @@ -6,5 +6,4 @@ When invoked, you receive the latest conversation transcript and any relevant mo When user text is routed from realtime, treat it as a transcript. It may be unpunctuated or contain recognition errors. -- Ask brief clarification questions when needed. -- Keep responses concise and action-oriented. +- Keep responses concise and action-oriented. Your updates should help the intermediary respond to the user. From aa14b3cd2af2f6c23954a1e55ef794eb126f2f10 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 18:23:38 +0000 Subject: [PATCH 04/17] fix more --- .../src/presentation_artifact/pptx.rs | 20 +- .../src/presentation_artifact/render.rs | 207 ++++++++++++++++-- codex-rs/artifact-presentation/src/tests.rs | 84 ++++++- .../core/tests/suite/presentation_artifact.rs | 9 +- 4 files changed, 290 insertions(+), 30 deletions(-) diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index b9854745c7..ba53ca788b 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -1058,12 +1058,12 @@ fn export_text_body_xml( style.alignment.unwrap_or(TextAlignment::Left), rich_text, ); + let left_inset = points_to_emu(insets.left); + let right_inset = points_to_emu(insets.right); + let top_inset = points_to_emu(insets.top); + let bottom_inset = points_to_emu(insets.bottom); format!( - r#"{auto_fit}{paragraphs}"#, - points_to_emu(insets.left), - points_to_emu(insets.right), - points_to_emu(insets.top), - points_to_emu(insets.bottom), + r#"{auto_fit}{paragraphs}"# ) } @@ -1110,12 +1110,12 @@ fn export_table_cell_text_body_xml( TextAutoFitMode::ResizeShapeToFitText => "".to_string(), }; let insets = cell.rich_text.layout.insets.map(|insets| { + let left_inset = points_to_emu(insets.left); + let right_inset = points_to_emu(insets.right); + let top_inset = points_to_emu(insets.top); + let bottom_inset = points_to_emu(insets.bottom); format!( - r#" lIns="{}" rIns="{}" tIns="{}" bIns="{}""#, - points_to_emu(insets.left), - points_to_emu(insets.right), - points_to_emu(insets.top), - points_to_emu(insets.bottom), + r#" lIns="{left_inset}" rIns="{right_inset}" tIns="{top_inset}" bIns="{bottom_inset}""#, ) }); let paragraphs = export_text_paragraphs_xml( diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs index 83e8cdfc71..7592136e62 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs @@ -153,7 +153,7 @@ fn render_slide_image( } let mut ordered = slide.elements.iter().collect::>(); - ordered.sort_by_key(|element| element.z_order()); + ordered.sort_by_key(|element| (preview_render_layer(element), element.z_order())); for element in ordered { render_element(&mut canvas, document, slide, element, render_scale)?; } @@ -1123,11 +1123,13 @@ fn layout_text_lines(glyphs: &[StyledGlyph], config: TextRenderConfig) -> Vec Vec config.width { - lines.push(TextLine { - glyphs: std::mem::take(&mut current), - width: current_width, - spaces: current_spaces, - }); + push_text_line( + &mut lines, + &mut current, + &mut current_width, + &mut current_spaces, + config.font_px, + ); current_width = 0; current_spaces = 0; + if matches!(glyph.ch, ' ' | '\t') { + continue; + } + } + if current.is_empty() && matches!(glyph.ch, ' ' | '\t') { + continue; } if glyph.ch == ' ' { current_spaces += 1; @@ -1152,15 +1162,41 @@ fn layout_text_lines(glyphs: &[StyledGlyph], config: TextRenderConfig) -> Vec, + current: &mut Vec, + current_width: &mut u32, + current_spaces: &mut usize, + font_px: u32, +) { + while let Some(glyph) = current.last() { + if !matches!(glyph.ch, ' ' | '\t') { + break; + } + let trimmed = current.pop().expect("last glyph must exist"); + *current_width = current_width.saturating_sub(measure_glyph_width(&trimmed, font_px)); + if trimmed.ch == ' ' { + *current_spaces = current_spaces.saturating_sub(1); + } + } + lines.push(TextLine { + glyphs: std::mem::take(current), + width: *current_width, + spaces: *current_spaces, + }); +} + fn draw_bitmap_glyph( image: &mut RgbaImage, left: u32, @@ -1637,6 +1673,17 @@ fn parse_rgba(hex: &str, alpha: u8) -> Rgba { Rgba([0, 0, 0, alpha]) } +fn preview_render_layer(element: &PresentationElement) -> usize { + match element { + PresentationElement::Text(_) + | PresentationElement::Shape(_) + | PresentationElement::Connector(_) + | PresentationElement::Image(ImageElement { payload: None, .. }) => 0, + PresentationElement::Image(_) | PresentationElement::Chart(_) => 1, + PresentationElement::Table(_) => 2, + } +} + fn shape_path( geometry: ShapeGeometry, width: u32, @@ -1645,9 +1692,10 @@ fn shape_path( let width = width.max(1) as f32; let height = height.max(1) as f32; match geometry { - ShapeGeometry::Rectangle | ShapeGeometry::RoundedRectangle => { + ShapeGeometry::Rectangle => { polygon_path(&[(0.0, 0.0), (width, 0.0), (width, height), (0.0, height)]) } + ShapeGeometry::RoundedRectangle => rounded_rectangle_path(width, height), ShapeGeometry::Ellipse => ellipse_path(width as u32, height as u32), ShapeGeometry::Triangle => polygon_path(&[(width / 2.0, 0.0), (width, height), (0.0, height)]), ShapeGeometry::RightTriangle => polygon_path(&[(0.0, 0.0), (width, height), (0.0, height)]), @@ -1799,6 +1847,55 @@ fn ellipse_path(width: u32, height: u32) -> Result Result { + let radius = (width.min(height) * 0.18).max(2.0); + let radius = radius.min(width / 2.0).min(height / 2.0); + let control = radius * (4.0 * (2.0_f32.sqrt() - 1.0) / 3.0); + let mut builder = PathBuilder::new(); + builder.move_to(radius, 0.0); + builder.line_to(width - radius, 0.0); + builder.cubic_to( + width - radius + control, + 0.0, + width, + radius - control, + width, + radius, + ); + builder.line_to(width, height - radius); + builder.cubic_to( + width, + height - radius + control, + width - radius + control, + height, + width - radius, + height, + ); + builder.line_to(radius, height); + builder.cubic_to( + radius - control, + height, + 0.0, + height - radius + control, + 0.0, + height - radius, + ); + builder.line_to(0.0, radius); + builder.cubic_to( + 0.0, + radius - control, + radius - control, + 0.0, + radius, + 0.0, + ); + builder.close(); + builder.finish().ok_or_else(|| PresentationArtifactError::RenderFailed { + action: "render_preview".to_string(), + message: "failed to build rounded rectangle path".to_string(), + }) +} + fn regular_polygon_path( sides: usize, width: f32, @@ -2171,3 +2268,79 @@ fn resolve_render_slide_index( } Ok(0) } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn wrapped_lines_skip_leading_spaces() { + let text = "One strong visual lands the story."; + let style = TextStyle::default(); + let glyphs = styled_glyphs(text, &style, &RichTextState::default(), None); + let prefix = "One strong visual lands the"; + let width = glyphs + .iter() + .take(prefix.chars().count()) + .map(|glyph| measure_glyph_width(glyph, 14)) + .sum(); + let lines = layout_text_lines( + &glyphs, + TextRenderConfig { + width, + font_px: 14, + line_height: 18, + baseline_offset: 12, + wrap: TextWrapMode::Square, + vertical_alignment: TextVerticalAlignment::Top, + alignment: TextAlignment::Left, + }, + ); + + assert_eq!(lines.len(), 2); + assert_eq!( + lines[0].glyphs.iter().map(|glyph| glyph.ch).collect::(), + prefix + ); + assert_eq!( + lines[1].glyphs.iter().map(|glyph| glyph.ch).collect::(), + "story." + ); + } + + #[test] + fn wrapped_lines_trim_trailing_spaces() { + let text = "One strong visual lands the story."; + let style = TextStyle::default(); + let glyphs = styled_glyphs(text, &style, &RichTextState::default(), None); + let prefix = "One strong visual lands the "; + let width = glyphs + .iter() + .take(prefix.chars().count()) + .map(|glyph| measure_glyph_width(glyph, 14)) + .sum(); + let lines = layout_text_lines( + &glyphs, + TextRenderConfig { + width, + font_px: 14, + line_height: 18, + baseline_offset: 12, + wrap: TextWrapMode::Square, + vertical_alignment: TextVerticalAlignment::Top, + alignment: TextAlignment::Left, + }, + ); + + assert_eq!(lines.len(), 2); + assert_eq!( + lines[0].glyphs.iter().map(|glyph| glyph.ch).collect::(), + "One strong visual lands the" + ); + assert_eq!( + lines[1].glyphs.iter().map(|glyph| glyph.ch).collect::(), + "story." + ); + } +} diff --git a/codex-rs/artifact-presentation/src/tests.rs b/codex-rs/artifact-presentation/src/tests.rs index 793d646976..6f468b586c 100644 --- a/codex-rs/artifact-presentation/src/tests.rs +++ b/codex-rs/artifact-presentation/src/tests.rs @@ -132,7 +132,7 @@ fn manager_can_import_exported_presentation() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let image_path = temp_dir.path().join("stacking-source.png"); + image::RgbaImage::from_pixel(40, 40, image::Rgba([0x2A, 0x80, 0xD7, 0xFF])) + .save(&image_path)?; + + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Preview stacking parity" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({ + "background_fill": "#D9EAF7" + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "path": image_path, + "position": { "left": 160, "top": 80, "width": 180, "height": 180 }, + "fit": "stretch" + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rounded_rectangle", + "position": { "left": 120, "top": 200, "width": 220, "height": 120 }, + "fill": "#FFFFFF" + }), + }, + temp_dir.path(), + )?; + + let target_path = temp_dir.path().join("stacking-preview.png"); + let response = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "export_preview".to_string(), + args: serde_json::json!({ + "path": target_path, + "slide_index": 0 + }), + }, + temp_dir.path(), + )?; + let preview = image::open( + response + .exported_paths + .first() + .expect("exported preview path present"), + )?; + + assert_eq!(preview.get_pixel(200, 220).0, [0x2A, 0x80, 0xD7, 0xFF]); + assert_eq!(preview.get_pixel(200, 290).0, [0xFF, 0xFF, 0xFF, 0xFF]); + assert_eq!(preview.get_pixel(122, 202).0, [0xD9, 0xEA, 0xF7, 0xFF]); + Ok(()) +} + #[test] fn export_preview_writes_all_slides_with_stable_names() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; diff --git a/codex-rs/core/tests/suite/presentation_artifact.rs b/codex-rs/core/tests/suite/presentation_artifact.rs index f3d6e743f9..affd7b2a9b 100644 --- a/codex-rs/core/tests/suite/presentation_artifact.rs +++ b/codex-rs/core/tests/suite/presentation_artifact.rs @@ -3,6 +3,7 @@ use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use codex_core::CodexAuth; +use codex_core::features::Feature; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::InputModality; @@ -46,7 +47,12 @@ async fn presentation_artifact_render_preview_returns_inline_image() -> anyhow:: cwd, session_configured, .. - } = test_codex().build(&server).await?; + } = test_codex() + .with_config(|config| { + config.features.enable(Feature::Artifact); + }) + .build(&server) + .await?; let call_id = "presentation-render-preview"; let arguments = serde_json::json!({ @@ -206,6 +212,7 @@ async fn presentation_artifact_render_preview_fails_for_text_only_model() -> any let TestCodex { codex, cwd, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { + config.features.enable(Feature::Artifact); config.model = Some(model_slug.to_string()); }) .build(&server) From 8da7e4bdae61881b2241b6e6225aa8d44c67789f Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Tue, 3 Mar 2026 10:25:51 -0800 Subject: [PATCH 05/17] app-server-protocol: export flat v2 schema bundle (#13324) ## Summary - add an `--experimental` flag to the export binary and thread the option through TypeScript and JSON schema generation - flatten the v2 schema bundle into a datamodel-code-generator-friendly `codex_app_server_protocol.v2.schemas.json` export - retarget shared helper refs to namespaced v2 definitions, add coverage for the new export behavior, and vendor the generated schema fixtures ## Validation - `cargo test -p codex-app-server-protocol` (71 unit tests and bin targets passed locally; the final schema fixture integration target was revalidated via fresh schema regeneration and a tree diff) - `./target/debug/write_schema_fixtures --schema-root ` - `diff -rq app-server-protocol/schema ` ## Tickets - None --- .../codex_app_server_protocol.schemas.json | 18 +- .../codex_app_server_protocol.v2.schemas.json | 13958 ++++++++++++++++ .../app-server-protocol/src/bin/export.rs | 14 +- codex-rs/app-server-protocol/src/export.rs | 635 +- 4 files changed, 14607 insertions(+), 18 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 4cf3933e75..ddeee0ce73 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -5,7 +5,7 @@ "properties": { "read": { "items": { - "$ref": "#/definitions/AbsolutePathBuf" + "$ref": "#/definitions/v2/AbsolutePathBuf" }, "type": [ "array", @@ -14,7 +14,7 @@ }, "write": { "items": { - "$ref": "#/definitions/AbsolutePathBuf" + "$ref": "#/definitions/v2/AbsolutePathBuf" }, "type": [ "array", @@ -1382,7 +1382,7 @@ "thread_id": { "allOf": [ { - "$ref": "#/definitions/ThreadId" + "$ref": "#/definitions/v2/ThreadId" } ], "description": "Thread ID of the receiver/new agent." @@ -1420,7 +1420,7 @@ "thread_id": { "allOf": [ { - "$ref": "#/definitions/ThreadId" + "$ref": "#/definitions/v2/ThreadId" } ], "description": "Thread ID of the receiver/new agent." @@ -4732,7 +4732,7 @@ "properties": { "read": { "items": { - "$ref": "#/definitions/AbsolutePathBuf" + "$ref": "#/definitions/v2/AbsolutePathBuf" }, "type": [ "array", @@ -4741,7 +4741,7 @@ }, "write": { "items": { - "$ref": "#/definitions/AbsolutePathBuf" + "$ref": "#/definitions/v2/AbsolutePathBuf" }, "type": [ "array", @@ -7131,7 +7131,7 @@ "properties": { "content": { "items": { - "$ref": "#/definitions/UserInput" + "$ref": "#/definitions/v2/UserInput" }, "type": "array" }, @@ -7169,7 +7169,7 @@ "phase": { "anyOf": [ { - "$ref": "#/definitions/MessagePhase" + "$ref": "#/definitions/v2/MessagePhase" }, { "type": "null" @@ -7254,7 +7254,7 @@ { "properties": { "action": { - "$ref": "#/definitions/WebSearchAction" + "$ref": "#/definitions/v2/WebSearchAction" }, "id": { "type": "string" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json new file mode 100644 index 0000000000..0f8e83d572 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -0,0 +1,13958 @@ +{ + "$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" + }, + "Account": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyAccountType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyAccount", + "type": "object" + }, + { + "properties": { + "email": { + "type": "string" + }, + "planType": { + "$ref": "#/definitions/PlanType" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "ChatgptAccountType", + "type": "string" + } + }, + "required": [ + "email", + "planType", + "type" + ], + "title": "ChatgptAccount", + "type": "object" + } + ] + }, + "AccountLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "title": "AccountLoginCompletedNotification", + "type": "object" + }, + "AccountRateLimitsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "rateLimits": { + "$ref": "#/definitions/RateLimitSnapshot" + } + }, + "required": [ + "rateLimits" + ], + "title": "AccountRateLimitsUpdatedNotification", + "type": "object" + }, + "AccountUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authMode": { + "anyOf": [ + { + "$ref": "#/definitions/AuthMode" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + } + }, + "title": "AccountUpdatedNotification", + "type": "object" + }, + "AgentMessageContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Text" + ], + "title": "TextAgentMessageContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextAgentMessageContent", + "type": "object" + } + ] + }, + "AgentMessageDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "AgentMessageDeltaNotification", + "type": "object" + }, + "AgentStatus": { + "description": "Agent lifecycle status, derived from emitted events.", + "oneOf": [ + { + "description": "Agent is waiting for initialization.", + "enum": [ + "pending_init" + ], + "type": "string" + }, + { + "description": "Agent is currently running.", + "enum": [ + "running" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Agent is done. Contains the final assistant message.", + "properties": { + "completed": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "completed" + ], + "title": "CompletedAgentStatus", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Agent encountered an error.", + "properties": { + "errored": { + "type": "string" + } + }, + "required": [ + "errored" + ], + "title": "ErroredAgentStatus", + "type": "object" + }, + { + "description": "Agent has been shutdown.", + "enum": [ + "shutdown" + ], + "type": "string" + }, + { + "description": "Agent is not found.", + "enum": [ + "not_found" + ], + "type": "string" + } + ] + }, + "AnalyticsConfig": { + "additionalProperties": true, + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "AppBranding": { + "description": "EXPERIMENTAL - app metadata returned by app-list APIs.", + "properties": { + "category": { + "type": [ + "string", + "null" + ] + }, + "developer": { + "type": [ + "string", + "null" + ] + }, + "isDiscoverableApp": { + "type": "boolean" + }, + "privacyPolicy": { + "type": [ + "string", + "null" + ] + }, + "termsOfService": { + "type": [ + "string", + "null" + ] + }, + "website": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "isDiscoverableApp" + ], + "type": "object" + }, + "AppConfig": { + "properties": { + "default_tools_approval_mode": { + "anyOf": [ + { + "$ref": "#/definitions/AppToolApproval" + }, + { + "type": "null" + } + ] + }, + "default_tools_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "destructive_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "enabled": { + "default": true, + "type": "boolean" + }, + "open_world_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/AppToolsConfig" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "AppInfo": { + "description": "EXPERIMENTAL - app metadata returned by app-list APIs.", + "properties": { + "appMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/AppMetadata" + }, + { + "type": "null" + } + ] + }, + "branding": { + "anyOf": [ + { + "$ref": "#/definitions/AppBranding" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "distributionChannel": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "installUrl": { + "type": [ + "string", + "null" + ] + }, + "isAccessible": { + "default": false, + "type": "boolean" + }, + "isEnabled": { + "default": true, + "description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", + "type": "boolean" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "logoUrl": { + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "AppListUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - notification emitted when the app list changes.", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/AppInfo" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "AppListUpdatedNotification", + "type": "object" + }, + "AppMetadata": { + "properties": { + "categories": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "developer": { + "type": [ + "string", + "null" + ] + }, + "firstPartyRequiresInstall": { + "type": [ + "boolean", + "null" + ] + }, + "firstPartyType": { + "type": [ + "string", + "null" + ] + }, + "review": { + "anyOf": [ + { + "$ref": "#/definitions/AppReview" + }, + { + "type": "null" + } + ] + }, + "screenshots": { + "items": { + "$ref": "#/definitions/AppScreenshot" + }, + "type": [ + "array", + "null" + ] + }, + "seoDescription": { + "type": [ + "string", + "null" + ] + }, + "showInComposerWhenUnlinked": { + "type": [ + "boolean", + "null" + ] + }, + "subCategories": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "version": { + "type": [ + "string", + "null" + ] + }, + "versionId": { + "type": [ + "string", + "null" + ] + }, + "versionNotes": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "AppReview": { + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "AppScreenshot": { + "properties": { + "fileId": { + "type": [ + "string", + "null" + ] + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "userPrompt": { + "type": "string" + } + }, + "required": [ + "userPrompt" + ], + "type": "object" + }, + "AppToolApproval": { + "enum": [ + "auto", + "prompt", + "approve" + ], + "type": "string" + }, + "AppToolConfig": { + "properties": { + "approval_mode": { + "anyOf": [ + { + "$ref": "#/definitions/AppToolApproval" + }, + { + "type": "null" + } + ] + }, + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "AppToolsConfig": { + "type": "object" + }, + "AppsConfig": { + "properties": { + "_default": { + "anyOf": [ + { + "$ref": "#/definitions/AppsDefaultConfig" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "type": "object" + }, + "AppsDefaultConfig": { + "properties": { + "destructive_enabled": { + "default": true, + "type": "boolean" + }, + "enabled": { + "default": true, + "type": "boolean" + }, + "open_world_enabled": { + "default": true, + "type": "boolean" + } + }, + "type": "object" + }, + "AppsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - list available apps/connectors.", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "forceRefetch": { + "description": "When true, bypass app caches and fetch the latest data from sources.", + "type": "boolean" + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "threadId": { + "description": "Optional thread id used to evaluate app feature gating from that thread's config.", + "type": [ + "string", + "null" + ] + } + }, + "title": "AppsListParams", + "type": "object" + }, + "AppsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - app list response.", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/AppInfo" + }, + "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": "AppsListResponse", + "type": "object" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "reject": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "reject" + ], + "title": "RejectAskForApproval", + "type": "object" + } + ] + }, + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "enum": [ + "apikey" + ], + "type": "string" + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "enum": [ + "chatgpt" + ], + "type": "string" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "enum": [ + "chatgptAuthTokens" + ], + "type": "string" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "_meta": true, + "content": { + "items": true, + "type": "array" + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "CancelLoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "loginId": { + "type": "string" + } + }, + "required": [ + "loginId" + ], + "title": "CancelLoginAccountParams", + "type": "object" + }, + "CancelLoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/CancelLoginAccountStatus" + } + }, + "required": [ + "status" + ], + "title": "CancelLoginAccountResponse", + "type": "object" + }, + "CancelLoginAccountStatus": { + "enum": [ + "canceled", + "notFound" + ], + "type": "string" + }, + "ClientInfo": { + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ClientRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Request from the client to the server.", + "oneOf": [ + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "initialize" + ], + "title": "InitializeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/InitializeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "InitializeRequest", + "type": "object" + }, + { + "description": "NEW APIs", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/start" + ], + "title": "Thread/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/resume" + ], + "title": "Thread/resumeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadResumeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/resumeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/fork" + ], + "title": "Thread/forkRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadForkParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/forkRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/archive" + ], + "title": "Thread/archiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/archiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/unsubscribe" + ], + "title": "Thread/unsubscribeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadUnsubscribeParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/unsubscribeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/name/set" + ], + "title": "Thread/name/setRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadSetNameParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/name/setRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/unarchive" + ], + "title": "Thread/unarchiveRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadUnarchiveParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/unarchiveRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadCompactStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/compact/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/rollback" + ], + "title": "Thread/rollbackRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRollbackParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/rollbackRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/list" + ], + "title": "Thread/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/loaded/list" + ], + "title": "Thread/loaded/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadLoadedListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/loaded/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/read" + ], + "title": "Thread/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/list" + ], + "title": "Skills/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/list" + ], + "title": "Skills/remote/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsRemoteReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/remote/export" + ], + "title": "Skills/remote/exportRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsRemoteWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/remote/exportRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "app/list" + ], + "title": "App/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AppsListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "App/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "skills/config/write" + ], + "title": "Skills/config/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/SkillsConfigWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Skills/config/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/start" + ], + "title": "Turn/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/steer" + ], + "title": "Turn/steerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnSteerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/steerRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/interrupt" + ], + "title": "Turn/interruptRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnInterruptParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/interruptRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "review/start" + ], + "title": "Review/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReviewStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Review/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "model/list" + ], + "title": "Model/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ModelListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Model/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "experimentalFeature/list" + ], + "title": "ExperimentalFeature/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExperimentalFeatureListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExperimentalFeature/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServer/oauth/login" + ], + "title": "McpServer/oauth/loginRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpServerOauthLoginParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServer/oauth/loginRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/mcpServer/reload" + ], + "title": "Config/mcpServer/reloadRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Config/mcpServer/reloadRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "mcpServerStatus/list" + ], + "title": "McpServerStatus/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ListMcpServerStatusParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "McpServerStatus/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "windowsSandbox/setupStart" + ], + "title": "WindowsSandbox/setupStartRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/WindowsSandboxSetupStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "WindowsSandbox/setupStartRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/start" + ], + "title": "Account/login/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/startRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/login/cancel" + ], + "title": "Account/login/cancelRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CancelLoginAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/login/cancelRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/logout" + ], + "title": "Account/logoutRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/logoutRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/rateLimits/read" + ], + "title": "Account/rateLimits/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "Account/rateLimits/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "feedback/upload" + ], + "title": "Feedback/uploadRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FeedbackUploadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Feedback/uploadRequest", + "type": "object" + }, + { + "description": "Execute a command (argv vector) under the server's sandbox.", + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "command/exec" + ], + "title": "Command/execRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Command/execRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/read" + ], + "title": "Config/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigReadParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "externalAgentConfig/detect" + ], + "title": "ExternalAgentConfig/detectRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExternalAgentConfigDetectParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExternalAgentConfig/detectRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "externalAgentConfig/import" + ], + "title": "ExternalAgentConfig/importRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExternalAgentConfigImportParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExternalAgentConfig/importRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/value/write" + ], + "title": "Config/value/writeRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigValueWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/value/writeRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "config/batchWrite" + ], + "title": "Config/batchWriteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigBatchWriteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Config/batchWriteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "configRequirements/read" + ], + "title": "ConfigRequirements/readRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ConfigRequirements/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "account/read" + ], + "title": "Account/readRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetAccountParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Account/readRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "fuzzyFileSearch" + ], + "title": "FuzzyFileSearchRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "FuzzyFileSearchRequest", + "type": "object" + } + ], + "title": "ClientRequest" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "enum": [ + "contextWindowExceeded", + "usageLimitExceeded", + "serverOverloaded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "httpConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "httpConnectionFailed" + ], + "title": "HttpConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Failed to connect to the response SSE stream.", + "properties": { + "responseStreamConnectionFailed": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamConnectionFailed" + ], + "title": "ResponseStreamConnectionFailedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "properties": { + "responseStreamDisconnected": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseStreamDisconnected" + ], + "title": "ResponseStreamDisconnectedCodexErrorInfo", + "type": "object" + }, + { + "additionalProperties": false, + "description": "Reached the retry limit for responses.", + "properties": { + "responseTooManyFailedAttempts": { + "properties": { + "httpStatusCode": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + } + }, + "required": [ + "responseTooManyFailedAttempts" + ], + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo", + "type": "object" + } + ] + }, + "CollabAgentRef": { + "properties": { + "agent_nickname": { + "description": "Optional nickname assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "agent_role": { + "description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver/new agent." + } + }, + "required": [ + "thread_id" + ], + "type": "object" + }, + "CollabAgentState": { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CollabAgentStatus" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "CollabAgentStatus": { + "enum": [ + "pendingInit", + "running", + "completed", + "errored", + "shutdown", + "notFound" + ], + "type": "string" + }, + "CollabAgentStatusEntry": { + "properties": { + "agent_nickname": { + "description": "Optional nickname assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "agent_role": { + "description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the agent." + }, + "thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver/new agent." + } + }, + "required": [ + "status", + "thread_id" + ], + "type": "object" + }, + "CollabAgentTool": { + "enum": [ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent" + ], + "type": "string" + }, + "CollabAgentToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "CollaborationModeMask": { + "description": "EXPERIMENTAL - collaboration mode preset metadata for clients.", + "properties": { + "mode": { + "anyOf": [ + { + "$ref": "#/definitions/ModeKind" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "CommandAction": { + "oneOf": [ + { + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "name", + "path", + "type" + ], + "title": "ReadCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "ListFilesCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "SearchCommandAction", + "type": "object" + }, + { + "properties": { + "command": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType", + "type": "string" + } + }, + "required": [ + "command", + "type" + ], + "title": "UnknownCommandAction", + "type": "object" + } + ] + }, + "CommandExecParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "timeoutMs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "command" + ], + "title": "CommandExecParams", + "type": "object" + }, + "CommandExecResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "exitCode": { + "format": "int32", + "type": "integer" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "title": "CommandExecResponse", + "type": "object" + }, + "CommandExecutionOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "CommandExecutionOutputDeltaNotification", + "type": "object" + }, + "CommandExecutionStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "Config": { + "additionalProperties": true, + "properties": { + "analytics": { + "anyOf": [ + { + "$ref": "#/definitions/AnalyticsConfig" + }, + { + "type": "null" + } + ] + }, + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "compact_prompt": { + "type": [ + "string", + "null" + ] + }, + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "forced_chatgpt_workspace_id": { + "type": [ + "string", + "null" + ] + }, + "forced_login_method": { + "anyOf": [ + { + "$ref": "#/definitions/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_auto_compact_token_limit": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "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" + } + ] + }, + "profile": { + "type": [ + "string", + "null" + ] + }, + "profiles": { + "additionalProperties": { + "$ref": "#/definitions/ProfileV2" + }, + "default": {}, + "type": "object" + }, + "review_model": { + "type": [ + "string", + "null" + ] + }, + "sandbox_mode": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandbox_workspace_write": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxWorkspaceWrite" + }, + { + "type": "null" + } + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/ToolsV2" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ConfigBatchWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "edits": { + "items": { + "$ref": "#/definitions/ConfigEdit" + }, + "type": "array" + }, + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "edits" + ], + "title": "ConfigBatchWriteParams", + "type": "object" + }, + "ConfigEdit": { + "properties": { + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "type": "object" + }, + "ConfigLayer": { + "properties": { + "config": true, + "disabledReason": { + "type": [ + "string", + "null" + ] + }, + "name": { + "$ref": "#/definitions/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "config", + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerMetadata": { + "properties": { + "name": { + "$ref": "#/definitions/ConfigLayerSource" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "ConfigLayerSource": { + "oneOf": [ + { + "description": "Managed preferences layer delivered by MDM (macOS only).", + "properties": { + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "enum": [ + "mdm" + ], + "title": "MdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "domain", + "key", + "type" + ], + "title": "MdmConfigLayerSource", + "type": "object" + }, + { + "description": "Managed config layer from a file (usually `managed_config.toml`).", + "properties": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the system config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "system" + ], + "title": "SystemConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "SystemConfigLayerSource", + "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": { + "file": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "This is the path to the user's config.toml file, though it is not guaranteed to exist." + }, + "type": { + "enum": [ + "user" + ], + "title": "UserConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "UserConfigLayerSource", + "type": "object" + }, + { + "description": "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + "properties": { + "dotCodexFolder": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "project" + ], + "title": "ProjectConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "dotCodexFolder", + "type" + ], + "title": "ProjectConfigLayerSource", + "type": "object" + }, + { + "description": "Session-layer overrides supplied via `-c`/`--config`.", + "properties": { + "type": { + "enum": [ + "sessionFlags" + ], + "title": "SessionFlagsConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SessionFlagsConfigLayerSource", + "type": "object" + }, + { + "description": "`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a \"best effort\" while we phase out `managed_config.toml` in favor of `requirements.toml`.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "legacyManagedConfigTomlFromFile" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "type" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSource", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "legacyManagedConfigTomlFromMdm" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSource", + "type": "object" + } + ] + }, + "ConfigReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwd": { + "description": "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + "type": [ + "string", + "null" + ] + }, + "includeLayers": { + "default": false, + "type": "boolean" + } + }, + "title": "ConfigReadParams", + "type": "object" + }, + "ConfigReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "config": { + "$ref": "#/definitions/Config" + }, + "layers": { + "items": { + "$ref": "#/definitions/ConfigLayer" + }, + "type": [ + "array", + "null" + ] + }, + "origins": { + "additionalProperties": { + "$ref": "#/definitions/ConfigLayerMetadata" + }, + "type": "object" + } + }, + "required": [ + "config", + "origins" + ], + "title": "ConfigReadResponse", + "type": "object" + }, + "ConfigRequirements": { + "properties": { + "allowedApprovalPolicies": { + "items": { + "$ref": "#/definitions/AskForApproval" + }, + "type": [ + "array", + "null" + ] + }, + "allowedSandboxModes": { + "items": { + "$ref": "#/definitions/SandboxMode" + }, + "type": [ + "array", + "null" + ] + }, + "allowedWebSearchModes": { + "items": { + "$ref": "#/definitions/WebSearchMode" + }, + "type": [ + "array", + "null" + ] + }, + "enforceResidency": { + "anyOf": [ + { + "$ref": "#/definitions/ResidencyRequirement" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "ConfigRequirementsReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "requirements": { + "anyOf": [ + { + "$ref": "#/definitions/ConfigRequirements" + }, + { + "type": "null" + } + ], + "description": "Null if no requirements are configured (e.g. no requirements.toml/MDM entries)." + } + }, + "title": "ConfigRequirementsReadResponse", + "type": "object" + }, + "ConfigValueWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/MergeStrategy" + }, + "value": true + }, + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "title": "ConfigValueWriteParams", + "type": "object" + }, + "ConfigWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "details": { + "description": "Optional extra guidance or error details.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Optional path to the config file that triggered the warning.", + "type": [ + "string", + "null" + ] + }, + "range": { + "anyOf": [ + { + "$ref": "#/definitions/TextRange" + }, + { + "type": "null" + } + ], + "description": "Optional range for the error location inside the config file." + }, + "summary": { + "description": "Concise summary of the warning.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "ConfigWarningNotification", + "type": "object" + }, + "ConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "filePath": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "Canonical path to the config file that was written." + }, + "overriddenMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/OverriddenMetadata" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/WriteStatus" + }, + "version": { + "type": "string" + } + }, + "required": [ + "filePath", + "status", + "version" + ], + "title": "ConfigWriteResponse", + "type": "object" + }, + "ContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageContentItem", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "OutputTextContentItem", + "type": "object" + } + ] + }, + "ContextCompactedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "ContextCompactedNotification", + "type": "object" + }, + "CreditsSnapshot": { + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + }, + "required": [ + "hasCredits", + "unlimited" + ], + "type": "object" + }, + "CustomPrompt": { + "properties": { + "argument_hint": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "string" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "DeprecationNoticeNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + } + }, + "required": [ + "summary" + ], + "title": "DeprecationNoticeNotification", + "type": "object" + }, + "Duration": { + "properties": { + "nanos": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "secs": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "nanos", + "secs" + ], + "type": "object" + }, + "DynamicToolCallOutputContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "inputText" + ], + "title": "InputTextDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextDynamicToolCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "imageUrl": { + "type": "string" + }, + "type": { + "enum": [ + "inputImage" + ], + "title": "InputImageDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "imageUrl", + "type" + ], + "title": "InputImageDynamicToolCallOutputContentItem", + "type": "object" + } + ] + }, + "DynamicToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "DynamicToolSpec": { + "properties": { + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "inputSchema", + "name" + ], + "type": "object" + }, + "ErrorNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "$ref": "#/definitions/TurnError" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "willRetry": { + "type": "boolean" + } + }, + "required": [ + "error", + "threadId", + "turnId", + "willRetry" + ], + "title": "ErrorNotification", + "type": "object" + }, + "EventMsg": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.", + "oneOf": [ + { + "description": "Error while executing a submission", + "properties": { + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "title": "ErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ErrorEventMsg", + "type": "object" + }, + { + "description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.", + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "warning" + ], + "title": "WarningEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "WarningEventMsg", + "type": "object" + }, + { + "description": "Realtime conversation lifecycle start event.", + "properties": { + "session_id": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "realtime_conversation_started" + ], + "title": "RealtimeConversationStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "RealtimeConversationStartedEventMsg", + "type": "object" + }, + { + "description": "Realtime conversation streaming payload event.", + "properties": { + "payload": { + "$ref": "#/definitions/RealtimeEvent" + }, + "type": { + "enum": [ + "realtime_conversation_realtime" + ], + "title": "RealtimeConversationRealtimeEventMsgType", + "type": "string" + } + }, + "required": [ + "payload", + "type" + ], + "title": "RealtimeConversationRealtimeEventMsg", + "type": "object" + }, + { + "description": "Realtime conversation lifecycle close event.", + "properties": { + "reason": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "realtime_conversation_closed" + ], + "title": "RealtimeConversationClosedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "RealtimeConversationClosedEventMsg", + "type": "object" + }, + { + "description": "Model routing changed from the requested model to a different model.", + "properties": { + "from_model": { + "type": "string" + }, + "reason": { + "$ref": "#/definitions/ModelRerouteReason" + }, + "to_model": { + "type": "string" + }, + "type": { + "enum": [ + "model_reroute" + ], + "title": "ModelRerouteEventMsgType", + "type": "string" + } + }, + "required": [ + "from_model", + "reason", + "to_model", + "type" + ], + "title": "ModelRerouteEventMsg", + "type": "object" + }, + { + "description": "Conversation history was compacted (either automatically or manually).", + "properties": { + "type": { + "enum": [ + "context_compacted" + ], + "title": "ContextCompactedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ContextCompactedEventMsg", + "type": "object" + }, + { + "description": "Conversation history was rolled back by dropping the last N user turns.", + "properties": { + "num_turns": { + "description": "Number of user turns that were removed from context.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "thread_rolled_back" + ], + "title": "ThreadRolledBackEventMsgType", + "type": "string" + } + }, + "required": [ + "num_turns", + "type" + ], + "title": "ThreadRolledBackEventMsg", + "type": "object" + }, + { + "description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.", + "properties": { + "collaboration_mode_kind": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": "default" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "task_started" + ], + "title": "TaskStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "turn_id", + "type" + ], + "title": "TaskStartedEventMsg", + "type": "object" + }, + { + "description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.", + "properties": { + "last_agent_message": { + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "task_complete" + ], + "title": "TaskCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "turn_id", + "type" + ], + "title": "TaskCompleteEventMsg", + "type": "object" + }, + { + "description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.", + "properties": { + "info": { + "anyOf": [ + { + "$ref": "#/definitions/TokenUsageInfo" + }, + { + "type": "null" + } + ] + }, + "rate_limits": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "token_count" + ], + "title": "TokenCountEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "TokenCountEventMsg", + "type": "object" + }, + { + "description": "Agent text output message", + "properties": { + "message": { + "type": "string" + }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ], + "default": null + }, + "type": { + "enum": [ + "agent_message" + ], + "title": "AgentMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "AgentMessageEventMsg", + "type": "object" + }, + { + "description": "User/system input message (what was sent to the model)", + "properties": { + "images": { + "description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "local_images": { + "default": [], + "description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.", + "items": { + "type": "string" + }, + "type": "array" + }, + "message": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `message` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "user_message" + ], + "title": "UserMessageEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "UserMessageEventMsg", + "type": "object" + }, + { + "description": "Agent text output delta message", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_delta" + ], + "title": "AgentMessageDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentMessageDeltaEventMsg", + "type": "object" + }, + { + "description": "Reasoning event from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning" + ], + "title": "AgentReasoningEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_delta" + ], + "title": "AgentReasoningDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningDeltaEventMsg", + "type": "object" + }, + { + "description": "Raw chain-of-thought from agent.", + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content" + ], + "title": "AgentReasoningRawContentEventMsgType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "AgentReasoningRawContentEventMsg", + "type": "object" + }, + { + "description": "Agent reasoning content delta event from agent.", + "properties": { + "delta": { + "type": "string" + }, + "type": { + "enum": [ + "agent_reasoning_raw_content_delta" + ], + "title": "AgentReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "type" + ], + "title": "AgentReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).", + "properties": { + "item_id": { + "default": "", + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "agent_reasoning_section_break" + ], + "title": "AgentReasoningSectionBreakEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AgentReasoningSectionBreakEventMsg", + "type": "object" + }, + { + "description": "Ack the client's configure message.", + "properties": { + "approval_policy": { + "allOf": [ + { + "$ref": "#/definitions/AskForApproval" + } + ], + "description": "When to escalate for approval for execution" + }, + "cwd": { + "description": "Working directory that should be treated as the *root* of the session.", + "type": "string" + }, + "forked_from_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ] + }, + "history_entry_count": { + "description": "Current number of entries in the history log.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "history_log_id": { + "description": "Identifier of the history log file (inode on Unix, 0 otherwise).", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "initial_messages": { + "description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.", + "items": { + "$ref": "#/definitions/EventMsg" + }, + "type": [ + "array", + "null" + ] + }, + "model": { + "description": "Tell the client what model is being queried.", + "type": "string" + }, + "model_provider_id": { + "type": "string" + }, + "network_proxy": { + "anyOf": [ + { + "$ref": "#/definitions/SessionNetworkProxyRuntime" + }, + { + "type": "null" + } + ], + "description": "Runtime proxy bind addresses, when the managed proxy was started for this session." + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "The effort the model is putting into reasoning about the user's request." + }, + "rollout_path": { + "description": "Path in which the rollout is stored. Can be `None` for ephemeral threads", + "type": [ + "string", + "null" + ] + }, + "sandbox_policy": { + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "How to sandbox commands executed in the system" + }, + "session_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "description": "Optional user-facing thread name (may be unset).", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "session_configured" + ], + "title": "SessionConfiguredEventMsgType", + "type": "string" + } + }, + "required": [ + "approval_policy", + "cwd", + "history_entry_count", + "history_log_id", + "model", + "model_provider_id", + "sandbox_policy", + "session_id", + "type" + ], + "title": "SessionConfiguredEventMsg", + "type": "object" + }, + { + "description": "Updated session metadata (e.g., thread name changes).", + "properties": { + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "thread_name": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "thread_name_updated" + ], + "title": "ThreadNameUpdatedEventMsgType", + "type": "string" + } + }, + "required": [ + "thread_id", + "type" + ], + "title": "ThreadNameUpdatedEventMsg", + "type": "object" + }, + { + "description": "Incremental MCP startup progress updates.", + "properties": { + "server": { + "description": "Server name being started.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/McpStartupStatus" + } + ], + "description": "Current startup status." + }, + "type": { + "enum": [ + "mcp_startup_update" + ], + "title": "McpStartupUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "server", + "status", + "type" + ], + "title": "McpStartupUpdateEventMsg", + "type": "object" + }, + { + "description": "Aggregate MCP startup completion summary.", + "properties": { + "cancelled": { + "items": { + "type": "string" + }, + "type": "array" + }, + "failed": { + "items": { + "$ref": "#/definitions/McpStartupFailure" + }, + "type": "array" + }, + "ready": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "mcp_startup_complete" + ], + "title": "McpStartupCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "cancelled", + "failed", + "ready", + "type" + ], + "title": "McpStartupCompleteEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the McpToolCallEnd event.", + "type": "string" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "type": { + "enum": [ + "mcp_tool_call_begin" + ], + "title": "McpToolCallBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "invocation", + "type" + ], + "title": "McpToolCallBeginEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Identifier for the corresponding McpToolCallBegin that finished.", + "type": "string" + }, + "duration": { + "$ref": "#/definitions/Duration" + }, + "invocation": { + "$ref": "#/definitions/McpInvocation" + }, + "result": { + "allOf": [ + { + "$ref": "#/definitions/Result_of_CallToolResult_or_String" + } + ], + "description": "Result of the tool call. Note this could be an error." + }, + "type": { + "enum": [ + "mcp_tool_call_end" + ], + "title": "McpToolCallEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "duration", + "invocation", + "result", + "type" + ], + "title": "McpToolCallEndEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_begin" + ], + "title": "WebSearchBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "type" + ], + "title": "WebSearchBeginEventMsg", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "call_id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "web_search_end" + ], + "title": "WebSearchEndEventMsgType", + "type": "string" + } + }, + "required": [ + "action", + "call_id", + "query", + "type" + ], + "title": "WebSearchEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the server is about to execute a command.", + "properties": { + "call_id": { + "description": "Identifier so this can be paired with the ExecCommandEnd event.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_begin" + ], + "title": "ExecCommandBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "turn_id", + "type" + ], + "title": "ExecCommandBeginEventMsg", + "type": "object" + }, + { + "description": "Incremental chunk of output from a running command.", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "chunk": { + "description": "Raw bytes from the stream (may not be valid UTF-8).", + "type": "string" + }, + "stream": { + "allOf": [ + { + "$ref": "#/definitions/ExecOutputStream" + } + ], + "description": "Which stream produced this chunk." + }, + "type": { + "enum": [ + "exec_command_output_delta" + ], + "title": "ExecCommandOutputDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "chunk", + "stream", + "type" + ], + "title": "ExecCommandOutputDeltaEventMsg", + "type": "object" + }, + { + "description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).", + "properties": { + "call_id": { + "description": "Identifier for the ExecCommandBegin that produced this chunk.", + "type": "string" + }, + "process_id": { + "description": "Process id associated with the running command.", + "type": "string" + }, + "stdin": { + "description": "Stdin sent to the running session.", + "type": "string" + }, + "type": { + "enum": [ + "terminal_interaction" + ], + "title": "TerminalInteractionEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "process_id", + "stdin", + "type" + ], + "title": "TerminalInteractionEventMsg", + "type": "object" + }, + { + "properties": { + "aggregated_output": { + "default": "", + "description": "Captured aggregated output", + "type": "string" + }, + "call_id": { + "description": "Identifier for the ExecCommandBegin that finished.", + "type": "string" + }, + "command": { + "description": "The command that was executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory if not the default cwd for the agent.", + "type": "string" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the command execution." + }, + "exit_code": { + "description": "The command's exit code.", + "format": "int32", + "type": "integer" + }, + "formatted_output": { + "description": "Formatted output from the command, as seen by the model.", + "type": "string" + }, + "interaction_input": { + "description": "Raw input sent to a unified exec session (if this is an interaction event).", + "type": [ + "string", + "null" + ] + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "process_id": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandSource" + } + ], + "default": "agent", + "description": "Where the command originated. Defaults to Agent for backward compatibility." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/ExecCommandStatus" + } + ], + "description": "Completion status for this command execution." + }, + "stderr": { + "description": "Captured stderr", + "type": "string" + }, + "stdout": { + "description": "Captured stdout", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this command belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "exec_command_end" + ], + "title": "ExecCommandEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "duration", + "exit_code", + "formatted_output", + "parsed_cmd", + "status", + "stderr", + "stdout", + "turn_id", + "type" + ], + "title": "ExecCommandEndEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent attached a local image via the view_image tool.", + "properties": { + "call_id": { + "description": "Identifier for the originating tool call.", + "type": "string" + }, + "path": { + "description": "Local filesystem path provided to the tool.", + "type": "string" + }, + "type": { + "enum": [ + "view_image_tool_call" + ], + "title": "ViewImageToolCallEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "path", + "type" + ], + "title": "ViewImageToolCallEventMsg", + "type": "object" + }, + { + "properties": { + "additional_permissions": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "description": "Optional additional filesystem permissions requested for this command." + }, + "approval_id": { + "description": "Identifier for this specific approval callback.\n\nWhen absent, the approval is for the command item itself (`call_id`). This is present for subcommand approvals (via execve intercept).", + "type": [ + "string", + "null" + ] + }, + "available_decisions": { + "description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.", + "items": { + "$ref": "#/definitions/ReviewDecision" + }, + "type": [ + "array", + "null" + ] + }, + "call_id": { + "description": "Identifier for the associated command execution item.", + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "network_approval_context": { + "anyOf": [ + { + "$ref": "#/definitions/NetworkApprovalContext" + }, + { + "type": "null" + } + ], + "description": "Optional network context for a blocked request that can be approved." + }, + "parsed_cmd": { + "items": { + "$ref": "#/definitions/ParsedCommand" + }, + "type": "array" + }, + "proposed_execpolicy_amendment": { + "description": "Proposed execpolicy amendment that can be applied to allow future runs.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "proposed_network_policy_amendments": { + "description": "Proposed network policy amendments (for example allow/deny this host in future).", + "items": { + "$ref": "#/definitions/NetworkPolicyAmendment" + }, + "type": [ + "array", + "null" + ] + }, + "reason": { + "description": "Optional human-readable reason for the approval (e.g. retry without sandbox).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "exec_approval_request" + ], + "title": "ExecApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "command", + "cwd", + "parsed_cmd", + "type" + ], + "title": "ExecApprovalRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated tool call, if available.", + "type": "string" + }, + "questions": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestion" + }, + "type": "array" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "request_user_input" + ], + "title": "RequestUserInputEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "questions", + "type" + ], + "title": "RequestUserInputEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_request" + ], + "title": "DynamicToolCallRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "callId", + "tool", + "turnId", + "type" + ], + "title": "DynamicToolCallRequestEventMsg", + "type": "object" + }, + { + "properties": { + "arguments": { + "description": "Dynamic tool call arguments." + }, + "call_id": { + "description": "Identifier for the corresponding DynamicToolCallRequest.", + "type": "string" + }, + "content_items": { + "description": "Dynamic tool response content items.", + "items": { + "$ref": "#/definitions/DynamicToolCallOutputContentItem" + }, + "type": "array" + }, + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "description": "The duration of the dynamic tool call." + }, + "error": { + "description": "Optional error text when the tool call failed before producing a response.", + "type": [ + "string", + "null" + ] + }, + "success": { + "description": "Whether the tool call succeeded.", + "type": "boolean" + }, + "tool": { + "description": "Dynamic tool name.", + "type": "string" + }, + "turn_id": { + "description": "Turn ID that this dynamic tool call belongs to.", + "type": "string" + }, + "type": { + "enum": [ + "dynamic_tool_call_response" + ], + "title": "DynamicToolCallResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "content_items", + "duration", + "success", + "tool", + "turn_id", + "type" + ], + "title": "DynamicToolCallResponseEventMsg", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "message": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "type": { + "enum": [ + "elicitation_request" + ], + "title": "ElicitationRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "message", + "server_name", + "type" + ], + "title": "ElicitationRequestEventMsg", + "type": "object" + }, + { + "properties": { + "call_id": { + "description": "Responses API call id for the associated patch apply call, if available.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "type": "object" + }, + "grant_root": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.", + "type": "string" + }, + "type": { + "enum": [ + "apply_patch_approval_request" + ], + "title": "ApplyPatchApprovalRequestEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "changes", + "type" + ], + "title": "ApplyPatchApprovalRequestEventMsg", + "type": "object" + }, + { + "description": "Notification advising the user that something they are using has been deprecated and should be phased out.", + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + }, + "type": { + "enum": [ + "deprecation_notice" + ], + "title": "DeprecationNoticeEventMsgType", + "type": "string" + } + }, + "required": [ + "summary", + "type" + ], + "title": "DeprecationNoticeEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": "string" + }, + "type": { + "enum": [ + "background_event" + ], + "title": "BackgroundEventEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "BackgroundEventEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "undo_started" + ], + "title": "UndoStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UndoStartedEventMsg", + "type": "object" + }, + { + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + }, + "type": { + "enum": [ + "undo_completed" + ], + "title": "UndoCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "success", + "type" + ], + "title": "UndoCompletedEventMsg", + "type": "object" + }, + { + "description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).", + "properties": { + "additional_details": { + "default": null, + "description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).", + "type": [ + "string", + "null" + ] + }, + "codex_error_info": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "message": { + "type": "string" + }, + "type": { + "enum": [ + "stream_error" + ], + "title": "StreamErrorEventMsgType", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "StreamErrorEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so front‑ends can show progress indicators.", + "properties": { + "auto_approved": { + "description": "If true, there was no ApplyPatchApprovalRequest for this patch.", + "type": "boolean" + }, + "call_id": { + "description": "Identifier so this can be paired with the PatchApplyEnd event.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "description": "The changes to be applied.", + "type": "object" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_begin" + ], + "title": "PatchApplyBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "auto_approved", + "call_id", + "changes", + "type" + ], + "title": "PatchApplyBeginEventMsg", + "type": "object" + }, + { + "description": "Notification that a patch application has finished.", + "properties": { + "call_id": { + "description": "Identifier for the PatchApplyBegin that finished.", + "type": "string" + }, + "changes": { + "additionalProperties": { + "$ref": "#/definitions/FileChange" + }, + "default": {}, + "description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).", + "type": "object" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/PatchApplyStatus" + } + ], + "description": "Completion status for this patch application." + }, + "stderr": { + "description": "Captured stderr (parser errors, IO failures, etc.).", + "type": "string" + }, + "stdout": { + "description": "Captured stdout (summary printed by apply_patch).", + "type": "string" + }, + "success": { + "description": "Whether the patch was applied successfully.", + "type": "boolean" + }, + "turn_id": { + "default": "", + "description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.", + "type": "string" + }, + "type": { + "enum": [ + "patch_apply_end" + ], + "title": "PatchApplyEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "status", + "stderr", + "stdout", + "success", + "type" + ], + "title": "PatchApplyEndEventMsg", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "turn_diff" + ], + "title": "TurnDiffEventMsgType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "TurnDiffEventMsg", + "type": "object" + }, + { + "description": "Response to GetHistoryEntryRequest.", + "properties": { + "entry": { + "anyOf": [ + { + "$ref": "#/definitions/HistoryEntry" + }, + { + "type": "null" + } + ], + "description": "The entry at the requested offset, if available and parseable." + }, + "log_id": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, + "offset": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "type": { + "enum": [ + "get_history_entry_response" + ], + "title": "GetHistoryEntryResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "log_id", + "offset", + "type" + ], + "title": "GetHistoryEntryResponseEventMsg", + "type": "object" + }, + { + "description": "List of MCP tools available to the agent.", + "properties": { + "auth_statuses": { + "additionalProperties": { + "$ref": "#/definitions/McpAuthStatus" + }, + "description": "Authentication status for each configured MCP server.", + "type": "object" + }, + "resource_templates": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "description": "Known resource templates grouped by server name.", + "type": "object" + }, + "resources": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "description": "Known resources grouped by server name.", + "type": "object" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "description": "Fully qualified tool name -> tool definition.", + "type": "object" + }, + "type": { + "enum": [ + "mcp_list_tools_response" + ], + "title": "McpListToolsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "auth_statuses", + "resource_templates", + "resources", + "tools", + "type" + ], + "title": "McpListToolsResponseEventMsg", + "type": "object" + }, + { + "description": "List of custom prompts available to the agent.", + "properties": { + "custom_prompts": { + "items": { + "$ref": "#/definitions/CustomPrompt" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_custom_prompts_response" + ], + "title": "ListCustomPromptsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "custom_prompts", + "type" + ], + "title": "ListCustomPromptsResponseEventMsg", + "type": "object" + }, + { + "description": "List of skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_skills_response" + ], + "title": "ListSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "List of remote skills available to the agent.", + "properties": { + "skills": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "list_remote_skills_response" + ], + "title": "ListRemoteSkillsResponseEventMsgType", + "type": "string" + } + }, + "required": [ + "skills", + "type" + ], + "title": "ListRemoteSkillsResponseEventMsg", + "type": "object" + }, + { + "description": "Remote skill downloaded to local cache.", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "remote_skill_downloaded" + ], + "title": "RemoteSkillDownloadedEventMsgType", + "type": "string" + } + }, + "required": [ + "id", + "name", + "path", + "type" + ], + "title": "RemoteSkillDownloadedEventMsg", + "type": "object" + }, + { + "description": "Notification that skill data may have been updated and clients may want to reload.", + "properties": { + "type": { + "enum": [ + "skills_update_available" + ], + "title": "SkillsUpdateAvailableEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SkillsUpdateAvailableEventMsg", + "type": "object" + }, + { + "properties": { + "explanation": { + "default": null, + "description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).", + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/PlanItemArg" + }, + "type": "array" + }, + "type": { + "enum": [ + "plan_update" + ], + "title": "PlanUpdateEventMsgType", + "type": "string" + } + }, + "required": [ + "plan", + "type" + ], + "title": "PlanUpdateEventMsg", + "type": "object" + }, + { + "properties": { + "reason": { + "$ref": "#/definitions/TurnAbortReason" + }, + "turn_id": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "turn_aborted" + ], + "title": "TurnAbortedEventMsgType", + "type": "string" + } + }, + "required": [ + "reason", + "type" + ], + "title": "TurnAbortedEventMsg", + "type": "object" + }, + { + "description": "Notification that the agent is shutting down.", + "properties": { + "type": { + "enum": [ + "shutdown_complete" + ], + "title": "ShutdownCompleteEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ShutdownCompleteEventMsg", + "type": "object" + }, + { + "description": "Entered review mode.", + "properties": { + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "type": { + "enum": [ + "entered_review_mode" + ], + "title": "EnteredReviewModeEventMsgType", + "type": "string" + }, + "user_facing_hint": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "target", + "type" + ], + "title": "EnteredReviewModeEventMsg", + "type": "object" + }, + { + "description": "Exited review mode with an optional final result to apply.", + "properties": { + "review_output": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewOutputEvent" + }, + { + "type": "null" + } + ] + }, + "type": { + "enum": [ + "exited_review_mode" + ], + "title": "ExitedReviewModeEventMsgType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExitedReviewModeEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "type": { + "enum": [ + "raw_response_item" + ], + "title": "RawResponseItemEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "type" + ], + "title": "RawResponseItemEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_started" + ], + "title": "ItemStartedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemStartedEventMsg", + "type": "object" + }, + { + "properties": { + "item": { + "$ref": "#/definitions/TurnItem" + }, + "thread_id": { + "$ref": "#/definitions/ThreadId" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "item_completed" + ], + "title": "ItemCompletedEventMsgType", + "type": "string" + } + }, + "required": [ + "item", + "thread_id", + "turn_id", + "type" + ], + "title": "ItemCompletedEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "agent_message_content_delta" + ], + "title": "AgentMessageContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "AgentMessageContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "plan_delta" + ], + "title": "PlanDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "PlanDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "summary_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_content_delta" + ], + "title": "ReasoningContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningContentDeltaEventMsg", + "type": "object" + }, + { + "properties": { + "content_index": { + "default": 0, + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "thread_id": { + "type": "string" + }, + "turn_id": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_raw_content_delta" + ], + "title": "ReasoningRawContentDeltaEventMsgType", + "type": "string" + } + }, + "required": [ + "delta", + "item_id", + "thread_id", + "turn_id", + "type" + ], + "title": "ReasoningRawContentDeltaEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_spawn_begin" + ], + "title": "CollabAgentSpawnBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "type" + ], + "title": "CollabAgentSpawnBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent spawn end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "new_agent_nickname": { + "description": "Optional nickname assigned to the new agent.", + "type": [ + "string", + "null" + ] + }, + "new_agent_role": { + "description": "Optional role assigned to the new agent.", + "type": [ + "string", + "null" + ] + }, + "new_thread_id": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadId" + }, + { + "type": "null" + } + ], + "description": "Thread ID of the newly spawned agent, if it was created." + }, + "prompt": { + "description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the new agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_spawn_end" + ], + "title": "CollabAgentSpawnEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentSpawnEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_agent_interaction_begin" + ], + "title": "CollabAgentInteractionBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabAgentInteractionBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: agent interaction end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.", + "type": "string" + }, + "receiver_agent_nickname": { + "description": "Optional nickname assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_agent_role": { + "description": "Optional role assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent." + }, + "type": { + "enum": [ + "collab_agent_interaction_end" + ], + "title": "CollabAgentInteractionEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "prompt", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabAgentInteractionEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting begin.", + "properties": { + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "receiver_agents": { + "description": "Optional nicknames/roles for receivers.", + "items": { + "$ref": "#/definitions/CollabAgentRef" + }, + "type": "array" + }, + "receiver_thread_ids": { + "description": "Thread ID of the receivers.", + "items": { + "$ref": "#/definitions/ThreadId" + }, + "type": "array" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_waiting_begin" + ], + "title": "CollabWaitingBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_ids", + "sender_thread_id", + "type" + ], + "title": "CollabWaitingBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: waiting end.", + "properties": { + "agent_statuses": { + "description": "Optional receiver metadata paired with final statuses.", + "items": { + "$ref": "#/definitions/CollabAgentStatusEntry" + }, + "type": "array" + }, + "call_id": { + "description": "ID of the waiting call.", + "type": "string" + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "statuses": { + "additionalProperties": { + "$ref": "#/definitions/AgentStatus" + }, + "description": "Last known status of the receiver agents reported to the sender agent.", + "type": "object" + }, + "type": { + "enum": [ + "collab_waiting_end" + ], + "title": "CollabWaitingEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "sender_thread_id", + "statuses", + "type" + ], + "title": "CollabWaitingEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_close_begin" + ], + "title": "CollabCloseBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabCloseBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: close end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_agent_nickname": { + "description": "Optional nickname assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_agent_role": { + "description": "Optional role assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent before the close." + }, + "type": { + "enum": [ + "collab_close_end" + ], + "title": "CollabCloseEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabCloseEndEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: resume begin.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_agent_nickname": { + "description": "Optional nickname assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_agent_role": { + "description": "Optional role assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "type": { + "enum": [ + "collab_resume_begin" + ], + "title": "CollabResumeBeginEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "type" + ], + "title": "CollabResumeBeginEventMsg", + "type": "object" + }, + { + "description": "Collab interaction: resume end.", + "properties": { + "call_id": { + "description": "Identifier for the collab tool call.", + "type": "string" + }, + "receiver_agent_nickname": { + "description": "Optional nickname assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_agent_role": { + "description": "Optional role assigned to the receiver agent.", + "type": [ + "string", + "null" + ] + }, + "receiver_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the receiver." + }, + "sender_thread_id": { + "allOf": [ + { + "$ref": "#/definitions/ThreadId" + } + ], + "description": "Thread ID of the sender." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/AgentStatus" + } + ], + "description": "Last known status of the receiver agent reported to the sender agent after resume." + }, + "type": { + "enum": [ + "collab_resume_end" + ], + "title": "CollabResumeEndEventMsgType", + "type": "string" + } + }, + "required": [ + "call_id", + "receiver_thread_id", + "sender_thread_id", + "status", + "type" + ], + "title": "CollabResumeEndEventMsg", + "type": "object" + } + ], + "title": "EventMsg" + }, + "ExecCommandSource": { + "enum": [ + "agent", + "user_shell", + "unified_exec_startup", + "unified_exec_interaction" + ], + "type": "string" + }, + "ExecCommandStatus": { + "enum": [ + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "ExecOutputStream": { + "enum": [ + "stdout", + "stderr" + ], + "type": "string" + }, + "ExperimentalFeature": { + "properties": { + "announcement": { + "description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "defaultEnabled": { + "description": "Whether this feature is enabled by default.", + "type": "boolean" + }, + "description": { + "description": "Short summary describing what the feature does. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "displayName": { + "description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "enabled": { + "description": "Whether this feature is currently enabled in the loaded config.", + "type": "boolean" + }, + "name": { + "description": "Stable key used in config.toml and CLI flag toggles.", + "type": "string" + }, + "stage": { + "allOf": [ + { + "$ref": "#/definitions/ExperimentalFeatureStage" + } + ], + "description": "Lifecycle stage of this feature flag." + } + }, + "required": [ + "defaultEnabled", + "enabled", + "name", + "stage" + ], + "type": "object" + }, + "ExperimentalFeatureListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ExperimentalFeatureListParams", + "type": "object" + }, + "ExperimentalFeatureListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/ExperimentalFeature" + }, + "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": "ExperimentalFeatureListResponse", + "type": "object" + }, + "ExperimentalFeatureStage": { + "oneOf": [ + { + "description": "Feature is available for user testing and feedback.", + "enum": [ + "beta" + ], + "type": "string" + }, + { + "description": "Feature is still being built and not ready for broad use.", + "enum": [ + "underDevelopment" + ], + "type": "string" + }, + { + "description": "Feature is production-ready.", + "enum": [ + "stable" + ], + "type": "string" + }, + { + "description": "Feature is deprecated and should be avoided.", + "enum": [ + "deprecated" + ], + "type": "string" + }, + { + "description": "Feature flag is retained only for backwards compatibility.", + "enum": [ + "removed" + ], + "type": "string" + } + ] + }, + "ExternalAgentConfigDetectParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwds": { + "description": "Zero or more working directories to include for repo-scoped detection.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "includeHome": { + "description": "If true, include detection under the user's home (~/.claude, ~/.codex, etc.).", + "type": "boolean" + } + }, + "title": "ExternalAgentConfigDetectParams", + "type": "object" + }, + "ExternalAgentConfigDetectResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "items": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItem" + }, + "type": "array" + } + }, + "required": [ + "items" + ], + "title": "ExternalAgentConfigDetectResponse", + "type": "object" + }, + "ExternalAgentConfigImportParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "migrationItems": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItem" + }, + "type": "array" + } + }, + "required": [ + "migrationItems" + ], + "title": "ExternalAgentConfigImportParams", + "type": "object" + }, + "ExternalAgentConfigImportResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportResponse", + "type": "object" + }, + "ExternalAgentConfigMigrationItem": { + "properties": { + "cwd": { + "description": "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + "type": [ + "string", + "null" + ] + }, + "description": { + "type": "string" + }, + "itemType": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItemType" + } + }, + "required": [ + "description", + "itemType" + ], + "type": "object" + }, + "ExternalAgentConfigMigrationItemType": { + "enum": [ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "MCP_SERVER_CONFIG" + ], + "type": "string" + }, + "FeedbackUploadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "classification": { + "type": "string" + }, + "extraLogFiles": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "includeLogs": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "classification", + "includeLogs" + ], + "title": "FeedbackUploadParams", + "type": "object" + }, + "FeedbackUploadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "FeedbackUploadResponse", + "type": "object" + }, + "FileChange": { + "oneOf": [ + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "add" + ], + "title": "AddFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "AddFileChange", + "type": "object" + }, + { + "properties": { + "content": { + "type": "string" + }, + "type": { + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "DeleteFileChange", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdateFileChangeType", + "type": "string" + }, + "unified_diff": { + "type": "string" + } + }, + "required": [ + "type", + "unified_diff" + ], + "title": "UpdateFileChange", + "type": "object" + } + ] + }, + "FileChangeOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "FileChangeOutputDeltaNotification", + "type": "object" + }, + "FileSystemPermissions": { + "properties": { + "read": { + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": [ + "array", + "null" + ] + }, + "write": { + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, + "FileUpdateChange": { + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/PatchChangeKind" + }, + "path": { + "type": "string" + } + }, + "required": [ + "diff", + "kind", + "path" + ], + "type": "object" + }, + "ForcedLoginMethod": { + "enum": [ + "chatgpt", + "api" + ], + "type": "string" + }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextFunctionCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "image_url": { + "type": "string" + }, + "type": { + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "image_url", + "type" + ], + "title": "InputImageFunctionCallOutputContentItem", + "type": "object" + } + ] + }, + "FunctionCallOutputPayload": { + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", + "properties": { + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" + }, + "success": { + "type": [ + "boolean", + "null" + ] + } + }, + "required": [ + "body" + ], + "type": "object" + }, + "FuzzyFileSearchParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cancellationToken": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": "string" + }, + "roots": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "query", + "roots" + ], + "title": "FuzzyFileSearchParams", + "type": "object" + }, + "FuzzyFileSearchResult": { + "description": "Superset of [`codex_file_search::FileMatch`]", + "properties": { + "file_name": { + "type": "string" + }, + "indices": { + "items": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "type": [ + "array", + "null" + ] + }, + "path": { + "type": "string" + }, + "root": { + "type": "string" + }, + "score": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "file_name", + "path", + "root", + "score" + ], + "type": "object" + }, + "FuzzyFileSearchSessionCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "sessionId": { + "type": "string" + } + }, + "required": [ + "sessionId" + ], + "title": "FuzzyFileSearchSessionCompletedNotification", + "type": "object" + }, + "FuzzyFileSearchSessionUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "files": { + "items": { + "$ref": "#/definitions/FuzzyFileSearchResult" + }, + "type": "array" + }, + "query": { + "type": "string" + }, + "sessionId": { + "type": "string" + } + }, + "required": [ + "files", + "query", + "sessionId" + ], + "title": "FuzzyFileSearchSessionUpdatedNotification", + "type": "object" + }, + "GetAccountParams": { + "$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" + } + }, + "title": "GetAccountParams", + "type": "object" + }, + "GetAccountRateLimitsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "rateLimits": { + "allOf": [ + { + "$ref": "#/definitions/RateLimitSnapshot" + } + ], + "description": "Backward-compatible single-bucket view; mirrors the historical payload." + }, + "rateLimitsByLimitId": { + "additionalProperties": { + "$ref": "#/definitions/RateLimitSnapshot" + }, + "description": "Multi-bucket view keyed by metered `limit_id` (for example, `codex`).", + "type": [ + "object", + "null" + ] + } + }, + "required": [ + "rateLimits" + ], + "title": "GetAccountRateLimitsResponse", + "type": "object" + }, + "GetAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/Account" + }, + { + "type": "null" + } + ] + }, + "requiresOpenaiAuth": { + "type": "boolean" + } + }, + "required": [ + "requiresOpenaiAuth" + ], + "title": "GetAccountResponse", + "type": "object" + }, + "GhostCommit": { + "description": "Details of a ghost commit created from a repository state.", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": [ + "string", + "null" + ] + }, + "preexisting_untracked_dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preexisting_untracked_files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "id", + "preexisting_untracked_dirs", + "preexisting_untracked_files" + ], + "type": "object" + }, + "GitInfo": { + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "HazelnutScope": { + "enum": [ + "example", + "workspace-shared", + "all-shared", + "personal" + ], + "type": "string" + }, + "HistoryEntry": { + "properties": { + "conversation_id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "ts": { + "format": "uint64", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "conversation_id", + "text", + "ts" + ], + "type": "object" + }, + "InitializeCapabilities": { + "description": "Client-declared capabilities negotiated during initialize.", + "properties": { + "experimentalApi": { + "default": false, + "description": "Opt into receiving experimental API methods and fields.", + "type": "boolean" + }, + "optOutNotificationMethods": { + "description": "Exact notification method names that should be suppressed for this connection (for example `codex/event/session_configured`).", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, + "InitializeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "capabilities": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeCapabilities" + }, + { + "type": "null" + } + ] + }, + "clientInfo": { + "$ref": "#/definitions/ClientInfo" + } + }, + "required": [ + "clientInfo" + ], + "title": "InitializeParams", + "type": "object" + }, + "InputModality": { + "description": "Canonical user-input modality tags advertised by a model.", + "oneOf": [ + { + "description": "Plain text turns and tool payloads.", + "enum": [ + "text" + ], + "type": "string" + }, + { + "description": "Image attachments included in user turns.", + "enum": [ + "image" + ], + "type": "string" + } + ] + }, + "ItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemCompletedNotification", + "type": "object" + }, + "ItemStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "ItemStartedNotification", + "type": "object" + }, + "ListMcpServerStatusParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a server-defined value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ListMcpServerStatusParams", + "type": "object" + }, + "ListMcpServerStatusResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/McpServerStatus" + }, + "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": "ListMcpServerStatusResponse", + "type": "object" + }, + "LocalShellAction": { + "oneOf": [ + { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": [ + "object", + "null" + ] + }, + "timeout_ms": { + "format": "uint64", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "type": { + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType", + "type": "string" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "command", + "type" + ], + "title": "ExecLocalShellAction", + "type": "object" + } + ] + }, + "LocalShellStatus": { + "enum": [ + "completed", + "in_progress", + "incomplete" + ], + "type": "string" + }, + "LoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "apiKey": { + "type": "string" + }, + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "apiKey", + "type" + ], + "title": "ApiKeyv2::LoginAccountParams", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "Chatgptv2::LoginAccountParams", + "type": "object" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", + "properties": { + "accessToken": { + "description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.", + "type": "string" + }, + "chatgptAccountId": { + "description": "Workspace/account identifier supplied by the client.", + "type": "string" + }, + "chatgptPlanType": { + "description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParamsType", + "type": "string" + } + }, + "required": [ + "accessToken", + "chatgptAccountId", + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParams", + "type": "object" + } + ], + "title": "LoginAccountParams" + }, + "LoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ApiKeyv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "authUrl": { + "description": "URL the client should open in a browser to initiate the OAuth flow.", + "type": "string" + }, + "loginId": { + "type": "string" + }, + "type": { + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "authUrl", + "loginId", + "type" + ], + "title": "Chatgptv2::LoginAccountResponse", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponseType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponse", + "type": "object" + } + ], + "title": "LoginAccountResponse" + }, + "LogoutAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutAccountResponse", + "type": "object" + }, + "MacOsAutomationValue": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "MacOsPermissions": { + "properties": { + "accessibility": { + "type": [ + "boolean", + "null" + ] + }, + "automations": { + "anyOf": [ + { + "$ref": "#/definitions/MacOsAutomationValue" + }, + { + "type": "null" + } + ] + }, + "calendar": { + "type": [ + "boolean", + "null" + ] + }, + "preferences": { + "anyOf": [ + { + "$ref": "#/definitions/MacOsPreferencesValue" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "MacOsPreferencesValue": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "McpAuthStatus": { + "enum": [ + "unsupported", + "notLoggedIn", + "bearerToken", + "oAuth" + ], + "type": "string" + }, + "McpInvocation": { + "properties": { + "arguments": { + "description": "Arguments to the tool call." + }, + "server": { + "description": "Name of the MCP server as defined in the config.", + "type": "string" + }, + "tool": { + "description": "Name of the tool as given by the MCP server.", + "type": "string" + } + }, + "required": [ + "server", + "tool" + ], + "type": "object" + }, + "McpServerOauthLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "name", + "success" + ], + "title": "McpServerOauthLoginCompletedNotification", + "type": "object" + }, + "McpServerOauthLoginParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "timeoutSecs": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "name" + ], + "title": "McpServerOauthLoginParams", + "type": "object" + }, + "McpServerOauthLoginResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "authorizationUrl": { + "type": "string" + } + }, + "required": [ + "authorizationUrl" + ], + "title": "McpServerOauthLoginResponse", + "type": "object" + }, + "McpServerRefreshResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerRefreshResponse", + "type": "object" + }, + "McpServerStatus": { + "properties": { + "authStatus": { + "$ref": "#/definitions/McpAuthStatus" + }, + "name": { + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/definitions/ResourceTemplate" + }, + "type": "array" + }, + "resources": { + "items": { + "$ref": "#/definitions/Resource" + }, + "type": "array" + }, + "tools": { + "additionalProperties": { + "$ref": "#/definitions/Tool" + }, + "type": "object" + } + }, + "required": [ + "authStatus", + "name", + "resourceTemplates", + "resources", + "tools" + ], + "type": "object" + }, + "McpStartupFailure": { + "properties": { + "error": { + "type": "string" + }, + "server": { + "type": "string" + } + }, + "required": [ + "error", + "server" + ], + "type": "object" + }, + "McpStartupStatus": { + "oneOf": [ + { + "properties": { + "state": { + "enum": [ + "starting" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "StartingMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "ready" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "ReadyMcpStartupStatus", + "type": "object" + }, + { + "properties": { + "error": { + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "error", + "state" + ], + "type": "object" + }, + { + "properties": { + "state": { + "enum": [ + "cancelled" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "title": "CancelledMcpStartupStatus", + "type": "object" + } + ] + }, + "McpToolCallError": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "McpToolCallProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "message", + "threadId", + "turnId" + ], + "title": "McpToolCallProgressNotification", + "type": "object" + }, + "McpToolCallResult": { + "properties": { + "content": { + "items": true, + "type": "array" + }, + "structuredContent": true + }, + "required": [ + "content" + ], + "type": "object" + }, + "McpToolCallStatus": { + "enum": [ + "inProgress", + "completed", + "failed" + ], + "type": "string" + }, + "MergeStrategy": { + "enum": [ + "replace", + "upsert" + ], + "type": "string" + }, + "MessagePhase": { + "description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.", + "oneOf": [ + { + "description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + "enum": [ + "commentary" + ], + "type": "string" + }, + { + "description": "The assistant's terminal answer text for the current turn.", + "enum": [ + "final_answer" + ], + "type": "string" + } + ] + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "Model": { + "properties": { + "availabilityNux": { + "anyOf": [ + { + "$ref": "#/definitions/ModelAvailabilityNux" + }, + { + "type": "null" + } + ] + }, + "defaultReasoningEffort": { + "$ref": "#/definitions/ReasoningEffort" + }, + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "inputModalities": { + "default": [ + "text", + "image" + ], + "items": { + "$ref": "#/definitions/InputModality" + }, + "type": "array" + }, + "isDefault": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "supportedReasoningEfforts": { + "items": { + "$ref": "#/definitions/ReasoningEffortOption" + }, + "type": "array" + }, + "supportsPersonality": { + "default": false, + "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] + }, + "upgradeInfo": { + "anyOf": [ + { + "$ref": "#/definitions/ModelUpgradeInfo" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "defaultReasoningEffort", + "description", + "displayName", + "hidden", + "id", + "isDefault", + "model", + "supportedReasoningEfforts" + ], + "type": "object" + }, + "ModelAvailabilityNux": { + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "ModelListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "includeHidden": { + "description": "When true, include models that are hidden from the default picker list.", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ModelListParams", + "type": "object" + }, + "ModelListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/Model" + }, + "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": "ModelListResponse", + "type": "object" + }, + "ModelRerouteReason": { + "enum": [ + "highRiskCyberActivity" + ], + "type": "string" + }, + "ModelReroutedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "fromModel": { + "type": "string" + }, + "reason": { + "$ref": "#/definitions/ModelRerouteReason" + }, + "threadId": { + "type": "string" + }, + "toModel": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "fromModel", + "reason", + "threadId", + "toModel", + "turnId" + ], + "title": "ModelReroutedNotification", + "type": "object" + }, + "ModelUpgradeInfo": { + "properties": { + "migrationMarkdown": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "modelLink": { + "type": [ + "string", + "null" + ] + }, + "upgradeCopy": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "NetworkApprovalContext": { + "properties": { + "host": { + "type": "string" + }, + "protocol": { + "$ref": "#/definitions/NetworkApprovalProtocol" + } + }, + "required": [ + "host", + "protocol" + ], + "type": "object" + }, + "NetworkApprovalProtocol": { + "enum": [ + "http", + "https", + "socks5Tcp", + "socks5Udp" + ], + "type": "string" + }, + "NetworkPolicyAmendment": { + "properties": { + "action": { + "$ref": "#/definitions/NetworkPolicyRuleAction" + }, + "host": { + "type": "string" + } + }, + "required": [ + "action", + "host" + ], + "type": "object" + }, + "NetworkPolicyRuleAction": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, + "NetworkRequirements": { + "properties": { + "allowLocalBinding": { + "type": [ + "boolean", + "null" + ] + }, + "allowUnixSockets": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "allowUpstreamProxy": { + "type": [ + "boolean", + "null" + ] + }, + "allowedDomains": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "dangerouslyAllowAllUnixSockets": { + "type": [ + "boolean", + "null" + ] + }, + "dangerouslyAllowNonLoopbackAdmin": { + "type": [ + "boolean", + "null" + ] + }, + "dangerouslyAllowNonLoopbackProxy": { + "type": [ + "boolean", + "null" + ] + }, + "deniedDomains": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "enabled": { + "type": [ + "boolean", + "null" + ] + }, + "httpPort": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "socksPort": { + "format": "uint16", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, + "OverriddenMetadata": { + "properties": { + "effectiveValue": true, + "message": { + "type": "string" + }, + "overridingLayer": { + "$ref": "#/definitions/ConfigLayerMetadata" + } + }, + "required": [ + "effectiveValue", + "message", + "overridingLayer" + ], + "type": "object" + }, + "ParsedCommand": { + "oneOf": [ + { + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "enum": [ + "read" + ], + "title": "ReadParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "name", + "path", + "type" + ], + "title": "ReadParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "ListFilesParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "SearchParsedCommand", + "type": "object" + }, + { + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType", + "type": "string" + } + }, + "required": [ + "cmd", + "type" + ], + "title": "UnknownParsedCommand", + "type": "object" + } + ] + }, + "PatchApplyStatus": { + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ], + "type": "string" + }, + "PatchChangeKind": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "AddPatchChangeKind", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DeletePatchChangeKind", + "type": "object" + }, + { + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UpdatePatchChangeKind", + "type": "object" + } + ] + }, + "PermissionProfile": { + "properties": { + "file_system": { + "anyOf": [ + { + "$ref": "#/definitions/FileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "macos": { + "anyOf": [ + { + "$ref": "#/definitions/MacOsPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "PlanDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "PlanDeltaNotification", + "type": "object" + }, + "PlanItemArg": { + "additionalProperties": false, + "properties": { + "status": { + "$ref": "#/definitions/StepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "PlanType": { + "enum": [ + "free", + "go", + "plus", + "pro", + "team", + "business", + "enterprise", + "edu", + "unknown" + ], + "type": "string" + }, + "ProductSurface": { + "enum": [ + "chatgpt", + "codex", + "api", + "atlas" + ], + "type": "string" + }, + "ProfileV2": { + "additionalProperties": true, + "properties": { + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "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" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchMode" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitSnapshot": { + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "limitId": { + "type": [ + "string", + "null" + ] + }, + "limitName": { + "type": [ + "string", + "null" + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "RateLimitWindow": { + "properties": { + "resetsAt": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "usedPercent": { + "format": "int32", + "type": "integer" + }, + "windowDurationMins": { + "format": "int64", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "usedPercent" + ], + "type": "object" + }, + "RawResponseItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "item": { + "$ref": "#/definitions/ResponseItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId", + "turnId" + ], + "title": "RawResponseItemCompletedNotification", + "type": "object" + }, + "ReadOnlyAccess": { + "oneOf": [ + { + "properties": { + "includePlatformDefaults": { + "default": true, + "type": "boolean" + }, + "readableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + }, + "type": { + "enum": [ + "restricted" + ], + "title": "RestrictedReadOnlyAccessType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "RestrictedReadOnlyAccess", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "fullAccess" + ], + "title": "FullAccessReadOnlyAccessType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "FullAccessReadOnlyAccess", + "type": "object" + } + ] + }, + "RealtimeAudioFrame": { + "properties": { + "data": { + "type": "string" + }, + "num_channels": { + "format": "uint16", + "minimum": 0.0, + "type": "integer" + }, + "sample_rate": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "samples_per_channel": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "data", + "num_channels", + "sample_rate" + ], + "type": "object" + }, + "RealtimeEvent": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "SessionUpdated": { + "properties": { + "instructions": { + "type": [ + "string", + "null" + ] + }, + "session_id": { + "type": "string" + } + }, + "required": [ + "session_id" + ], + "type": "object" + } + }, + "required": [ + "SessionUpdated" + ], + "title": "SessionUpdatedRealtimeEvent", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "AudioOut": { + "$ref": "#/definitions/RealtimeAudioFrame" + } + }, + "required": [ + "AudioOut" + ], + "title": "AudioOutRealtimeEvent", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "ConversationItemAdded": true + }, + "required": [ + "ConversationItemAdded" + ], + "title": "ConversationItemAddedRealtimeEvent", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "ConversationItemDone": { + "properties": { + "item_id": { + "type": "string" + } + }, + "required": [ + "item_id" + ], + "type": "object" + } + }, + "required": [ + "ConversationItemDone" + ], + "title": "ConversationItemDoneRealtimeEvent", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "HandoffRequested": { + "$ref": "#/definitions/RealtimeHandoffRequested" + } + }, + "required": [ + "HandoffRequested" + ], + "title": "HandoffRequestedRealtimeEvent", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "Error": { + "type": "string" + } + }, + "required": [ + "Error" + ], + "title": "ErrorRealtimeEvent", + "type": "object" + } + ] + }, + "RealtimeHandoffMessage": { + "properties": { + "role": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "required": [ + "role", + "text" + ], + "type": "object" + }, + "RealtimeHandoffRequested": { + "properties": { + "handoff_id": { + "type": "string" + }, + "input_transcript": { + "type": "string" + }, + "item_id": { + "type": "string" + }, + "messages": { + "items": { + "$ref": "#/definitions/RealtimeHandoffMessage" + }, + "type": "array" + } + }, + "required": [ + "handoff_id", + "input_transcript", + "item_id", + "messages" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningEffortOption": { + "properties": { + "description": { + "type": "string" + }, + "reasoningEffort": { + "$ref": "#/definitions/ReasoningEffort" + } + }, + "required": [ + "description", + "reasoningEffort" + ], + "type": "object" + }, + "ReasoningItemContent": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "ReasoningTextReasoningItemContent", + "type": "object" + }, + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextReasoningItemContent", + "type": "object" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "SummaryTextReasoningItemReasoningSummary", + "type": "object" + } + ] + }, + "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": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryPartAddedNotification", + "type": "object" + }, + "ReasoningSummaryTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "summaryIndex": { + "format": "int64", + "type": "integer" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "delta", + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "title": "ReasoningSummaryTextDeltaNotification", + "type": "object" + }, + "ReasoningTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "contentIndex": { + "format": "int64", + "type": "integer" + }, + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "contentIndex", + "delta", + "itemId", + "threadId", + "turnId" + ], + "title": "ReasoningTextDeltaNotification", + "type": "object" + }, + "RemoteSkillSummary": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "int64", + "type": "integer" + } + ] + }, + "RequestUserInputQuestion": { + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "items": { + "$ref": "#/definitions/RequestUserInputQuestionOption" + }, + "type": [ + "array", + "null" + ] + }, + "question": { + "type": "string" + } + }, + "required": [ + "header", + "id", + "question" + ], + "type": "object" + }, + "RequestUserInputQuestionOption": { + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "description", + "label" + ], + "type": "object" + }, + "ResidencyRequirement": { + "enum": [ + "us" + ], + "type": "string" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "_meta": true, + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResponseItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/ContentItem" + }, + "type": "array" + }, + "end_turn": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ] + }, + "role": { + "type": "string" + }, + "type": { + "enum": [ + "message" + ], + "title": "MessageResponseItemType", + "type": "string" + } + }, + "required": [ + "content", + "role", + "type" + ], + "title": "MessageResponseItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": null, + "items": { + "$ref": "#/definitions/ReasoningItemContent" + }, + "type": [ + "array", + "null" + ] + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string", + "writeOnly": true + }, + "summary": { + "items": { + "$ref": "#/definitions/ReasoningItemReasoningSummary" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "type" + ], + "title": "ReasoningResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Legacy id field retained for compatibility with older payloads.", + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "$ref": "#/definitions/LocalShellStatus" + }, + "type": { + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType", + "type": "string" + } + }, + "required": [ + "action", + "status", + "type" + ], + "title": "LocalShellCallResponseItem", + "type": "object" + }, + { + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "title": "FunctionCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "FunctionCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "input": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "input", + "name", + "type" + ], + "title": "CustomToolCallResponseItem", + "type": "object" + }, + { + "properties": { + "call_id": { + "type": "string" + }, + "output": { + "$ref": "#/definitions/FunctionCallOutputPayload" + }, + "type": { + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType", + "type": "string" + } + }, + "required": [ + "call_id", + "output", + "type" + ], + "title": "CustomToolCallOutputResponseItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ], + "writeOnly": true + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "WebSearchCallResponseItem", + "type": "object" + }, + { + "properties": { + "ghost_commit": { + "$ref": "#/definitions/GhostCommit" + }, + "type": { + "enum": [ + "ghost_snapshot" + ], + "title": "GhostSnapshotResponseItemType", + "type": "string" + } + }, + "required": [ + "ghost_commit", + "type" + ], + "title": "GhostSnapshotResponseItem", + "type": "object" + }, + { + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType", + "type": "string" + } + }, + "required": [ + "encrypted_content", + "type" + ], + "title": "CompactionResponseItem", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherResponseItemType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherResponseItem", + "type": "object" + } + ] + }, + "Result_of_CallToolResult_or_String": { + "oneOf": [ + { + "properties": { + "Ok": { + "$ref": "#/definitions/CallToolResult" + } + }, + "required": [ + "Ok" + ], + "title": "OkResult_of_CallToolResult_or_String", + "type": "object" + }, + { + "properties": { + "Err": { + "type": "string" + } + }, + "required": [ + "Err" + ], + "title": "ErrResult_of_CallToolResult_or_String", + "type": "object" + } + ] + }, + "ReviewCodeLocation": { + "description": "Location of the code related to a review finding.", + "properties": { + "absolute_file_path": { + "type": "string" + }, + "line_range": { + "$ref": "#/definitions/ReviewLineRange" + } + }, + "required": [ + "absolute_file_path", + "line_range" + ], + "type": "object" + }, + "ReviewDecision": { + "description": "User's decision in response to an ExecApprovalRequest.", + "oneOf": [ + { + "description": "User has approved this command and the agent should execute it.", + "enum": [ + "approved" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + "properties": { + "approved_execpolicy_amendment": { + "properties": { + "proposed_execpolicy_amendment": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "proposed_execpolicy_amendment" + ], + "type": "object" + } + }, + "required": [ + "approved_execpolicy_amendment" + ], + "title": "ApprovedExecpolicyAmendmentReviewDecision", + "type": "object" + }, + { + "description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.", + "enum": [ + "approved_for_session" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "User chose to persist a network policy rule (allow/deny) for future requests to the same host.", + "properties": { + "network_policy_amendment": { + "properties": { + "network_policy_amendment": { + "$ref": "#/definitions/NetworkPolicyAmendment" + } + }, + "required": [ + "network_policy_amendment" + ], + "type": "object" + } + }, + "required": [ + "network_policy_amendment" + ], + "title": "NetworkPolicyAmendmentReviewDecision", + "type": "object" + }, + { + "description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "enum": [ + "denied" + ], + "type": "string" + }, + { + "description": "User has denied this command and the agent should not do anything until the user's next command.", + "enum": [ + "abort" + ], + "type": "string" + } + ] + }, + "ReviewDelivery": { + "enum": [ + "inline", + "detached" + ], + "type": "string" + }, + "ReviewFinding": { + "description": "A single review finding describing an observed issue or recommendation.", + "properties": { + "body": { + "type": "string" + }, + "code_location": { + "$ref": "#/definitions/ReviewCodeLocation" + }, + "confidence_score": { + "format": "float", + "type": "number" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "title": { + "type": "string" + } + }, + "required": [ + "body", + "code_location", + "confidence_score", + "priority", + "title" + ], + "type": "object" + }, + "ReviewLineRange": { + "description": "Inclusive line range in a file associated with the finding.", + "properties": { + "end": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "ReviewOutputEvent": { + "description": "Structured review result produced by a child review session.", + "properties": { + "findings": { + "items": { + "$ref": "#/definitions/ReviewFinding" + }, + "type": "array" + }, + "overall_confidence_score": { + "format": "float", + "type": "number" + }, + "overall_correctness": { + "type": "string" + }, + "overall_explanation": { + "type": "string" + } + }, + "required": [ + "findings", + "overall_confidence_score", + "overall_correctness", + "overall_explanation" + ], + "type": "object" + }, + "ReviewStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "delivery": { + "anyOf": [ + { + "$ref": "#/definitions/ReviewDelivery" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`)." + }, + "target": { + "$ref": "#/definitions/ReviewTarget" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "target", + "threadId" + ], + "title": "ReviewStartParams", + "type": "object" + }, + "ReviewStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "reviewThreadId": { + "description": "Identifies the thread where the review runs.\n\nFor inline reviews, this is the original thread id. For detached reviews, this is the id of the new review thread.", + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "reviewThreadId", + "turn" + ], + "title": "ReviewStartResponse", + "type": "object" + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "properties": { + "type": { + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "UncommittedChangesReviewTarget", + "type": "object" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "properties": { + "branch": { + "type": "string" + }, + "type": { + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType", + "type": "string" + } + }, + "required": [ + "branch", + "type" + ], + "title": "BaseBranchReviewTarget", + "type": "object" + }, + { + "description": "Review the changes introduced by a specific commit.", + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType", + "type": "string" + } + }, + "required": [ + "sha", + "type" + ], + "title": "CommitReviewTarget", + "type": "object" + }, + { + "description": "Arbitrary instructions, equivalent to the old free-form prompt.", + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType", + "type": "string" + } + }, + "required": [ + "instructions", + "type" + ], + "title": "CustomReviewTarget", + "type": "object" + } + ] + }, + "SandboxMode": { + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ], + "type": "string" + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "access": { + "allOf": [ + { + "$ref": "#/definitions/ReadOnlyAccess" + } + ], + "default": { + "type": "fullAccess" + } + }, + "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" + }, + "readOnlyAccess": { + "allOf": [ + { + "$ref": "#/definitions/ReadOnlyAccess" + } + ], + "default": { + "type": "fullAccess" + } + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "SandboxWorkspaceWrite": { + "properties": { + "exclude_slash_tmp": { + "default": false, + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "type": "boolean" + }, + "network_access": { + "default": false, + "type": "boolean" + }, + "writable_roots": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "ServerNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Notification sent from the server to the client.", + "oneOf": [ + { + "description": "NEW NOTIFICATIONS", + "properties": { + "method": { + "enum": [ + "error" + ], + "title": "ErrorNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ErrorNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ErrorNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/started" + ], + "title": "Thread/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/status/changed" + ], + "title": "Thread/status/changedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadStatusChangedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/status/changedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/archived" + ], + "title": "Thread/archivedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArchivedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/archivedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/unarchived" + ], + "title": "Thread/unarchivedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadUnarchivedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/unarchivedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/closed" + ], + "title": "Thread/closedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadClosedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/closedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/name/updated" + ], + "title": "Thread/name/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadNameUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/name/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/tokenUsage/updated" + ], + "title": "Thread/tokenUsage/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadTokenUsageUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/tokenUsage/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/started" + ], + "title": "Turn/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/completed" + ], + "title": "Turn/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/diff/updated" + ], + "title": "Turn/diff/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnDiffUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/diff/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "turn/plan/updated" + ], + "title": "Turn/plan/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnPlanUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Turn/plan/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/started" + ], + "title": "Item/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ItemStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/completed" + ], + "title": "Item/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ItemCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/agentMessage/delta" + ], + "title": "Item/agentMessage/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AgentMessageDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/agentMessage/deltaNotification", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items.", + "properties": { + "method": { + "enum": [ + "item/plan/delta" + ], + "title": "Item/plan/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/PlanDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/plan/deltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/outputDelta" + ], + "title": "Item/commandExecution/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommandExecutionOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/commandExecution/terminalInteraction" + ], + "title": "Item/commandExecution/terminalInteractionNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TerminalInteractionNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/commandExecution/terminalInteractionNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/fileChange/outputDelta" + ], + "title": "Item/fileChange/outputDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FileChangeOutputDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/fileChange/outputDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "serverRequest/resolved" + ], + "title": "ServerRequest/resolvedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ServerRequestResolvedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ServerRequest/resolvedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/mcpToolCall/progress" + ], + "title": "Item/mcpToolCall/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpToolCallProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/mcpToolCall/progressNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "mcpServer/oauthLogin/completed" + ], + "title": "McpServer/oauthLogin/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/McpServerOauthLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "McpServer/oauthLogin/completedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/updated" + ], + "title": "Account/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/rateLimits/updated" + ], + "title": "Account/rateLimits/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountRateLimitsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/rateLimits/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "app/list/updated" + ], + "title": "App/list/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AppListUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "App/list/updatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryTextDelta" + ], + "title": "Item/reasoning/summaryTextDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningSummaryTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryTextDeltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/summaryPartAdded" + ], + "title": "Item/reasoning/summaryPartAddedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningSummaryPartAddedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/summaryPartAddedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "item/reasoning/textDelta" + ], + "title": "Item/reasoning/textDeltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReasoningTextDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Item/reasoning/textDeltaNotification", + "type": "object" + }, + { + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "properties": { + "method": { + "enum": [ + "thread/compacted" + ], + "title": "Thread/compactedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ContextCompactedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/compactedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "model/rerouted" + ], + "title": "Model/reroutedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ModelReroutedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Model/reroutedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "deprecationNotice" + ], + "title": "DeprecationNoticeNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeprecationNoticeNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "DeprecationNoticeNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "configWarning" + ], + "title": "ConfigWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ConfigWarningNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "fuzzyFileSearch/sessionUpdated" + ], + "title": "FuzzyFileSearch/sessionUpdatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchSessionUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "FuzzyFileSearch/sessionUpdatedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "fuzzyFileSearch/sessionCompleted" + ], + "title": "FuzzyFileSearch/sessionCompletedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchSessionCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "FuzzyFileSearch/sessionCompletedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/realtime/started" + ], + "title": "Thread/realtime/startedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRealtimeStartedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/realtime/startedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/realtime/itemAdded" + ], + "title": "Thread/realtime/itemAddedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRealtimeItemAddedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/realtime/itemAddedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/realtime/outputAudio/delta" + ], + "title": "Thread/realtime/outputAudio/deltaNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRealtimeOutputAudioDeltaNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/realtime/outputAudio/deltaNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/realtime/error" + ], + "title": "Thread/realtime/errorNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRealtimeErrorNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/realtime/errorNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "thread/realtime/closed" + ], + "title": "Thread/realtime/closedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadRealtimeClosedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/realtime/closedNotification", + "type": "object" + }, + { + "description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.", + "properties": { + "method": { + "enum": [ + "windows/worldWritableWarning" + ], + "title": "Windows/worldWritableWarningNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/WindowsWorldWritableWarningNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Windows/worldWritableWarningNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "windowsSandbox/setupCompleted" + ], + "title": "WindowsSandbox/setupCompletedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/WindowsSandboxSetupCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "WindowsSandbox/setupCompletedNotification", + "type": "object" + }, + { + "properties": { + "method": { + "enum": [ + "account/login/completed" + ], + "title": "Account/login/completedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/AccountLoginCompletedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Account/login/completedNotification", + "type": "object" + } + ], + "title": "ServerNotification" + }, + "ServerRequestResolvedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "requestId": { + "$ref": "#/definitions/RequestId" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "requestId", + "threadId" + ], + "title": "ServerRequestResolvedNotification", + "type": "object" + }, + "SessionNetworkProxyRuntime": { + "properties": { + "admin_addr": { + "type": "string" + }, + "http_addr": { + "type": "string" + }, + "socks_addr": { + "type": "string" + } + }, + "required": [ + "admin_addr", + "http_addr", + "socks_addr" + ], + "type": "object" + }, + "SessionSource": { + "oneOf": [ + { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "subAgent": { + "$ref": "#/definitions/SubAgentSource" + } + }, + "required": [ + "subAgent" + ], + "title": "SubAgentSessionSource", + "type": "object" + } + ] + }, + "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" + }, + "SkillDependencies": { + "properties": { + "tools": { + "items": { + "$ref": "#/definitions/SkillToolDependency" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "SkillErrorInfo": { + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "message", + "path" + ], + "type": "object" + }, + "SkillInterface": { + "properties": { + "brandColor": { + "type": [ + "string", + "null" + ] + }, + "defaultPrompt": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "iconLarge": { + "type": [ + "string", + "null" + ] + }, + "iconSmall": { + "type": [ + "string", + "null" + ] + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "SkillMetadata": { + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/SkillScope" + }, + "shortDescription": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "type": "object" + }, + "SkillScope": { + "enum": [ + "user", + "repo", + "system", + "admin" + ], + "type": "string" + }, + "SkillToolDependency": { + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "SkillsConfigWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "enabled": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "enabled", + "path" + ], + "title": "SkillsConfigWriteParams", + "type": "object" + }, + "SkillsConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "effectiveEnabled": { + "type": "boolean" + } + }, + "required": [ + "effectiveEnabled" + ], + "title": "SkillsConfigWriteResponse", + "type": "object" + }, + "SkillsListEntry": { + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "items": { + "$ref": "#/definitions/SkillErrorInfo" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/SkillMetadata" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "errors", + "skills" + ], + "type": "object" + }, + "SkillsListExtraRootsForCwd": { + "properties": { + "cwd": { + "type": "string" + }, + "extraUserRoots": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "cwd", + "extraUserRoots" + ], + "type": "object" + }, + "SkillsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "items": { + "type": "string" + }, + "type": "array" + }, + "forceReload": { + "description": "When true, bypass the skills cache and re-scan skills from disk.", + "type": "boolean" + }, + "perCwdExtraUserRoots": { + "default": null, + "description": "Optional per-cwd extra roots to scan as user-scoped skills.", + "items": { + "$ref": "#/definitions/SkillsListExtraRootsForCwd" + }, + "type": [ + "array", + "null" + ] + } + }, + "title": "SkillsListParams", + "type": "object" + }, + "SkillsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/SkillsListEntry" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsListResponse", + "type": "object" + }, + "SkillsRemoteReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "enabled": { + "default": false, + "type": "boolean" + }, + "hazelnutScope": { + "allOf": [ + { + "$ref": "#/definitions/HazelnutScope" + } + ], + "default": "example" + }, + "productSurface": { + "allOf": [ + { + "$ref": "#/definitions/ProductSurface" + } + ], + "default": "codex" + } + }, + "title": "SkillsRemoteReadParams", + "type": "object" + }, + "SkillsRemoteReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/RemoteSkillSummary" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "SkillsRemoteReadResponse", + "type": "object" + }, + "SkillsRemoteWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "hazelnutId": { + "type": "string" + } + }, + "required": [ + "hazelnutId" + ], + "title": "SkillsRemoteWriteParams", + "type": "object" + }, + "SkillsRemoteWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "id", + "path" + ], + "title": "SkillsRemoteWriteResponse", + "type": "object" + }, + "StepStatus": { + "enum": [ + "pending", + "in_progress", + "completed" + ], + "type": "string" + }, + "SubAgentSource": { + "oneOf": [ + { + "enum": [ + "review", + "compact", + "memory_consolidation" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "thread_spawn": { + "properties": { + "agent_nickname": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "agent_role": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "depth": { + "format": "int32", + "type": "integer" + }, + "parent_thread_id": { + "$ref": "#/definitions/ThreadId" + } + }, + "required": [ + "depth", + "parent_thread_id" + ], + "type": "object" + } + }, + "required": [ + "thread_spawn" + ], + "title": "ThreadSpawnSubAgentSource", + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "other": { + "type": "string" + } + }, + "required": [ + "other" + ], + "title": "OtherSubAgentSource", + "type": "object" + } + ] + }, + "TerminalInteractionNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "itemId": { + "type": "string" + }, + "processId": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "itemId", + "processId", + "stdin", + "threadId", + "turnId" + ], + "title": "TerminalInteractionNotification", + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TextPosition": { + "properties": { + "column": { + "description": "1-based column number (in Unicode scalar values).", + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "line": { + "description": "1-based line number.", + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "column", + "line" + ], + "type": "object" + }, + "TextRange": { + "properties": { + "end": { + "$ref": "#/definitions/TextPosition" + }, + "start": { + "$ref": "#/definitions/TextPosition" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "Thread": { + "properties": { + "agentNickname": { + "description": "Optional random unique nickname assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "agentRole": { + "description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "format": "int64", + "type": "integer" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "type": "string" + }, + "ephemeral": { + "description": "Whether the thread is ephemeral and should not be materialized on disk.", + "type": "boolean" + }, + "gitInfo": { + "anyOf": [ + { + "$ref": "#/definitions/GitInfo" + }, + { + "type": "null" + } + ], + "description": "Optional Git metadata captured when the thread was created." + }, + "id": { + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "name": { + "description": "Optional user-facing thread title.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/SessionSource" + } + ], + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.)." + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/ThreadStatus" + } + ], + "description": "Current runtime status for the thread." + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "items": { + "$ref": "#/definitions/Turn" + }, + "type": "array" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cliVersion", + "createdAt", + "cwd", + "ephemeral", + "id", + "modelProvider", + "preview", + "source", + "status", + "turns", + "updatedAt" + ], + "type": "object" + }, + "ThreadActiveFlag": { + "enum": [ + "waitingOnApproval", + "waitingOnUserInput" + ], + "type": "string" + }, + "ThreadArchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadArchiveParams", + "type": "object" + }, + "ThreadArchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchiveResponse", + "type": "object" + }, + "ThreadArchivedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadArchivedNotification", + "type": "object" + }, + "ThreadClosedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadClosedNotification", + "type": "object" + }, + "ThreadCompactStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadCompactStartParams", + "type": "object" + }, + "ThreadCompactStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" + }, + "ThreadForkParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the forked thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadForkParams", + "type": "object" + }, + "ThreadForkResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadForkResponse", + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ], + "default": null + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "AgentMessageThreadItem", + "type": "object" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "plan" + ], + "title": "PlanThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanThreadItem", + "type": "object" + }, + { + "properties": { + "content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ReasoningThreadItem", + "type": "object" + }, + { + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "items": { + "$ref": "#/definitions/CommandAction" + }, + "type": "array" + }, + "cwd": { + "description": "The command's working directory.", + "type": "string" + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "exitCode": { + "description": "The command's exit code.", + "format": "int32", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/CommandExecutionStatus" + }, + "type": { + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType", + "type": "string" + } + }, + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "title": "CommandExecutionThreadItem", + "type": "object" + }, + { + "properties": { + "changes": { + "items": { + "$ref": "#/definitions/FileUpdateChange" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/PatchApplyStatus" + }, + "type": { + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType", + "type": "string" + } + }, + "required": [ + "changes", + "id", + "status", + "type" + ], + "title": "FileChangeThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "title": "McpToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "arguments": true, + "contentItems": { + "items": { + "$ref": "#/definitions/DynamicToolCallOutputContentItem" + }, + "type": [ + "array", + "null" + ] + }, + "durationMs": { + "description": "The duration of the dynamic tool call in milliseconds.", + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/DynamicToolCallStatus" + }, + "success": { + "type": [ + "boolean", + "null" + ] + }, + "tool": { + "type": "string" + }, + "type": { + "enum": [ + "dynamicToolCall" + ], + "title": "DynamicToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "arguments", + "id", + "status", + "tool", + "type" + ], + "title": "DynamicToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "agentsStates": { + "additionalProperties": { + "$ref": "#/definitions/CollabAgentState" + }, + "description": "Last known status of the target agents, when available.", + "type": "object" + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "items": { + "type": "string" + }, + "type": "array" + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentToolCallStatus" + } + ], + "description": "Current status of the collab tool call." + }, + "tool": { + "allOf": [ + { + "$ref": "#/definitions/CollabAgentTool" + } + ], + "description": "Name of the collab tool that was invoked." + }, + "type": { + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType", + "type": "string" + } + }, + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "title": "CollabAgentToolCallThreadItem", + "type": "object" + }, + { + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "query", + "type" + ], + "title": "WebSearchThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "path", + "type" + ], + "title": "ImageViewThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "EnteredReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "review", + "type" + ], + "title": "ExitedReviewModeThreadItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionThreadItem", + "type": "object" + } + ] + }, + "ThreadListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "archived": { + "description": "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + "type": [ + "boolean", + "null" + ] + }, + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "cwd": { + "description": "Optional cwd filter; when set, only threads whose session cwd exactly matches this path are returned.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + }, + "modelProviders": { + "description": "Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "searchTerm": { + "description": "Optional substring filter for the extracted thread title.", + "type": [ + "string", + "null" + ] + }, + "sortKey": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadSortKey" + }, + { + "type": "null" + } + ], + "description": "Optional sort key; defaults to created_at." + }, + "sourceKinds": { + "description": "Optional source filter; when set, only sessions from these source kinds are returned. When omitted or empty, defaults to interactive sources.", + "items": { + "$ref": "#/definitions/ThreadSourceKind" + }, + "type": [ + "array", + "null" + ] + } + }, + "title": "ThreadListParams", + "type": "object" + }, + "ThreadListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/Thread" + }, + "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": "ThreadListResponse", + "type": "object" + }, + "ThreadLoadedListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to no limit.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ThreadLoadedListParams", + "type": "object" + }, + "ThreadLoadedListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "description": "Thread ids for sessions currently loaded in memory.", + "items": { + "type": "string" + }, + "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": "ThreadLoadedListResponse", + "type": "object" + }, + "ThreadNameUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "threadName": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "threadId" + ], + "title": "ThreadNameUpdatedNotification", + "type": "object" + }, + "ThreadReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "includeTurns": { + "default": false, + "description": "When true, include turns and their items from rollout history.", + "type": "boolean" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadReadParams", + "type": "object" + }, + "ThreadReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadReadResponse", + "type": "object" + }, + "ThreadRealtimeAudioChunk": { + "description": "EXPERIMENTAL - thread realtime audio chunk.", + "properties": { + "data": { + "type": "string" + }, + "numChannels": { + "format": "uint16", + "minimum": 0.0, + "type": "integer" + }, + "sampleRate": { + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "samplesPerChannel": { + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "data", + "numChannels", + "sampleRate" + ], + "type": "object" + }, + "ThreadRealtimeClosedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - emitted when thread realtime transport closes.", + "properties": { + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadRealtimeClosedNotification", + "type": "object" + }, + "ThreadRealtimeErrorNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - emitted when thread realtime encounters an error.", + "properties": { + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "message", + "threadId" + ], + "title": "ThreadRealtimeErrorNotification", + "type": "object" + }, + "ThreadRealtimeItemAddedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.", + "properties": { + "item": true, + "threadId": { + "type": "string" + } + }, + "required": [ + "item", + "threadId" + ], + "title": "ThreadRealtimeItemAddedNotification", + "type": "object" + }, + "ThreadRealtimeOutputAudioDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - streamed output audio emitted by thread realtime.", + "properties": { + "audio": { + "$ref": "#/definitions/ThreadRealtimeAudioChunk" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "audio", + "threadId" + ], + "title": "ThreadRealtimeOutputAudioDeltaNotification", + "type": "object" + }, + "ThreadRealtimeStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.", + "properties": { + "sessionId": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadRealtimeStartedNotification", + "type": "object" + }, + "ThreadResumeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the resumed thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadResumeParams", + "type": "object" + }, + "ThreadResumeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadResumeResponse", + "type": "object" + }, + "ThreadRollbackParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "numTurns": { + "description": "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", + "format": "uint32", + "minimum": 0.0, + "type": "integer" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "numTurns", + "threadId" + ], + "title": "ThreadRollbackParams", + "type": "object" + }, + "ThreadRollbackResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "allOf": [ + { + "$ref": "#/definitions/Thread" + } + ], + "description": "The updated thread after applying the rollback, with `turns` populated.\n\nThe ThreadItems stored in each Turn are lossy since we explicitly do not persist all agent interactions, such as command executions. This is the same behavior as `thread/resume`." + } + }, + "required": [ + "thread" + ], + "title": "ThreadRollbackResponse", + "type": "object" + }, + "ThreadSetNameParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "name": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "name", + "threadId" + ], + "title": "ThreadSetNameParams", + "type": "object" + }, + "ThreadSetNameResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSetNameResponse", + "type": "object" + }, + "ThreadSortKey": { + "enum": [ + "created_at", + "updated_at" + ], + "type": "string" + }, + "ThreadSourceKind": { + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown" + ], + "type": "string" + }, + "ThreadStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "additionalProperties": true, + "type": [ + "object", + "null" + ] + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "ephemeral": { + "type": [ + "boolean", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "serviceName": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ThreadStartParams", + "type": "object" + }, + "ThreadStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "$ref": "#/definitions/AskForApproval" + }, + "cwd": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "$ref": "#/definitions/SandboxPolicy" + }, + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "approvalPolicy", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "title": "ThreadStartResponse", + "type": "object" + }, + "ThreadStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadStartedNotification", + "type": "object" + }, + "ThreadStatus": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "notLoaded" + ], + "title": "NotLoadedThreadStatusType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "NotLoadedThreadStatus", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "idle" + ], + "title": "IdleThreadStatusType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "IdleThreadStatus", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "systemError" + ], + "title": "SystemErrorThreadStatusType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SystemErrorThreadStatus", + "type": "object" + }, + { + "properties": { + "activeFlags": { + "items": { + "$ref": "#/definitions/ThreadActiveFlag" + }, + "type": "array" + }, + "type": { + "enum": [ + "active" + ], + "title": "ActiveThreadStatusType", + "type": "string" + } + }, + "required": [ + "activeFlags", + "type" + ], + "title": "ActiveThreadStatus", + "type": "object" + } + ] + }, + "ThreadStatusChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/ThreadStatus" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "status", + "threadId" + ], + "title": "ThreadStatusChangedNotification", + "type": "object" + }, + "ThreadTokenUsage": { + "properties": { + "last": { + "$ref": "#/definitions/TokenUsageBreakdown" + }, + "modelContextWindow": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total": { + "$ref": "#/definitions/TokenUsageBreakdown" + } + }, + "required": [ + "last", + "total" + ], + "type": "object" + }, + "ThreadTokenUsageUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "tokenUsage": { + "$ref": "#/definitions/ThreadTokenUsage" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "tokenUsage", + "turnId" + ], + "title": "ThreadTokenUsageUpdatedNotification", + "type": "object" + }, + "ThreadUnarchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadUnarchiveParams", + "type": "object" + }, + "ThreadUnarchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "thread": { + "$ref": "#/definitions/Thread" + } + }, + "required": [ + "thread" + ], + "title": "ThreadUnarchiveResponse", + "type": "object" + }, + "ThreadUnarchivedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadUnarchivedNotification", + "type": "object" + }, + "ThreadUnsubscribeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadUnsubscribeParams", + "type": "object" + }, + "ThreadUnsubscribeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/ThreadUnsubscribeStatus" + } + }, + "required": [ + "status" + ], + "title": "ThreadUnsubscribeResponse", + "type": "object" + }, + "ThreadUnsubscribeStatus": { + "enum": [ + "notLoaded", + "notSubscribed", + "unsubscribed" + ], + "type": "string" + }, + "TokenUsage": { + "properties": { + "cached_input_tokens": { + "format": "int64", + "type": "integer" + }, + "input_tokens": { + "format": "int64", + "type": "integer" + }, + "output_tokens": { + "format": "int64", + "type": "integer" + }, + "reasoning_output_tokens": { + "format": "int64", + "type": "integer" + }, + "total_tokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cached_input_tokens", + "input_tokens", + "output_tokens", + "reasoning_output_tokens", + "total_tokens" + ], + "type": "object" + }, + "TokenUsageBreakdown": { + "properties": { + "cachedInputTokens": { + "format": "int64", + "type": "integer" + }, + "inputTokens": { + "format": "int64", + "type": "integer" + }, + "outputTokens": { + "format": "int64", + "type": "integer" + }, + "reasoningOutputTokens": { + "format": "int64", + "type": "integer" + }, + "totalTokens": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "cachedInputTokens", + "inputTokens", + "outputTokens", + "reasoningOutputTokens", + "totalTokens" + ], + "type": "object" + }, + "TokenUsageInfo": { + "properties": { + "last_token_usage": { + "$ref": "#/definitions/TokenUsage" + }, + "model_context_window": { + "format": "int64", + "type": [ + "integer", + "null" + ] + }, + "total_token_usage": { + "$ref": "#/definitions/TokenUsage" + } + }, + "required": [ + "last_token_usage", + "total_token_usage" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "_meta": true, + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "icons": { + "items": true, + "type": [ + "array", + "null" + ] + }, + "inputSchema": true, + "name": { + "type": "string" + }, + "outputSchema": true, + "title": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolsV2": { + "properties": { + "view_image": { + "type": [ + "boolean", + "null" + ] + }, + "web_search": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, + "Turn": { + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/TurnError" + }, + { + "type": "null" + } + ], + "description": "Only populated when the Turn's status is failed." + }, + "id": { + "type": "string" + }, + "items": { + "description": "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + "items": { + "$ref": "#/definitions/ThreadItem" + }, + "type": "array" + }, + "status": { + "$ref": "#/definitions/TurnStatus" + } + }, + "required": [ + "id", + "items", + "status" + ], + "type": "object" + }, + "TurnAbortReason": { + "enum": [ + "interrupted", + "replaced", + "review_ended" + ], + "type": "string" + }, + "TurnCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnCompletedNotification", + "type": "object" + }, + "TurnDiffUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + "properties": { + "diff": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "diff", + "threadId", + "turnId" + ], + "title": "TurnDiffUpdatedNotification", + "type": "object" + }, + "TurnError": { + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "TurnInterruptParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "threadId", + "turnId" + ], + "title": "TurnInterruptParams", + "type": "object" + }, + "TurnInterruptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnInterruptResponse", + "type": "object" + }, + "TurnItem": { + "oneOf": [ + { + "properties": { + "content": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "type": { + "enum": [ + "UserMessage" + ], + "title": "UserMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "UserMessageTurnItem", + "type": "object" + }, + { + "description": "Assistant-authored message payload used in turn-item streams.\n\n`phase` is optional because not all providers/models emit it. Consumers should use it when present, but retain legacy completion semantics when it is `None`.", + "properties": { + "content": { + "items": { + "$ref": "#/definitions/AgentMessageContent" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/MessagePhase" + }, + { + "type": "null" + } + ], + "description": "Optional phase metadata carried through from `ResponseItem::Message`.\n\nThis is currently used by TUI rendering to distinguish mid-turn commentary from a final answer and avoid status-indicator jitter." + }, + "type": { + "enum": [ + "AgentMessage" + ], + "title": "AgentMessageTurnItemType", + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "title": "AgentMessageTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "enum": [ + "Plan" + ], + "title": "PlanTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "text", + "type" + ], + "title": "PlanTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "raw_content": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "summary_text": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "enum": [ + "Reasoning" + ], + "title": "ReasoningTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "summary_text", + "type" + ], + "title": "ReasoningTurnItem", + "type": "object" + }, + { + "properties": { + "action": { + "$ref": "#/definitions/WebSearchAction" + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "enum": [ + "WebSearch" + ], + "title": "WebSearchTurnItemType", + "type": "string" + } + }, + "required": [ + "action", + "id", + "query", + "type" + ], + "title": "WebSearchTurnItem", + "type": "object" + }, + { + "properties": { + "id": { + "type": "string" + }, + "type": { + "enum": [ + "ContextCompaction" + ], + "title": "ContextCompactionTurnItemType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ContextCompactionTurnItem", + "type": "object" + } + ] + }, + "TurnPlanStep": { + "properties": { + "status": { + "$ref": "#/definitions/TurnPlanStepStatus" + }, + "step": { + "type": "string" + } + }, + "required": [ + "status", + "step" + ], + "type": "object" + }, + "TurnPlanStepStatus": { + "enum": [ + "pending", + "inProgress", + "completed" + ], + "type": "string" + }, + "TurnPlanUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "explanation": { + "type": [ + "string", + "null" + ] + }, + "plan": { + "items": { + "$ref": "#/definitions/TurnPlanStep" + }, + "type": "array" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + }, + "required": [ + "plan", + "threadId", + "turnId" + ], + "title": "TurnPlanUpdatedNotification", + "type": "object" + }, + "TurnStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "title": "TurnStartParams", + "type": "object" + }, + "TurnStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "turn" + ], + "title": "TurnStartResponse", + "type": "object" + }, + "TurnStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/Turn" + } + }, + "required": [ + "threadId", + "turn" + ], + "title": "TurnStartedNotification", + "type": "object" + }, + "TurnStatus": { + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ], + "type": "string" + }, + "TurnSteerParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "expectedTurnId": { + "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", + "type": "string" + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "expectedTurnId", + "input", + "threadId" + ], + "title": "TurnSteerParams", + "type": "object" + }, + "TurnSteerResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "turnId": { + "type": "string" + } + }, + "required": [ + "turnId" + ], + "title": "TurnSteerResponse", + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "enum": [ + "low", + "medium", + "high" + ], + "type": "string" + }, + "WebSearchAction": { + "oneOf": [ + { + "properties": { + "queries": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SearchWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "open_page" + ], + "title": "OpenPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "OpenPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "enum": [ + "find_in_page" + ], + "title": "FindInPageWebSearchActionType", + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "type" + ], + "title": "FindInPageWebSearchAction", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "OtherWebSearchAction", + "type": "object" + } + ] + }, + "WebSearchMode": { + "enum": [ + "disabled", + "cached", + "live" + ], + "type": "string" + }, + "WindowsSandboxSetupCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "mode": { + "$ref": "#/definitions/WindowsSandboxSetupMode" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "mode", + "success" + ], + "title": "WindowsSandboxSetupCompletedNotification", + "type": "object" + }, + "WindowsSandboxSetupMode": { + "enum": [ + "elevated", + "unelevated" + ], + "type": "string" + }, + "WindowsSandboxSetupStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "mode": { + "$ref": "#/definitions/WindowsSandboxSetupMode" + } + }, + "required": [ + "mode" + ], + "title": "WindowsSandboxSetupStartParams", + "type": "object" + }, + "WindowsSandboxSetupStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "started": { + "type": "boolean" + } + }, + "required": [ + "started" + ], + "title": "WindowsSandboxSetupStartResponse", + "type": "object" + }, + "WindowsWorldWritableWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "extraCount": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "failedScan": { + "type": "boolean" + }, + "samplePaths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "extraCount", + "failedScan", + "samplePaths" + ], + "title": "WindowsWorldWritableWarningNotification", + "type": "object" + }, + "WriteStatus": { + "enum": [ + "ok", + "okOverridden" + ], + "type": "string" + } + }, + "title": "CodexAppServerProtocolV2", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/src/bin/export.rs b/codex-rs/app-server-protocol/src/bin/export.rs index d029ecbffa..10541d1fcb 100644 --- a/codex-rs/app-server-protocol/src/bin/export.rs +++ b/codex-rs/app-server-protocol/src/bin/export.rs @@ -14,9 +14,21 @@ struct Args { /// Optional Prettier executable path to format generated TypeScript files #[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")] prettier: Option, + + /// Include experimental API methods and fields in generated output. + #[arg(long = "experimental")] + experimental: bool, } fn main() -> Result<()> { let args = Args::parse(); - codex_app_server_protocol::generate_types(&args.out_dir, args.prettier.as_deref()) + codex_app_server_protocol::generate_ts_with_options( + &args.out_dir, + args.prettier.as_deref(), + codex_app_server_protocol::GenerateTsOptions { + experimental_api: args.experimental, + ..codex_app_server_protocol::GenerateTsOptions::default() + }, + )?; + codex_app_server_protocol::generate_json_with_experimental(&args.out_dir, args.experimental) } diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index b48651c03c..78d5507277 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -37,6 +37,14 @@ use ts_rs::TS; const HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n"; const IGNORED_DEFINITIONS: &[&str] = &["Option<()>"]; const JSON_V1_ALLOWLIST: &[&str] = &["InitializeParams", "InitializeResponse"]; +const SPECIAL_DEFINITIONS: &[&str] = &[ + "ClientNotification", + "ClientRequest", + "EventMsg", + "ServerNotification", + "ServerRequest", +]; +const FLAT_V2_SHARED_DEFINITIONS: &[&str] = &["ClientRequest", "EventMsg", "ServerNotification"]; const V1_CLIENT_REQUEST_METHODS: &[&str] = &[ "newConversation", "getConversationSummary", @@ -223,6 +231,11 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) - out_dir.join("codex_app_server_protocol.schemas.json"), &bundle, )?; + let flat_v2_bundle = build_flat_v2_schema(&bundle)?; + write_pretty_json( + out_dir.join("codex_app_server_protocol.v2.schemas.json"), + &flat_v2_bundle, + )?; if !experimental_api { filter_experimental_json_files(out_dir)?; @@ -870,14 +883,6 @@ impl Depth { } fn build_schema_bundle(schemas: Vec) -> Result { - const SPECIAL_DEFINITIONS: &[&str] = &[ - "ClientNotification", - "ClientRequest", - "EventMsg", - "ServerNotification", - "ServerRequest", - ]; - let namespaced_types = collect_namespaced_types(&schemas); let mut definitions = Map::new(); @@ -895,6 +900,8 @@ fn build_schema_bundle(schemas: Vec) -> Result { if let Some(ref ns) = namespace { rewrite_refs_to_namespace(&mut value, ns); + } else { + rewrite_refs_to_known_namespaces(&mut value, &namespaced_types); } let mut forced_namespace_refs: Vec<(String, String)> = Vec::new(); @@ -958,6 +965,210 @@ fn build_schema_bundle(schemas: Vec) -> Result { Ok(Value::Object(root)) } +/// Build a datamodel-code-generator-friendly v2 bundle from the mixed export. +/// +/// The full bundle keeps v2 schemas nested under `definitions.v2`, plus a few +/// shared root definitions like `ClientRequest`, `EventMsg`, and +/// `ServerNotification`. Python codegen only walks one definitions map level, so +/// a direct feed would treat `v2` itself as a schema and miss unreferenced v2 +/// leaves. This helper flattens all v2 definitions to the root definitions map, +/// then pulls in the shared root schemas and any non-v2 transitive deps they +/// still reference. Keep the shared root unions intact here: some valid +/// request/notification/event variants are inline or only reference shared root +/// helpers, so filtering them by the presence of a `#/definitions/v2/` ref +/// would silently drop real API surface from the flat bundle. +fn build_flat_v2_schema(bundle: &Value) -> Result { + let Value::Object(root) = bundle else { + return Err(anyhow!("expected bundle root to be an object")); + }; + let definitions = root + .get("definitions") + .and_then(Value::as_object) + .ok_or_else(|| anyhow!("expected bundle definitions map"))?; + let v2_definitions = definitions + .get("v2") + .and_then(Value::as_object) + .ok_or_else(|| anyhow!("expected v2 namespace in bundle definitions"))?; + + let mut flat_root = root.clone(); + let title = root + .get("title") + .and_then(Value::as_str) + .unwrap_or("CodexAppServerProtocol"); + let mut flat_definitions = v2_definitions.clone(); + let mut shared_definitions = Map::new(); + let mut non_v2_refs = HashSet::new(); + + for shared in FLAT_V2_SHARED_DEFINITIONS { + let Some(shared_schema) = definitions.get(*shared) else { + continue; + }; + let shared_schema = shared_schema.clone(); + non_v2_refs.extend(collect_non_v2_refs(&shared_schema)); + shared_definitions.insert((*shared).to_string(), shared_schema); + } + + for name in collect_definition_dependencies(definitions, non_v2_refs) { + if name == "v2" || flat_definitions.contains_key(&name) { + continue; + } + if let Some(schema) = definitions.get(&name) { + flat_definitions.insert(name, schema.clone()); + } + } + + flat_definitions.extend(shared_definitions); + flat_root.insert("title".to_string(), Value::String(format!("{title}V2"))); + flat_root.insert("definitions".to_string(), Value::Object(flat_definitions)); + let mut flat_bundle = Value::Object(flat_root); + rewrite_ref_prefix(&mut flat_bundle, "#/definitions/v2/", "#/definitions/"); + ensure_no_ref_prefix(&flat_bundle, "#/definitions/v2/", "flat v2")?; + ensure_referenced_definitions_present(&flat_bundle, "flat v2")?; + Ok(flat_bundle) +} + +fn collect_non_v2_refs(value: &Value) -> HashSet { + let mut refs = HashSet::new(); + collect_non_v2_refs_inner(value, &mut refs); + refs +} + +fn collect_non_v2_refs_inner(value: &Value, refs: &mut HashSet) { + match value { + Value::Object(obj) => { + if let Some(Value::String(reference)) = obj.get("$ref") + && let Some(name) = reference.strip_prefix("#/definitions/") + && !reference.starts_with("#/definitions/v2/") + { + refs.insert(name.to_string()); + } + for child in obj.values() { + collect_non_v2_refs_inner(child, refs); + } + } + Value::Array(items) => { + for child in items { + collect_non_v2_refs_inner(child, refs); + } + } + _ => {} + } +} + +fn collect_definition_dependencies( + definitions: &Map, + names: HashSet, +) -> HashSet { + let mut seen = HashSet::new(); + let mut to_process: Vec = names.into_iter().collect(); + while let Some(name) = to_process.pop() { + if !seen.insert(name.clone()) { + continue; + } + let Some(schema) = definitions.get(&name) else { + continue; + }; + for dep in collect_non_v2_refs(schema) { + if !seen.contains(&dep) { + to_process.push(dep); + } + } + } + seen +} + +fn rewrite_ref_prefix(value: &mut Value, prefix: &str, replacement: &str) { + match value { + Value::Object(obj) => { + if let Some(Value::String(reference)) = obj.get_mut("$ref") { + *reference = reference.replace(prefix, replacement); + } + for child in obj.values_mut() { + rewrite_ref_prefix(child, prefix, replacement); + } + } + Value::Array(items) => { + for child in items { + rewrite_ref_prefix(child, prefix, replacement); + } + } + _ => {} + } +} + +fn ensure_no_ref_prefix(value: &Value, prefix: &str, label: &str) -> Result<()> { + if let Some(reference) = first_ref_with_prefix(value, prefix) { + return Err(anyhow!( + "{label} schema still references namespaced definitions; found {reference}" + )); + } + Ok(()) +} + +fn first_ref_with_prefix(value: &Value, prefix: &str) -> Option { + match value { + Value::Object(obj) => { + if let Some(Value::String(reference)) = obj.get("$ref") + && reference.starts_with(prefix) + { + return Some(reference.clone()); + } + obj.values() + .find_map(|child| first_ref_with_prefix(child, prefix)) + } + Value::Array(items) => items + .iter() + .find_map(|child| first_ref_with_prefix(child, prefix)), + _ => None, + } +} + +fn ensure_referenced_definitions_present(schema: &Value, label: &str) -> Result<()> { + let definitions = schema + .get("definitions") + .and_then(Value::as_object) + .ok_or_else(|| anyhow!("expected definitions map in {label} schema"))?; + let mut missing = HashSet::new(); + collect_missing_definitions(schema, definitions, &mut missing); + if missing.is_empty() { + return Ok(()); + } + let mut missing_names: Vec = missing.into_iter().collect(); + missing_names.sort(); + Err(anyhow!( + "{label} schema missing definitions: {}", + missing_names.join(", ") + )) +} + +fn collect_missing_definitions( + value: &Value, + definitions: &Map, + missing: &mut HashSet, +) { + match value { + Value::Object(obj) => { + if let Some(Value::String(reference)) = obj.get("$ref") + && let Some(name) = reference.strip_prefix("#/definitions/") + { + let name = name.split('/').next().unwrap_or(name); + if !definitions.contains_key(name) { + missing.insert(name.to_string()); + } + } + for child in obj.values() { + collect_missing_definitions(child, definitions, missing); + } + } + Value::Array(items) => { + for child in items { + collect_missing_definitions(child, definitions, missing); + } + } + _ => {} + } +} + fn insert_into_namespace( definitions: &mut Map, namespace: &str, @@ -1230,6 +1441,43 @@ fn rewrite_refs_to_namespace(value: &mut Value, ns: &str) { } } +/// Recursively rewrite bare root definition refs to the namespace that owns the +/// referenced type in the bundle. +/// +/// The mixed export contains shared root helper schemas that are intentionally +/// left outside the `v2` namespace, but some of their extracted child +/// definitions still contain refs like `#/definitions/ThreadId`. When the real +/// schema only exists under `#/definitions/v2/ThreadId`, those refs become +/// dangling and downstream codegen falls back to placeholder `Any` models. This +/// rewrite keeps the shared helpers at the root while retargeting their refs to +/// the namespaced definitions that actually exist. +fn rewrite_refs_to_known_namespaces(value: &mut Value, types: &HashMap) { + match value { + Value::Object(obj) => { + if let Some(Value::String(reference)) = obj.get_mut("$ref") + && let Some(suffix) = reference.strip_prefix("#/definitions/") + { + let (name, tail) = suffix + .split_once('/') + .map_or((suffix, None), |(name, tail)| (name, Some(tail))); + if let Some(ns) = namespace_for_definition(name, types) { + let tail = tail.map_or(String::new(), |rest| format!("/{rest}")); + *reference = format!("#/definitions/{ns}/{name}{tail}"); + } + } + for v in obj.values_mut() { + rewrite_refs_to_known_namespaces(v, types); + } + } + Value::Array(items) => { + for v in items.iter_mut() { + rewrite_refs_to_known_namespaces(v, types); + } + } + _ => {} + } +} + fn collect_namespaced_types(schemas: &[GeneratedSchema]) -> HashMap { let mut types = HashMap::new(); for schema in schemas { @@ -1986,6 +2234,284 @@ mod tests { Ok(()) } + #[test] + fn build_schema_bundle_rewrites_root_helper_refs_to_namespaced_defs() -> Result<()> { + let bundle = build_schema_bundle(vec![ + GeneratedSchema { + namespace: None, + logical_name: "LegacyEnvelope".to_string(), + in_v1_dir: false, + value: serde_json::json!({ + "title": "LegacyEnvelope", + "type": "object", + "properties": { + "current_thread": { "$ref": "#/definitions/ThreadId" }, + "turn_item": { "$ref": "#/definitions/TurnItem" } + }, + "definitions": { + "TurnItem": { + "type": "object", + "properties": { + "thread_id": { "$ref": "#/definitions/ThreadId" }, + "phase": { "$ref": "#/definitions/MessagePhase" }, + "content": { + "type": "array", + "items": { "$ref": "#/definitions/UserInput" } + } + } + } + } + }), + }, + GeneratedSchema { + namespace: Some("v2".to_string()), + logical_name: "ThreadId".to_string(), + in_v1_dir: false, + value: serde_json::json!({ + "title": "ThreadId", + "type": "string" + }), + }, + GeneratedSchema { + namespace: Some("v2".to_string()), + logical_name: "MessagePhase".to_string(), + in_v1_dir: false, + value: serde_json::json!({ + "title": "MessagePhase", + "type": "string" + }), + }, + GeneratedSchema { + namespace: Some("v2".to_string()), + logical_name: "UserInput".to_string(), + in_v1_dir: false, + value: serde_json::json!({ + "title": "UserInput", + "type": "string" + }), + }, + ])?; + + assert_eq!( + bundle["definitions"]["LegacyEnvelope"]["properties"]["current_thread"]["$ref"], + serde_json::json!("#/definitions/v2/ThreadId") + ); + assert_eq!( + bundle["definitions"]["LegacyEnvelope"]["properties"]["turn_item"]["$ref"], + serde_json::json!("#/definitions/TurnItem") + ); + assert_eq!( + bundle["definitions"]["TurnItem"]["properties"]["thread_id"]["$ref"], + serde_json::json!("#/definitions/v2/ThreadId") + ); + assert_eq!( + bundle["definitions"]["TurnItem"]["properties"]["phase"]["$ref"], + serde_json::json!("#/definitions/v2/MessagePhase") + ); + assert_eq!( + bundle["definitions"]["TurnItem"]["properties"]["content"]["items"]["$ref"], + serde_json::json!("#/definitions/v2/UserInput") + ); + + Ok(()) + } + + #[test] + fn build_flat_v2_schema_keeps_shared_root_schemas_and_dependencies() -> Result<()> { + let bundle = serde_json::json!({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CodexAppServerProtocol", + "type": "object", + "definitions": { + "ClientRequest": { + "oneOf": [ + { + "title": "StartRequest", + "type": "object", + "properties": { + "params": { "$ref": "#/definitions/v2/ThreadStartParams" }, + "shared": { "$ref": "#/definitions/SharedHelper" } + } + }, + { + "title": "InitializeRequest", + "type": "object", + "properties": { + "params": { "$ref": "#/definitions/InitializeParams" } + } + }, + { + "title": "LogoutRequest", + "type": "object", + "properties": { + "params": { "type": "null" } + } + } + ] + }, + "EventMsg": { + "oneOf": [ + { "$ref": "#/definitions/v2/ThreadStartedEventMsg" }, + { + "title": "WarningEventMsg", + "type": "object", + "properties": { + "message": { "type": "string" }, + "type": { + "enum": ["warning"], + "type": "string" + } + }, + "required": ["message", "type"] + } + ] + }, + "ServerNotification": { + "oneOf": [ + { "$ref": "#/definitions/v2/ThreadStartedNotification" }, + { + "title": "ServerRequestResolvedNotification", + "type": "object", + "properties": { + "params": { "$ref": "#/definitions/ServerRequestResolvedNotificationPayload" } + } + } + ] + }, + "SharedHelper": { + "type": "object", + "properties": { + "leaf": { "$ref": "#/definitions/SharedLeaf" } + } + }, + "SharedLeaf": { + "title": "SharedLeaf", + "type": "string" + }, + "InitializeParams": { + "title": "InitializeParams", + "type": "string" + }, + "ServerRequestResolvedNotificationPayload": { + "title": "ServerRequestResolvedNotificationPayload", + "type": "string" + }, + "v2": { + "ThreadStartParams": { + "title": "ThreadStartParams", + "type": "object", + "properties": { + "cwd": { "type": "string" } + } + }, + "ThreadStartResponse": { + "title": "ThreadStartResponse", + "type": "object", + "properties": { + "ok": { "type": "boolean" } + } + }, + "ThreadStartedEventMsg": { + "title": "ThreadStartedEventMsg", + "type": "object", + "properties": { + "thread_id": { "type": "string" } + } + }, + "ThreadStartedNotification": { + "title": "ThreadStartedNotification", + "type": "object", + "properties": { + "thread_id": { "type": "string" } + } + } + } + } + }); + + let flat_bundle = build_flat_v2_schema(&bundle)?; + let definitions = flat_bundle["definitions"] + .as_object() + .expect("flat v2 schema should include definitions"); + + assert_eq!( + flat_bundle["title"], + serde_json::json!("CodexAppServerProtocolV2") + ); + assert_eq!(definitions.contains_key("v2"), false); + assert_eq!(definitions.contains_key("ThreadStartParams"), true); + assert_eq!(definitions.contains_key("ThreadStartResponse"), true); + assert_eq!(definitions.contains_key("ThreadStartedEventMsg"), true); + assert_eq!(definitions.contains_key("ThreadStartedNotification"), true); + assert_eq!(definitions.contains_key("SharedHelper"), true); + assert_eq!(definitions.contains_key("SharedLeaf"), true); + assert_eq!(definitions.contains_key("InitializeParams"), true); + assert_eq!( + definitions.contains_key("ServerRequestResolvedNotificationPayload"), + true + ); + let client_request_titles: BTreeSet = definitions["ClientRequest"]["oneOf"] + .as_array() + .expect("ClientRequest should remain a oneOf") + .iter() + .map(|variant| { + variant["title"] + .as_str() + .expect("ClientRequest variant should have a title") + .to_string() + }) + .collect(); + assert_eq!( + client_request_titles, + BTreeSet::from([ + "InitializeRequest".to_string(), + "LogoutRequest".to_string(), + "StartRequest".to_string(), + ]) + ); + let event_titles: BTreeSet = definitions["EventMsg"]["oneOf"] + .as_array() + .expect("EventMsg should remain a oneOf") + .iter() + .map(|variant| { + variant + .get("title") + .and_then(Value::as_str) + .unwrap_or_default() + .to_string() + }) + .collect(); + assert_eq!( + event_titles, + BTreeSet::from(["".to_string(), "WarningEventMsg".to_string(),]) + ); + let notification_titles: BTreeSet = definitions["ServerNotification"]["oneOf"] + .as_array() + .expect("ServerNotification should remain a oneOf") + .iter() + .map(|variant| { + variant + .get("title") + .and_then(Value::as_str) + .unwrap_or_default() + .to_string() + }) + .collect(); + assert_eq!( + notification_titles, + BTreeSet::from([ + "".to_string(), + "ServerRequestResolvedNotification".to_string(), + ]) + ); + assert_eq!( + first_ref_with_prefix(&flat_bundle, "#/definitions/v2/").is_none(), + true + ); + + Ok(()) + } + #[test] fn experimental_type_fields_ts_filter_handles_interface_shape() -> Result<()> { let output_dir = std::env::temp_dir().join(format!("codex_ts_filter_{}", Uuid::now_v7())); @@ -2114,6 +2640,99 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k bundle_json.contains("MockExperimentalMethodResponse"), false ); + let flat_v2_bundle_json = + fs::read_to_string(output_dir.join("codex_app_server_protocol.v2.schemas.json"))?; + assert_eq!(flat_v2_bundle_json.contains("mockExperimentalField"), false); + assert_eq!(flat_v2_bundle_json.contains("additionalPermissions"), false); + assert_eq!( + flat_v2_bundle_json.contains("MockExperimentalMethodParams"), + false + ); + assert_eq!( + flat_v2_bundle_json.contains("MockExperimentalMethodResponse"), + false + ); + assert_eq!(flat_v2_bundle_json.contains("#/definitions/v2/"), false); + assert_eq!( + flat_v2_bundle_json.contains("\"title\": \"CodexAppServerProtocolV2\""), + true + ); + let flat_v2_bundle = + read_json_value(&output_dir.join("codex_app_server_protocol.v2.schemas.json"))?; + let definitions = flat_v2_bundle["definitions"] + .as_object() + .expect("flat v2 bundle should include definitions"); + let client_request_methods: BTreeSet = definitions["ClientRequest"]["oneOf"] + .as_array() + .expect("flat v2 ClientRequest should remain a oneOf") + .iter() + .filter_map(|variant| { + variant["properties"]["method"]["enum"] + .as_array() + .and_then(|values| values.first()) + .and_then(Value::as_str) + .map(str::to_string) + }) + .collect(); + let missing_client_request_methods: Vec = [ + "account/logout", + "account/rateLimits/read", + "config/mcpServer/reload", + "configRequirements/read", + "fuzzyFileSearch", + "initialize", + ] + .into_iter() + .filter(|method| !client_request_methods.contains(*method)) + .map(str::to_string) + .collect(); + assert_eq!(missing_client_request_methods, Vec::::new()); + let server_notification_methods: BTreeSet = + definitions["ServerNotification"]["oneOf"] + .as_array() + .expect("flat v2 ServerNotification should remain a oneOf") + .iter() + .filter_map(|variant| { + variant["properties"]["method"]["enum"] + .as_array() + .and_then(|values| values.first()) + .and_then(Value::as_str) + .map(str::to_string) + }) + .collect(); + let missing_server_notification_methods: Vec = [ + "fuzzyFileSearch/sessionCompleted", + "fuzzyFileSearch/sessionUpdated", + "serverRequest/resolved", + ] + .into_iter() + .filter(|method| !server_notification_methods.contains(*method)) + .map(str::to_string) + .collect(); + assert_eq!(missing_server_notification_methods, Vec::::new()); + let event_types: BTreeSet = definitions["EventMsg"]["oneOf"] + .as_array() + .expect("flat v2 EventMsg should remain a oneOf") + .iter() + .filter_map(|variant| { + variant["properties"]["type"]["enum"] + .as_array() + .and_then(|values| values.first()) + .and_then(Value::as_str) + .map(str::to_string) + }) + .collect(); + let missing_event_types: Vec = [ + "agent_message_delta", + "task_complete", + "warning", + "web_search_begin", + ] + .into_iter() + .filter(|event_type| !event_types.contains(*event_type)) + .map(str::to_string) + .collect(); + assert_eq!(missing_event_types, Vec::::new()); assert_eq!( output_dir .join("v2") From 69df12efb35d07387165f48103e238f4f32044fa Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 3 Mar 2026 11:32:53 -0700 Subject: [PATCH 06/17] Remove Responses V1 websocket implementation (#13364) V2 is the way to go! --- codex-rs/codex-api/src/common.rs | 10 -- codex-rs/codex-api/src/lib.rs | 1 - codex-rs/codex-api/src/sse/responses.rs | 107 +------------ codex-rs/codex-api/tests/sse_end_to_end.rs | 2 - codex-rs/core/src/client.rs | 141 +++++------------- codex-rs/core/src/codex.rs | 9 +- codex-rs/core/src/compact.rs | 3 +- codex-rs/core/src/lib.rs | 1 - codex-rs/core/tests/common/responses.rs | 19 --- codex-rs/core/tests/responses_headers.rs | 7 +- codex-rs/core/tests/suite/agent_websocket.rs | 77 +++++----- codex-rs/core/tests/suite/client.rs | 3 +- .../core/tests/suite/client_websockets.rs | 97 +++++------- codex-rs/core/tests/suite/turn_state.rs | 3 +- .../core/tests/suite/websocket_fallback.rs | 6 +- 15 files changed, 133 insertions(+), 353 deletions(-) diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index b6126840d9..31b4dcdb44 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -67,8 +67,6 @@ pub enum ResponseEvent { Completed { response_id: String, token_usage: Option, - /// Whether the client can append more items to a long-running websocket response. - can_append: bool, }, OutputTextDelta(String), ReasoningSummaryDelta { @@ -211,20 +209,12 @@ pub struct ResponseCreateWsRequest { pub client_metadata: Option>, } -#[derive(Debug, Serialize)] -pub struct ResponseAppendWsRequest { - pub input: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub client_metadata: Option>, -} #[derive(Debug, Serialize)] #[serde(tag = "type")] #[allow(clippy::large_enum_variant)] pub enum ResponsesWsRequest { #[serde(rename = "response.create")] ResponseCreate(ResponseCreateWsRequest), - #[serde(rename = "response.append")] - ResponseAppend(ResponseAppendWsRequest), } pub fn create_text_param_for_request( diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index b9152ec576..138929602b 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -19,7 +19,6 @@ pub use crate::common::MemorySummarizeInput; pub use crate::common::MemorySummarizeOutput; pub use crate::common::RawMemory; pub use crate::common::RawMemoryMetadata; -pub use crate::common::ResponseAppendWsRequest; pub use crate::common::ResponseCreateWsRequest; pub use crate::common::ResponseEvent; pub use crate::common::ResponseStream; diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index 3c039380c9..d01e1d918a 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -118,14 +118,6 @@ struct ResponseCompleted { usage: Option, } -#[derive(Debug, Deserialize)] -struct ResponseDone { - #[serde(default)] - id: Option, - #[serde(default)] - usage: Option, -} - #[derive(Debug, Deserialize)] struct ResponseCompletedUsage { input_tokens: i64, @@ -324,7 +316,6 @@ pub fn process_responses_event( return Ok(Some(ResponseEvent::Completed { response_id: resp.id, token_usage: resp.usage.map(Into::into), - can_append: false, })); } Err(err) => { @@ -335,31 +326,6 @@ pub fn process_responses_event( } } } - "response.done" => { - if let Some(resp_val) = event.response { - match serde_json::from_value::(resp_val) { - Ok(resp) => { - return Ok(Some(ResponseEvent::Completed { - response_id: resp.id.unwrap_or_default(), - token_usage: resp.usage.map(Into::into), - can_append: true, - })); - } - Err(err) => { - let error = format!("failed to parse ResponseCompleted: {err}"); - debug!("{error}"); - return Err(ResponsesEventError::Api(ApiError::Stream(error))); - } - } - } - - debug!("response.done missing response payload"); - return Ok(Some(ResponseEvent::Completed { - response_id: String::new(), - token_usage: None, - can_append: true, - })); - } "response.output_item.added" => { if let Some(item_val) = event.item { if let Ok(item) = serde_json::from_value::(item_val) { @@ -639,11 +605,9 @@ mod tests { Ok(ResponseEvent::Completed { response_id, token_usage, - can_append, }) => { assert_eq!(response_id, "resp1"); assert!(token_usage.is_none()); - assert!(!can_append); } other => panic!("unexpected third event: {other:?}"), } @@ -677,69 +641,6 @@ mod tests { } } - #[tokio::test] - async fn response_done_emits_incremental_completed() { - let done = json!({ - "type": "response.done", - "response": { - "usage": { - "input_tokens": 1, - "input_tokens_details": null, - "output_tokens": 2, - "output_tokens_details": null, - "total_tokens": 3 - } - } - }) - .to_string(); - - let sse1 = format!("event: response.done\ndata: {done}\n\n"); - - let events = collect_events(&[sse1.as_bytes()]).await; - - assert_eq!(events.len(), 1); - - match &events[0] { - Ok(ResponseEvent::Completed { - response_id, - token_usage, - can_append, - }) => { - assert_eq!(response_id, ""); - assert!(token_usage.is_some()); - assert!(*can_append); - } - other => panic!("unexpected event: {other:?}"), - } - } - - #[tokio::test] - async fn response_done_without_payload_emits_completed() { - let done = json!({ - "type": "response.done" - }) - .to_string(); - - let sse1 = format!("event: response.done\ndata: {done}\n\n"); - - let events = collect_events(&[sse1.as_bytes()]).await; - - assert_eq!(events.len(), 1); - - match &events[0] { - Ok(ResponseEvent::Completed { - response_id, - token_usage, - can_append, - }) => { - assert_eq!(response_id, ""); - assert!(token_usage.is_none()); - assert!(*can_append); - } - other => panic!("unexpected event: {other:?}"), - } - } - #[tokio::test] async fn emits_completed_without_stream_end() { let completed = json!({ @@ -770,11 +671,9 @@ mod tests { Ok(ResponseEvent::Completed { response_id, token_usage, - can_append, }) => { assert_eq!(response_id, "resp1"); assert!(token_usage.is_none()); - assert!(!can_append); } other => panic!("unexpected event: {other:?}"), } @@ -996,8 +895,7 @@ mod tests { &events[1], ResponseEvent::Completed { response_id, - token_usage: None, - can_append: false + token_usage: None } if response_id == "resp-1" ); } @@ -1033,8 +931,7 @@ mod tests { &events[2], ResponseEvent::Completed { response_id, - token_usage: None, - can_append: false + token_usage: None } if response_id == "resp-1" ); } diff --git a/codex-rs/codex-api/tests/sse_end_to_end.rs b/codex-rs/codex-api/tests/sse_end_to_end.rs index ca840d1b9f..f625fdcd9d 100644 --- a/codex-rs/codex-api/tests/sse_end_to_end.rs +++ b/codex-rs/codex-api/tests/sse_end_to_end.rs @@ -159,11 +159,9 @@ async fn responses_stream_parses_items_and_completed_end_to_end() -> Result<()> ResponseEvent::Completed { response_id, token_usage, - can_append, } => { assert_eq!(response_id, "resp1"); assert!(token_usage.is_none()); - assert!(!can_append); } other => panic!("unexpected third event: {other:?}"), } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 534b1ec5e4..9e5974b487 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -20,9 +20,8 @@ //! //! ## Retry-Budget Tradeoff //! -//! V2 request prewarm is treated as the first websocket connection attempt for a turn. If it -//! fails, normal stream retry/fallback logic handles recovery on the same turn. V1 prewarm -//! remains connection-only. +//! WebSocket prewarm is treated as the first websocket connection attempt for a turn. If it +//! fails, normal stream retry/fallback logic handles recovery on the same turn. use std::collections::HashMap; use std::sync::Arc; @@ -43,7 +42,6 @@ use codex_api::MemorySummarizeOutput as ApiMemorySummarizeOutput; use codex_api::RawMemory as ApiRawMemory; use codex_api::RequestTelemetry; use codex_api::ReqwestTransport; -use codex_api::ResponseAppendWsRequest; use codex_api::ResponseCreateWsRequest; use codex_api::ResponsesApiRequest; use codex_api::ResponsesClient as ApiResponsesClient; @@ -101,32 +99,19 @@ use crate::model_provider_info::WireApi; use crate::tools::spec::create_tools_json_for_responses_api; pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta"; -pub const OPENAI_BETA_RESPONSES_WEBSOCKETS: &str = "responses_websockets=2026-02-04"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = "x-responsesapi-include-timing-metrics"; const RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE: &str = "responses_websockets=2026-02-06"; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ResponsesWebsocketVersion { - V1, - V2, -} - -pub fn ws_version_from_features(config: &Config) -> Option { - match ( - config +pub fn ws_version_from_features(config: &Config) -> bool { + config + .features + .enabled(crate::features::Feature::ResponsesWebsockets) + || config .features - .enabled(crate::features::Feature::ResponsesWebsockets), - config - .features - .enabled(crate::features::Feature::ResponsesWebsocketsV2), - ) { - (_, true) => Some(ResponsesWebsocketVersion::V2), - (true, false) => Some(ResponsesWebsocketVersion::V1), - (false, false) => None, - } + .enabled(crate::features::Feature::ResponsesWebsocketsV2) } /// Session-scoped state shared by all [`ModelClient`] clones. @@ -140,7 +125,7 @@ struct ModelClientState { provider: ModelProviderInfo, session_source: SessionSource, model_verbosity: Option, - responses_websocket_version: Option, + responses_websockets_enabled_by_feature: bool, enable_request_compression: bool, include_timing_metrics: bool, beta_features_header: Option, @@ -180,8 +165,8 @@ pub struct ModelClient { /// The session establishes a Responses WebSocket connection lazily and reuses it across multiple /// requests within the turn. It also caches per-turn state: /// -/// - The last full request, so subsequent calls can use `response.append` only when the current -/// request is an incremental extension of the previous one. +/// - The last full request, so subsequent calls can reuse incremental websocket request payloads +/// only when the current request is an incremental extension of the previous one. /// - The `x-codex-turn-state` sticky-routing token, which must be replayed for all requests within /// the same turn. /// @@ -208,7 +193,6 @@ pub struct ModelClientSession { struct LastResponse { response_id: String, items_added: Vec, - can_append: bool, } #[derive(Debug, Default)] @@ -235,7 +219,7 @@ impl ModelClient { provider: ModelProviderInfo, session_source: SessionSource, model_verbosity: Option, - responses_websocket_version: Option, + responses_websockets_enabled_by_feature: bool, enable_request_compression: bool, include_timing_metrics: bool, beta_features_header: Option, @@ -247,7 +231,7 @@ impl ModelClient { provider, session_source, model_verbosity, - responses_websocket_version, + responses_websockets_enabled_by_feature, enable_request_compression, include_timing_metrics, beta_features_header, @@ -391,25 +375,21 @@ impl ModelClient { request_telemetry } - /// Returns the active Responses-over-WebSocket version for this session. + /// Returns whether the Responses-over-WebSocket transport is active for this session. /// /// This combines provider capability and feature gating; both must be true for websocket paths /// to be eligible. /// /// If websockets are only enabled via model preference (no explicit feature flag), prefer the /// current v2 behavior. - pub fn active_ws_version(&self, model_info: &ModelInfo) -> Option { + pub fn responses_websocket_enabled(&self, model_info: &ModelInfo) -> bool { if !self.state.provider.supports_websockets || self.state.disable_websockets.load(Ordering::Relaxed) { - return None; + return false; } - match self.state.responses_websocket_version { - Some(version) => Some(version), - None if model_info.prefer_websockets => Some(ResponsesWebsocketVersion::V2), - None => None, - } + self.state.responses_websockets_enabled_by_feature || model_info.prefer_websockets } /// Returns auth + provider configuration resolved from the current session auth state. @@ -442,12 +422,10 @@ impl ModelClient { otel_manager: &OtelManager, api_provider: codex_api::Provider, api_auth: CoreAuthProvider, - ws_version: ResponsesWebsocketVersion, turn_state: Option>>, turn_metadata_header: Option<&str>, ) -> std::result::Result { - let headers = - self.build_websocket_headers(ws_version, turn_state.as_ref(), turn_metadata_header); + let headers = self.build_websocket_headers(turn_state.as_ref(), turn_metadata_header); let websocket_telemetry = ModelClientSession::build_websocket_telemetry(otel_manager); ApiWebSocketResponsesClient::new(api_provider, api_auth) .connect( @@ -465,7 +443,6 @@ impl ModelClient { /// replayed on reconnect within the same turn. fn build_websocket_headers( &self, - ws_version: ResponsesWebsocketVersion, turn_state: Option<&Arc>>, turn_metadata_header: Option<&str>, ) -> ApiHeaderMap { @@ -478,13 +455,9 @@ impl ModelClient { headers.extend(build_conversation_headers(Some( self.state.conversation_id.to_string(), ))); - let responses_websockets_beta_header = match ws_version { - ResponsesWebsocketVersion::V2 => RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE, - ResponsesWebsocketVersion::V1 => OPENAI_BETA_RESPONSES_WEBSOCKETS, - }; headers.insert( OPENAI_BETA_HEADER, - HeaderValue::from_static(responses_websockets_beta_header), + HeaderValue::from_static(RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE), ); if self.state.include_timing_metrics { headers.insert( @@ -613,8 +586,9 @@ impl ModelClientSession { last_response: Option<&LastResponse>, allow_empty_delta: bool, ) -> Option> { - // Checks whether the current request is an incremental append to the previous request. - // We only append when non-input request fields are unchanged and `input` is a strict + // Checks whether the current request is an incremental extension of the previous request. + // We only reuse an incremental input delta when non-input request fields are unchanged and + // `input` is a strict // extension of the previous known input. Server-returned output items are treated as part // of the baseline so we do not resend them. let previous_request = self.websocket_session.last_request.as_ref()?; @@ -659,42 +633,26 @@ impl ModelClientSession { &mut self, payload: ResponseCreateWsRequest, request: &ResponsesApiRequest, - ws_version: ResponsesWebsocketVersion, ) -> ResponsesWsRequest { let Some(last_response) = self.get_last_response() else { return ResponsesWsRequest::ResponseCreate(payload); }; - let allow_empty_delta = matches!(ws_version, ResponsesWebsocketVersion::V2); - let Some(append_items) = - self.get_incremental_items(request, Some(&last_response), allow_empty_delta) + let Some(incremental_items) = + self.get_incremental_items(request, Some(&last_response), true) else { return ResponsesWsRequest::ResponseCreate(payload); }; - match ws_version { - ResponsesWebsocketVersion::V2 => { - if last_response.response_id.is_empty() { - trace!("incremental request failed, no previous response id"); - return ResponsesWsRequest::ResponseCreate(payload); - } - - ResponsesWsRequest::ResponseCreate(ResponseCreateWsRequest { - previous_response_id: Some(last_response.response_id), - input: append_items, - ..payload - }) - } - ResponsesWebsocketVersion::V1 => { - if !last_response.can_append { - trace!("incremental request failed, can't append"); - return ResponsesWsRequest::ResponseCreate(payload); - } - ResponsesWsRequest::ResponseAppend(ResponseAppendWsRequest { - input: append_items, - client_metadata: payload.client_metadata, - }) - } + if last_response.response_id.is_empty() { + trace!("incremental request failed, no previous response id"); + return ResponsesWsRequest::ResponseCreate(payload); } + + ResponsesWsRequest::ResponseCreate(ResponseCreateWsRequest { + previous_response_id: Some(last_response.response_id), + input: incremental_items, + ..payload + }) } /// Opportunistically preconnects a websocket for this turn-scoped client session. @@ -705,9 +663,9 @@ impl ModelClientSession { otel_manager: &OtelManager, model_info: &ModelInfo, ) -> std::result::Result<(), ApiError> { - let Some(ws_version) = self.client.active_ws_version(model_info) else { + if !self.client.responses_websocket_enabled(model_info) { return Ok(()); - }; + } if self.websocket_session.connection.is_some() { return Ok(()); } @@ -724,7 +682,6 @@ impl ModelClientSession { otel_manager, client_setup.api_provider, client_setup.api_auth, - ws_version, Some(Arc::clone(&self.turn_state)), None, ) @@ -738,7 +695,6 @@ impl ModelClientSession { otel_manager: &OtelManager, api_provider: codex_api::Provider, api_auth: CoreAuthProvider, - ws_version: ResponsesWebsocketVersion, turn_metadata_header: Option<&str>, options: &ApiResponsesOptions, ) -> std::result::Result<&ApiWebSocketConnection, ApiError> { @@ -760,7 +716,6 @@ impl ModelClientSession { otel_manager, api_provider, api_auth, - ws_version, Some(turn_state), turn_metadata_header, ) @@ -862,7 +817,6 @@ impl ModelClientSession { &mut self, prompt: &Prompt, model_info: &ModelInfo, - ws_version: ResponsesWebsocketVersion, otel_manager: &OtelManager, effort: Option, summary: ReasoningSummaryConfig, @@ -901,7 +855,6 @@ impl ModelClientSession { otel_manager, client_setup.api_provider, client_setup.api_auth, - ws_version, turn_metadata_header, &options, ) @@ -922,7 +875,7 @@ impl ModelClientSession { Err(err) => return Err(map_api_error(err)), } - let ws_request = self.prepare_websocket_request(ws_payload, &request, ws_version); + let ws_request = self.prepare_websocket_request(ws_payload, &request); self.websocket_session.last_request = Some(request); let stream_result = self .websocket_session @@ -971,17 +924,10 @@ impl ModelClientSession { service_tier: Option, turn_metadata_header: Option<&str>, ) -> Result<()> { - let Some(ws_version) = self.client.active_ws_version(model_info) else { - return Ok(()); - }; - if self.websocket_session.last_request.is_some() { + if !self.client.responses_websocket_enabled(model_info) { return Ok(()); } - - if matches!(ws_version, ResponsesWebsocketVersion::V1) { - self.preconnect_websocket(otel_manager, model_info) - .await - .map_err(map_api_error)?; + if self.websocket_session.last_request.is_some() { return Ok(()); } @@ -989,7 +935,6 @@ impl ModelClientSession { .stream_responses_websocket( prompt, model_info, - ws_version, otel_manager, effort, summary, @@ -1038,12 +983,11 @@ impl ModelClientSession { let wire_api = self.client.state.provider.wire_api; match wire_api { WireApi::Responses => { - if let Some(ws_version) = self.client.active_ws_version(model_info) { + if self.client.responses_websocket_enabled(model_info) { match self .stream_responses_websocket( prompt, model_info, - ws_version, otel_manager, effort, summary, @@ -1085,7 +1029,7 @@ impl ModelClientSession { otel_manager: &OtelManager, model_info: &ModelInfo, ) -> bool { - let websocket_enabled = self.client.active_ws_version(model_info).is_some(); + let websocket_enabled = self.client.responses_websocket_enabled(model_info); let activated = self.activate_http_fallback(websocket_enabled); if activated { warn!("falling back to HTTP"); @@ -1183,7 +1127,6 @@ where Ok(ResponseEvent::Completed { response_id, token_usage, - can_append, }) => { if let Some(usage) = &token_usage { otel_manager.sse_event_completed( @@ -1198,14 +1141,12 @@ where let _ = sender.send(LastResponse { response_id: response_id.clone(), items_added: std::mem::take(&mut items_added), - can_append, }); } if tx_event .send(Ok(ResponseEvent::Completed { response_id, token_usage, - can_append, })) .await .is_err() @@ -1335,7 +1276,7 @@ mod tests { provider, session_source, None, - None, + false, false, false, None, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 7b540aa4d8..3a28a3e83b 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -5572,12 +5572,10 @@ async fn run_sampling_request( // transient reconnect messages. In debug builds, keep full visibility for diagnosis. let report_error = retries > 1 || cfg!(debug_assertions) - || sess + || !sess .services .model_client - .active_ws_version(&turn_context.model_info) - .is_none(); - + .responses_websocket_enabled(&turn_context.model_info); if report_error { // Surface retry information to any UI/front‑end so the // user understands what is happening instead of staring @@ -6418,7 +6416,6 @@ async fn try_run_sampling_request( ResponseEvent::Completed { response_id: _, token_usage, - can_append: _, } => { flush_assistant_text_segments_all( &sess, @@ -9194,7 +9191,7 @@ mod tests { handlers::run_user_shell_command(&session, "sub-id".to_string(), "echo shell".to_string()) .await; - let deadline = StdDuration::from_secs(5); + let deadline = StdDuration::from_secs(15); let start = std::time::Instant::now(); loop { let remaining = deadline.saturating_sub(start.elapsed()); diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index baabf8a42f..6c7dbb1050 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -109,7 +109,8 @@ async fn run_compact_task_inner( let max_retries = turn_context.provider.stream_max_retries(); let mut retries = 0; let mut client_session = sess.services.model_client.new_session(); - // Reuse one client session so turn-scoped state (sticky routing, websocket append tracking) + // Reuse one client session so turn-scoped state (sticky routing, websocket incremental + // request tracking) // survives retries within this compact turn. loop { diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index ef988f1c52..828bbe214a 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -147,7 +147,6 @@ pub(crate) use codex_shell_command::powershell; pub use client::ModelClient; pub use client::ModelClientSession; -pub use client::ResponsesWebsocketVersion; pub use client::X_CODEX_TURN_METADATA_HEADER; pub use client::ws_version_from_features; pub use client_common::Prompt; diff --git a/codex-rs/core/tests/common/responses.rs b/codex-rs/core/tests/common/responses.rs index c1a40e90fc..4f2e89636e 100644 --- a/codex-rs/core/tests/common/responses.rs +++ b/codex-rs/core/tests/common/responses.rs @@ -582,25 +582,6 @@ pub fn ev_completed(id: &str) -> Value { }) } -pub fn ev_done() -> Value { - serde_json::json!({ - "type": "response.done", - "response": { - "usage": {"input_tokens":0,"input_tokens_details":null,"output_tokens":0,"output_tokens_details":null,"total_tokens":0} - } - }) -} - -pub fn ev_done_with_id(id: &str) -> Value { - serde_json::json!({ - "type": "response.done", - "response": { - "id": id, - "usage": {"input_tokens":0,"input_tokens_details":null,"output_tokens":0,"output_tokens_details":null,"total_tokens":0} - } - }) -} - /// Convenience: SSE event for a created response with a specific id. pub fn ev_response_created(id: &str) -> Value { serde_json::json!({ diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index a46c45f234..be41a25a0f 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -6,7 +6,6 @@ use codex_core::ModelClient; use codex_core::ModelProviderInfo; use codex_core::Prompt; use codex_core::ResponseEvent; -use codex_core::ResponsesWebsocketVersion; use codex_core::WireApi; use codex_otel::OtelManager; use codex_otel::TelemetryAuthMode; @@ -92,7 +91,7 @@ async fn responses_stream_includes_subagent_header_on_review() { provider.clone(), session_source, config.model_verbosity, - None::, + false, false, false, None, @@ -205,7 +204,7 @@ async fn responses_stream_includes_subagent_header_on_other() { provider.clone(), session_source, config.model_verbosity, - None::, + false, false, false, None, @@ -317,7 +316,7 @@ async fn responses_respects_model_info_overrides_from_config() { provider.clone(), session_source, config.model_verbosity, - None::, + false, false, false, None, diff --git a/codex-rs/core/tests/suite/agent_websocket.rs b/codex-rs/core/tests/suite/agent_websocket.rs index fdd9f316d6..0db3cf61ad 100644 --- a/codex-rs/core/tests/suite/agent_websocket.rs +++ b/codex-rs/core/tests/suite/agent_websocket.rs @@ -4,8 +4,6 @@ use codex_protocol::config_types::ServiceTier; use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; -use core_test_support::responses::ev_done; -use core_test_support::responses::ev_done_with_id; use core_test_support::responses::ev_response_created; use core_test_support::responses::ev_shell_command_call; use core_test_support::responses::start_websocket_server; @@ -27,7 +25,7 @@ async fn websocket_test_codex_shell_chain() -> Result<()> { vec![ ev_response_created("resp-1"), ev_shell_command_call(call_id, "echo websocket"), - ev_done(), + ev_completed("resp-1"), ], vec![ ev_response_created("resp-2"), @@ -59,36 +57,30 @@ async fn websocket_test_codex_shell_chain() -> Result<()> { .body_json(); assert_eq!(first_turn["type"].as_str(), Some("response.create")); - assert_eq!(second_turn["type"].as_str(), Some("response.append")); + assert_eq!(second_turn["type"].as_str(), Some("response.create")); - let append_items = second_turn + let input_items = second_turn .get("input") .and_then(Value::as_array) - .expect("response.append input array"); - assert!(!append_items.is_empty()); - - let output_item = append_items - .iter() - .find(|item| item.get("type").and_then(Value::as_str) == Some("function_call_output")) - .expect("function_call_output in append"); - assert_eq!( - output_item.get("call_id").and_then(Value::as_str), - Some(call_id) - ); + .expect("second response.create input array"); + assert!(!input_items.is_empty()); server.shutdown().await; Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn websocket_first_turn_uses_preconnect_and_create() -> Result<()> { +async fn websocket_first_turn_uses_startup_prewarm_and_create() -> Result<()> { skip_if_no_network!(Ok(())); - let server = start_websocket_server(vec![vec![vec![ - ev_response_created("resp-1"), - ev_assistant_message("msg-1", "hello"), - ev_completed("resp-1"), - ]]]) + let server = start_websocket_server(vec![vec![ + vec![ev_response_created("warm-1"), ev_completed("warm-1")], + vec![ + ev_response_created("resp-1"), + ev_assistant_message("msg-1", "hello"), + ev_completed("resp-1"), + ], + ]]) .await; let mut builder = test_codex(); @@ -101,11 +93,14 @@ async fn websocket_first_turn_uses_preconnect_and_create() -> Result<()> { assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); - assert_eq!(connection.len(), 1); - let turn = connection + assert_eq!(connection.len(), 2); + let warmup = connection .first() - .expect("missing turn request") + .expect("missing warmup request") .body_json(); + let turn = connection.get(1).expect("missing turn request").body_json(); + assert_eq!(warmup["type"].as_str(), Some("response.create")); + assert_eq!(warmup["generate"].as_bool(), Some(false)); assert!( turn["tools"] .as_array() @@ -119,15 +114,18 @@ async fn websocket_first_turn_uses_preconnect_and_create() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn websocket_first_turn_handles_handshake_delay_with_preconnect() -> Result<()> { +async fn websocket_first_turn_handles_handshake_delay_with_startup_prewarm() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_websocket_server_with_headers(vec![WebSocketConnectionConfig { - requests: vec![vec![ - ev_response_created("resp-1"), - ev_assistant_message("msg-1", "hello"), - ev_completed("resp-1"), - ]], + requests: vec![ + vec![ev_response_created("warm-1"), ev_completed("warm-1")], + vec![ + ev_response_created("resp-1"), + ev_assistant_message("msg-1", "hello"), + ev_completed("resp-1"), + ], + ], response_headers: Vec::new(), // Delay handshake so turn processing must tolerate websocket startup latency. accept_delay: Some(Duration::from_millis(150)), @@ -144,11 +142,14 @@ async fn websocket_first_turn_handles_handshake_delay_with_preconnect() -> Resul assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); - assert_eq!(connection.len(), 1); - let turn = connection + assert_eq!(connection.len(), 2); + let warmup = connection .first() - .expect("missing turn request") + .expect("missing warmup request") .body_json(); + let turn = connection.get(1).expect("missing turn request").body_json(); + assert_eq!(warmup["type"].as_str(), Some("response.create")); + assert_eq!(warmup["generate"].as_bool(), Some(false)); assert!( turn["tools"] .as_array() @@ -167,7 +168,7 @@ async fn websocket_v2_test_codex_shell_chain() -> Result<()> { let call_id = "shell-command-call"; let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ ev_response_created("resp-1"), ev_shell_command_call(call_id, "echo websocket"), @@ -251,7 +252,7 @@ async fn websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm() skip_if_no_network!(Ok(())); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "fast"), @@ -300,7 +301,7 @@ async fn websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm() -> Resu skip_if_no_network!(Ok(())); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "standard"), @@ -349,7 +350,7 @@ async fn websocket_v2_next_turn_uses_updated_service_tier() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "fast"), diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 8521c91eb1..d4a8ce9f3e 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -4,7 +4,6 @@ use codex_core::ModelProviderInfo; use codex_core::NewThread; use codex_core::Prompt; use codex_core::ResponseEvent; -use codex_core::ResponsesWebsocketVersion; use codex_core::ThreadManager; use codex_core::WireApi; use codex_core::auth::AuthCredentialsStoreMode; @@ -1643,7 +1642,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { provider.clone(), SessionSource::Exec, config.model_verbosity, - None::, + false, false, false, None, diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 2c2dc7c7cf..b2098c039c 100755 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -31,8 +31,6 @@ use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::WebSocketTestServer; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; -use core_test_support::responses::ev_done; -use core_test_support::responses::ev_done_with_id; use core_test_support::responses::ev_response_created; use core_test_support::responses::start_websocket_server; use core_test_support::responses::start_websocket_server_with_headers; @@ -50,7 +48,6 @@ use tracing_test::traced_test; const MODEL: &str = "gpt-5.2-codex"; const OPENAI_BETA_HEADER: &str = "OpenAI-Beta"; -const OPENAI_BETA_RESPONSES_WEBSOCKETS: &str = "responses_websockets=2026-02-04"; const WS_V2_BETA_HEADER_VALUE: &str = "responses_websockets=2026-02-06"; struct WebsocketTestHarness { @@ -89,7 +86,7 @@ async fn responses_websocket_streams_request() { let handshake = server.single_handshake(); assert_eq!( handshake.header(OPENAI_BETA_HEADER), - Some(OPENAI_BETA_RESPONSES_WEBSOCKETS.to_string()) + Some(WS_V2_BETA_HEADER_VALUE.to_string()) ); server.shutdown().await; @@ -125,7 +122,7 @@ async fn responses_websocket_request_prewarm_reuses_connection() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ev_response_created("resp-1"), ev_completed("resp-1")], ]]) .await; @@ -244,7 +241,7 @@ async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes( skip_if_no_network!(); let server = start_websocket_server(vec![vec![ - vec![ev_response_created("warm-1"), ev_done_with_id("warm-1")], + vec![ev_response_created("warm-1"), ev_completed("warm-1")], vec![ev_response_created("resp-1"), ev_completed("resp-1")], ]]) .await; @@ -344,13 +341,6 @@ async fn responses_websocket_prewarm_uses_v2_when_model_prefers_websockets_and_f .map(str::trim) .any(|value| value == WS_V2_BETA_HEADER_VALUE) ); - assert!( - !openai_beta_header - .split(',') - .map(str::trim) - .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) - ); - stream_until_complete(&mut client_session, &harness, &prompt).await; assert_eq!(server.handshakes().len(), 1); let connection = server.single_connection(); @@ -404,13 +394,6 @@ async fn responses_websocket_preconnect_runs_when_only_v2_feature_enabled() { .map(str::trim) .any(|value| value == WS_V2_BETA_HEADER_VALUE) ); - assert!( - !openai_beta_header - .split(',') - .map(str::trim) - .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) - ); - server.shutdown().await; } @@ -422,7 +405,7 @@ async fn responses_websocket_v2_requests_use_v2_when_model_prefers_websockets() vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done_with_id("resp-1"), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -460,13 +443,6 @@ async fn responses_websocket_v2_requests_use_v2_when_model_prefers_websockets() .map(str::trim) .any(|value| value == WS_V2_BETA_HEADER_VALUE) ); - assert!( - !openai_beta_header - .split(',') - .map(str::trim) - .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) - ); - server.shutdown().await; } @@ -478,7 +454,7 @@ async fn responses_websocket_v2_incremental_requests_are_reused_across_turns() { vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done_with_id("resp-1"), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -522,7 +498,7 @@ async fn responses_websocket_v2_wins_when_both_features_enabled() { vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done_with_id("resp-1"), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -560,13 +536,6 @@ async fn responses_websocket_v2_wins_when_both_features_enabled() { .map(str::trim) .any(|value| value == WS_V2_BETA_HEADER_VALUE) ); - assert!( - !openai_beta_header - .split(',') - .map(str::trim) - .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) - ); - server.shutdown().await; } @@ -835,7 +804,14 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() { } }); - let server = start_websocket_server(vec![vec![vec![usage_limit_error]]]).await; + let server = start_websocket_server(vec![vec![ + vec![ + ev_response_created("resp-prewarm"), + ev_completed("resp-prewarm"), + ], + vec![usage_limit_error], + ]]) + .await; let mut builder = test_codex().with_config(|config| { config.model_provider.request_max_retries = Some(0); config.model_provider.stream_max_retries = Some(0); @@ -913,7 +889,14 @@ async fn responses_websocket_invalid_request_error_with_status_is_forwarded() { } }); - let server = start_websocket_server(vec![vec![vec![invalid_request_error]]]).await; + let server = start_websocket_server(vec![vec![ + vec![ + ev_response_created("resp-prewarm"), + ev_completed("resp-prewarm"), + ], + vec![invalid_request_error], + ]]) + .await; let mut builder = test_codex().with_config(|config| { config.model_provider.request_max_retries = Some(0); config.model_provider.stream_max_retries = Some(0); @@ -990,14 +973,14 @@ async fn responses_websocket_connection_limit_error_reconnects_and_completes() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_websocket_appends_on_prefix() { +async fn responses_websocket_uses_incremental_create_on_prefix() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done(), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -1024,24 +1007,25 @@ async fn responses_websocket_appends_on_prefix() { assert_eq!(first["model"].as_str(), Some(MODEL)); assert_eq!(first["stream"], serde_json::Value::Bool(true)); assert_eq!(first["input"].as_array().map(Vec::len), Some(1)); - let expected_append = serde_json::json!({ - "type": "response.append", - "input": serde_json::to_value(&prompt_two.input[2..]).expect("serialize append items"), - }); - assert_eq!(second, expected_append); + assert_eq!(second["type"].as_str(), Some("response.create")); + assert_eq!(second["previous_response_id"].as_str(), Some("resp-1")); + assert_eq!( + second["input"], + serde_json::to_value(&prompt_two.input[2..]).expect("serialize incremental items") + ); server.shutdown().await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_websocket_forwards_turn_metadata_on_create_and_append() { +async fn responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done(), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -1085,7 +1069,8 @@ async fn responses_websocket_forwards_turn_metadata_on_create_and_append() { first["client_metadata"]["x-codex-turn-metadata"].as_str(), Some(first_turn_metadata) ); - assert_eq!(second["type"].as_str(), Some("response.append")); + assert_eq!(second["type"].as_str(), Some("response.create")); + assert_eq!(second["previous_response_id"].as_str(), Some("resp-1")); assert_eq!( second["client_metadata"]["x-codex-turn-metadata"].as_str(), Some(enriched_turn_metadata) @@ -1107,7 +1092,7 @@ async fn responses_websocket_forwards_turn_metadata_on_create_and_append() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn responses_websocket_creates_on_prefix_when_previous_completion_cannot_append() { +async fn responses_websocket_uses_previous_response_id_when_prefix_after_completed() { skip_if_no_network!(); let server = start_websocket_server(vec![vec![ @@ -1137,9 +1122,10 @@ async fn responses_websocket_creates_on_prefix_when_previous_completion_cannot_a let second = connection.get(1).expect("missing request").body_json(); assert_eq!(second["type"].as_str(), Some("response.create")); + assert_eq!(second["previous_response_id"].as_str(), Some("resp-1")); assert_eq!( second["input"], - serde_json::to_value(&prompt_two.input).expect("serialize full input") + serde_json::to_value(&prompt_two.input[2..]).expect("serialize incremental input") ); server.shutdown().await; @@ -1222,7 +1208,7 @@ async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix() { vec![ ev_response_created("resp-1"), ev_assistant_message("msg-1", "assistant output"), - ev_done_with_id("resp-1"), + ev_completed("resp-1"), ], vec![ev_response_created("resp-2"), ev_completed("resp-2")], ]]) @@ -1409,13 +1395,6 @@ async fn responses_websocket_v2_sets_openai_beta_header() { .map(str::trim) .any(|value| value == WS_V2_BETA_HEADER_VALUE) ); - assert!( - !openai_beta_header - .split(',') - .map(str::trim) - .any(|value| value == OPENAI_BETA_RESPONSES_WEBSOCKETS) - ); - server.shutdown().await; } diff --git a/codex-rs/core/tests/suite/turn_state.rs b/codex-rs/core/tests/suite/turn_state.rs index 62bd974393..c068cfafb1 100644 --- a/codex-rs/core/tests/suite/turn_state.rs +++ b/codex-rs/core/tests/suite/turn_state.rs @@ -4,7 +4,6 @@ use anyhow::Result; use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; -use core_test_support::responses::ev_done; use core_test_support::responses::ev_reasoning_item; use core_test_support::responses::ev_response_created; use core_test_support::responses::ev_shell_command_call; @@ -100,7 +99,7 @@ async fn websocket_turn_state_persists_within_turn_and_resets_after() -> Result< ev_response_created("resp-1"), ev_reasoning_item("rsn-1", &["thinking"], &[]), ev_shell_command_call(call_id, "echo websocket"), - ev_done(), + ev_completed("resp-1"), ]], response_headers: vec![(TURN_STATE_HEADER.to_string(), "ts-1".to_string())], accept_delay: None, diff --git a/codex-rs/core/tests/suite/websocket_fallback.rs b/codex-rs/core/tests/suite/websocket_fallback.rs index 5be4bc9109..fadd427c16 100644 --- a/codex-rs/core/tests/suite/websocket_fallback.rs +++ b/codex-rs/core/tests/suite/websocket_fallback.rs @@ -66,9 +66,9 @@ async fn websocket_fallback_switches_to_http_on_upgrade_required_connect() -> Re .filter(|req| req.method == Method::POST && req.url.path().ends_with("/responses")) .count(); - // Startup prewarm now only preconnects for v1 (one websocket GET with no request body). - // The first turn then attempts websocket once, sees 426, and falls back to HTTP. - assert_eq!(websocket_attempts, 2); + // The startup prewarm request sees 426 and immediately switches the session to HTTP fallback, + // so the first turn goes straight to HTTP with no additional websocket connect attempt. + assert_eq!(websocket_attempts, 1); assert_eq!(http_attempts, 1); assert_eq!(response_mock.requests().len(), 1); From c4bd0aa3b914f0e7e3f09990363521fb60d67706 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 3 Mar 2026 11:17:06 -0800 Subject: [PATCH 07/17] app-server: source /feedback logs from sqlite at trace level (#12969) ## Summary - write app-server SQLite logs at TRACE level when SQLite is enabled - source app-server `/feedback` log attachments from SQLite for the requested thread when available - flush buffered SQLite log writes before `/feedback` queries them so newly emitted events are not lost behind the async inserter - include same-process threadless SQLite rows in those `/feedback` logs so the attachment matches the process-wide feedback buffer more closely - keep the existing in-memory ring buffer fallback unchanged, including when the SQLite query returns no rows ## Details - add a byte-bounded `query_feedback_logs` helper in `codex-state` so `/feedback` does not fetch all rows before truncating - scope SQLite feedback logs to the requested thread plus threadless rows from the same `process_uuid` - format exported SQLite feedback lines with the log level prefix to better match the in-memory feedback formatter - add an explicit `LogDbLayer::flush()` control path and await it in app-server before querying SQLite for feedback logs - pass optional SQLite log bytes through `codex-feedback` as the `codex-logs.log` attachment override - leave TUI behavior unchanged apart from the updated `upload_feedback` call signature - add regression coverage for: - newest-within-budget ordering - excluding oversized newest rows - including same-process threadless rows - keeping the newest suffix across mixed thread and threadless rows - matching the feedback formatter shape aside from span prefixes - falling back to the in-memory snapshot when SQLite returns no logs - flushing buffered SQLite rows before querying ## Follow-up - SQLite feedback exports still do not reproduce span prefixes like `feedback-thread{thread_id=...}:`; there is a `TODO(ccunningham)` in `codex-rs/state/src/log_db.rs` for that follow-up. ## Testing - `cd codex-rs && cargo test -p codex-state` - `cd codex-rs && cargo test -p codex-app-server` - `cd codex-rs && just fmt` --- codex-rs/app-server/Cargo.toml | 1 + .../app-server/src/codex_message_processor.rs | 30 ++ codex-rs/app-server/src/lib.rs | 21 + codex-rs/app-server/src/message_processor.rs | 4 + codex-rs/feedback/src/lib.rs | 3 +- codex-rs/state/src/log_db.rs | 187 ++++++++- codex-rs/state/src/runtime/logs.rs | 391 ++++++++++++++++++ codex-rs/tui/src/bottom_pane/feedback_view.rs | 1 + 8 files changed, 631 insertions(+), 7 deletions(-) diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 8a4ec05fd3..3efc7140af 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -32,6 +32,7 @@ codex-protocol = { workspace = true } codex-app-server-protocol = { workspace = true } codex-feedback = { workspace = true } codex-rmcp-client = { workspace = true } +codex-state = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 3c1c29ecab..ca9eb374c6 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -274,6 +274,7 @@ use codex_protocol::protocol::USER_MESSAGE_BEGIN; use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS; use codex_protocol::user_input::UserInput as CoreInputItem; use codex_rmcp_client::perform_oauth_login_return_url; +use codex_state::log_db::LogDbLayer; use codex_utils_json_to_toml::json_to_toml; use std::collections::HashMap; use std::collections::HashSet; @@ -383,6 +384,7 @@ pub(crate) struct CodexMessageProcessor { pending_fuzzy_searches: Arc>>>, fuzzy_search_sessions: Arc>>, feedback: CodexFeedback, + log_db: Option, } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -417,6 +419,7 @@ pub(crate) struct CodexMessageProcessorArgs { pub(crate) cli_overrides: Vec<(String, TomlValue)>, pub(crate) cloud_requirements: Arc>, pub(crate) feedback: CodexFeedback, + pub(crate) log_db: Option, } impl CodexMessageProcessor { @@ -461,6 +464,7 @@ impl CodexMessageProcessor { cli_overrides, cloud_requirements, feedback, + log_db, } = args; Self { auth_manager, @@ -477,6 +481,7 @@ impl CodexMessageProcessor { pending_fuzzy_searches: Arc::new(Mutex::new(HashMap::new())), fuzzy_search_sessions: Arc::new(Mutex::new(HashMap::new())), feedback, + log_db, } } @@ -6944,6 +6949,30 @@ impl CodexMessageProcessor { let snapshot = self.feedback.snapshot(conversation_id); let thread_id = snapshot.thread_id.clone(); + let sqlite_feedback_logs = if include_logs { + if let Some(log_db) = self.log_db.as_ref() { + log_db.flush().await; + } + let state_db_ctx = get_state_db(&self.config, None).await; + match (state_db_ctx.as_ref(), conversation_id) { + (Some(state_db_ctx), Some(conversation_id)) => { + let thread_id_text = conversation_id.to_string(); + match state_db_ctx.query_feedback_logs(&thread_id_text).await { + Ok(logs) if logs.is_empty() => None, + Ok(logs) => Some(logs), + Err(err) => { + warn!( + "failed to query feedback logs from sqlite for thread_id={thread_id_text}: {err}" + ); + None + } + } + } + _ => None, + } + } else { + None + }; let validated_rollout_path = if include_logs { match conversation_id { @@ -6967,6 +6996,7 @@ impl CodexMessageProcessor { include_logs, &attachment_paths, Some(session_source), + sqlite_feedback_logs, ) }) .await; diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index a84712ea05..580282d5d3 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -38,16 +38,20 @@ use codex_core::ExecPolicyError; use codex_core::check_execpolicy_for_warnings; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::TextRange as CoreTextRange; +use codex_core::features::Feature; use codex_feedback::CodexFeedback; +use codex_state::log_db; use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use toml::Value as TomlValue; +use tracing::Level; use tracing::error; use tracing::info; use tracing::warn; use tracing_subscriber::EnvFilter; use tracing_subscriber::Layer; +use tracing_subscriber::filter::Targets; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::registry::Registry; use tracing_subscriber::util::SubscriberInitExt; @@ -477,6 +481,21 @@ pub async fn run_main_with_transport( let feedback_layer = feedback.logger_layer(); let feedback_metadata_layer = feedback.metadata_layer(); + let log_db = if config.features.enabled(Feature::Sqlite) { + codex_state::StateRuntime::init( + config.sqlite_home.clone(), + config.model_provider_id.clone(), + None, + ) + .await + .ok() + .map(log_db::start) + } else { + None + }; + let log_db_layer = log_db + .clone() + .map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE))); let otel_logger_layer = otel.as_ref().and_then(|o| o.logger_layer()); let otel_tracing_layer = otel.as_ref().and_then(|o| o.tracing_layer()); @@ -484,6 +503,7 @@ pub async fn run_main_with_transport( .with(stderr_fmt) .with(feedback_layer) .with(feedback_metadata_layer) + .with(log_db_layer) .with(otel_logger_layer) .with(otel_tracing_layer) .try_init(); @@ -562,6 +582,7 @@ pub async fn run_main_with_transport( loader_overrides, cloud_requirements: cloud_requirements.clone(), feedback: feedback.clone(), + log_db, config_warnings, }); let mut thread_created_rx = processor.thread_created_receiver(); diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 48f2f69e87..f56e12467f 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -54,6 +54,7 @@ use codex_core::models_manager::collaboration_mode_presets::CollaborationModesCo use codex_feedback::CodexFeedback; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; +use codex_state::log_db::LogDbLayer; use futures::FutureExt; use tokio::sync::broadcast; use tokio::sync::watch; @@ -154,6 +155,7 @@ pub(crate) struct MessageProcessorArgs { pub(crate) loader_overrides: LoaderOverrides, pub(crate) cloud_requirements: CloudRequirementsLoader, pub(crate) feedback: CodexFeedback, + pub(crate) log_db: Option, pub(crate) config_warnings: Vec, } @@ -169,6 +171,7 @@ impl MessageProcessor { loader_overrides, cloud_requirements, feedback, + log_db, config_warnings, } = args; let auth_manager = AuthManager::shared( @@ -201,6 +204,7 @@ impl MessageProcessor { cli_overrides: cli_overrides.clone(), cloud_requirements: cloud_requirements.clone(), feedback, + log_db, }); let config_api = ConfigApi::new( config.codex_home.clone(), diff --git a/codex-rs/feedback/src/lib.rs b/codex-rs/feedback/src/lib.rs index a4fe6cc185..ee8e98c39e 100644 --- a/codex-rs/feedback/src/lib.rs +++ b/codex-rs/feedback/src/lib.rs @@ -226,6 +226,7 @@ impl CodexLogSnapshot { include_logs: bool, extra_log_files: &[PathBuf], session_source: Option, + logs_override: Option>, ) -> Result<()> { use std::collections::BTreeMap; use std::fs; @@ -310,7 +311,7 @@ impl CodexLogSnapshot { if include_logs { envelope.add_item(EnvelopeItem::Attachment(Attachment { - buffer: self.bytes.clone(), + buffer: logs_override.unwrap_or_else(|| self.bytes.clone()), filename: String::from("codex-logs.log"), content_type: Some("text/plain".to_string()), ty: None, diff --git a/codex-rs/state/src/log_db.rs b/codex-rs/state/src/log_db.rs index 64deeef2ad..e646828bda 100644 --- a/codex-rs/state/src/log_db.rs +++ b/codex-rs/state/src/log_db.rs @@ -26,6 +26,7 @@ use std::time::SystemTime; use std::time::UNIX_EPOCH; use tokio::sync::mpsc; +use tokio::sync::oneshot; use tracing::Event; use tracing::field::Field; use tracing::field::Visit; @@ -45,7 +46,7 @@ const LOG_FLUSH_INTERVAL: Duration = Duration::from_millis(250); const LOG_RETENTION_DAYS: i64 = 90; pub struct LogDbLayer { - sender: mpsc::Sender, + sender: mpsc::Sender, process_uuid: String, } @@ -61,6 +62,24 @@ pub fn start(state_db: std::sync::Arc) -> LogDbLayer { } } +impl Clone for LogDbLayer { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + process_uuid: self.process_uuid.clone(), + } + } +} + +impl LogDbLayer { + pub async fn flush(&self) { + let (tx, rx) = oneshot::channel(); + if self.sender.send(LogDbCommand::Flush(tx)).await.is_ok() { + let _ = rx.await; + } + } +} + impl Layer for LogDbLayer where S: tracing::Subscriber + for<'a> LookupSpan<'a>, @@ -131,10 +150,15 @@ where line: metadata.line().map(|line| line as i64), }; - let _ = self.sender.try_send(entry); + let _ = self.sender.try_send(LogDbCommand::Entry(entry)); } } +enum LogDbCommand { + Entry(LogEntry), + Flush(oneshot::Sender<()>), +} + #[derive(Clone, Debug, Default)] struct SpanLogContext { thread_id: Option, @@ -215,20 +239,24 @@ fn current_process_log_uuid() -> &'static str { async fn run_inserter( state_db: std::sync::Arc, - mut receiver: mpsc::Receiver, + mut receiver: mpsc::Receiver, ) { let mut buffer = Vec::with_capacity(LOG_BATCH_SIZE); let mut ticker = tokio::time::interval(LOG_FLUSH_INTERVAL); loop { tokio::select! { - maybe_entry = receiver.recv() => { - match maybe_entry { - Some(entry) => { + maybe_command = receiver.recv() => { + match maybe_command { + Some(LogDbCommand::Entry(entry)) => { buffer.push(entry); if buffer.len() >= LOG_BATCH_SIZE { flush(&state_db, &mut buffer).await; } } + Some(LogDbCommand::Flush(reply)) => { + flush(&state_db, &mut buffer).await; + let _ = reply.send(()); + } None => { flush(&state_db, &mut buffer).await; break; @@ -304,3 +332,150 @@ impl Visit for MessageVisitor { self.record_field(field, format!("{value:?}")); } } + +#[cfg(test)] +mod tests { + use std::io; + use std::sync::Arc; + use std::sync::Mutex; + use std::time::Duration; + + use tokio::time::Instant; + use tracing_subscriber::filter::Targets; + use tracing_subscriber::fmt::writer::MakeWriter; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + use super::*; + + #[derive(Clone, Default)] + struct SharedWriter { + bytes: Arc>>, + } + + impl SharedWriter { + fn snapshot(&self) -> String { + String::from_utf8(self.bytes.lock().expect("writer mutex poisoned").clone()) + .expect("valid utf-8") + } + } + + struct SharedWriterGuard { + bytes: Arc>>, + } + + impl<'a> MakeWriter<'a> for SharedWriter { + type Writer = SharedWriterGuard; + + fn make_writer(&'a self) -> Self::Writer { + SharedWriterGuard { + bytes: Arc::clone(&self.bytes), + } + } + } + + impl io::Write for SharedWriterGuard { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.bytes + .lock() + .expect("writer mutex poisoned") + .extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + #[tokio::test] + async fn sqlite_feedback_logs_match_feedback_formatter_shape() { + let codex_home = + std::env::temp_dir().join(format!("codex-state-log-db-{}", Uuid::new_v4())); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + let writer = SharedWriter::default(); + + let subscriber = tracing_subscriber::registry() + .with( + tracing_subscriber::fmt::layer() + .with_writer(writer.clone()) + .without_time() + .with_ansi(false) + .with_target(false) + .with_filter(Targets::new().with_default(tracing::Level::TRACE)), + ) + .with( + start(runtime.clone()) + .with_filter(Targets::new().with_default(tracing::Level::TRACE)), + ); + let guard = subscriber.set_default(); + + tracing::trace!("threadless-before"); + tracing::info_span!("feedback-thread", thread_id = "thread-1").in_scope(|| { + tracing::info!("thread-scoped"); + }); + tracing::debug!("threadless-after"); + + drop(guard); + + // TODO(ccunningham): Store enough span metadata in SQLite to reproduce span + // prefixes like `feedback-thread{thread_id="thread-1"}:` in feedback exports. + let feedback_logs = writer + .snapshot() + .replace("feedback-thread{thread_id=\"thread-1\"}: ", ""); + let deadline = Instant::now() + Duration::from_secs(2); + loop { + let sqlite_logs = String::from_utf8( + runtime + .query_feedback_logs("thread-1") + .await + .expect("query feedback logs"), + ) + .expect("valid utf-8"); + if sqlite_logs == feedback_logs { + break; + } + assert!( + Instant::now() < deadline, + "sqlite feedback logs did not match feedback formatter output before timeout\nsqlite:\n{sqlite_logs}\nfeedback:\n{feedback_logs}" + ); + tokio::time::sleep(Duration::from_millis(10)).await; + } + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn flush_persists_logs_for_query() { + let codex_home = + std::env::temp_dir().join(format!("codex-state-log-db-{}", Uuid::new_v4())); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + let layer = start(runtime.clone()); + + let guard = tracing_subscriber::registry() + .with( + layer + .clone() + .with_filter(Targets::new().with_default(tracing::Level::TRACE)), + ) + .set_default(); + + tracing::info!("buffered-log"); + + layer.flush().await; + drop(guard); + + let after_flush = runtime + .query_logs(&crate::LogQuery::default()) + .await + .expect("query logs after flush"); + assert_eq!(after_flush.len(), 1); + assert_eq!(after_flush[0].message.as_deref(), Some("buffered-log")); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } +} diff --git a/codex-rs/state/src/runtime/logs.rs b/codex-rs/state/src/runtime/logs.rs index 762f68f1a9..2ee8940afd 100644 --- a/codex-rs/state/src/runtime/logs.rs +++ b/codex-rs/state/src/runtime/logs.rs @@ -285,6 +285,67 @@ WHERE id IN ( Ok(rows) } + /// Query per-thread feedback logs, capped to the per-thread SQLite retention budget. + pub async fn query_feedback_logs(&self, thread_id: &str) -> anyhow::Result> { + let max_bytes = LOG_PARTITION_SIZE_LIMIT_BYTES; + let lines = sqlx::query_scalar::<_, String>( + r#" +WITH latest_process AS ( + SELECT process_uuid + FROM logs + WHERE thread_id = ? AND process_uuid IS NOT NULL + ORDER BY ts DESC, ts_nanos DESC, id DESC + LIMIT 1 +), +feedback_logs AS ( + SELECT + printf('%5s %s', level, message) || CASE + WHEN substr(message, -1, 1) = char(10) THEN '' + ELSE char(10) + END AS line, + length(CAST( + printf('%5s %s', level, message) || CASE + WHEN substr(message, -1, 1) = char(10) THEN '' + ELSE char(10) + END AS BLOB + )) AS line_bytes, + ts, + ts_nanos, + id + FROM logs + WHERE message IS NOT NULL AND ( + thread_id = ? + OR ( + thread_id IS NULL + AND process_uuid IN (SELECT process_uuid FROM latest_process) + ) + ) +) +SELECT line +FROM ( + SELECT + line, + ts, + ts_nanos, + id, + SUM(line_bytes) OVER ( + ORDER BY ts DESC, ts_nanos DESC, id DESC + ) AS cumulative_bytes + FROM feedback_logs +) +WHERE cumulative_bytes <= ? +ORDER BY ts ASC, ts_nanos ASC, id ASC +"#, + ) + .bind(thread_id) + .bind(thread_id) + .bind(max_bytes) + .fetch_all(self.pool.as_ref()) + .await?; + + Ok(lines.concat().into_bytes()) + } + /// Return the max log id matching optional filters. pub async fn max_log_id(&self, query: &LogQuery) -> anyhow::Result { let mut builder = @@ -712,4 +773,334 @@ mod tests { let _ = tokio::fs::remove_dir_all(codex_home).await; } + + #[tokio::test] + async fn query_feedback_logs_returns_newest_lines_within_limit_in_order() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + runtime + .insert_logs(&[ + LogEntry { + ts: 1, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("alpha".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 2, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("bravo".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 3, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("charlie".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + ]) + .await + .expect("insert test logs"); + + let bytes = runtime + .query_feedback_logs("thread-1") + .await + .expect("query feedback logs"); + + assert_eq!( + String::from_utf8(bytes).expect("valid utf-8"), + " INFO alpha\n INFO bravo\n INFO charlie\n" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn query_feedback_logs_excludes_oversized_newest_row() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + let eleven_mebibytes = "z".repeat(11 * 1024 * 1024); + + runtime + .insert_logs(&[ + LogEntry { + ts: 1, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("small".to_string()), + thread_id: Some("thread-oversized".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 2, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some(eleven_mebibytes), + thread_id: Some("thread-oversized".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + ]) + .await + .expect("insert test logs"); + + let bytes = runtime + .query_feedback_logs("thread-oversized") + .await + .expect("query feedback logs"); + + assert_eq!(bytes, Vec::::new()); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn query_feedback_logs_includes_threadless_rows_from_same_process() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + runtime + .insert_logs(&[ + LogEntry { + ts: 1, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("threadless-before".to_string()), + thread_id: None, + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 2, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("thread-scoped".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 3, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("threadless-after".to_string()), + thread_id: None, + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 4, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("other-process-threadless".to_string()), + thread_id: None, + process_uuid: Some("proc-2".to_string()), + file: None, + line: None, + module_path: None, + }, + ]) + .await + .expect("insert test logs"); + + let bytes = runtime + .query_feedback_logs("thread-1") + .await + .expect("query feedback logs"); + + assert_eq!( + String::from_utf8(bytes).expect("valid utf-8"), + " INFO threadless-before\n INFO thread-scoped\n INFO threadless-after\n" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn query_feedback_logs_excludes_threadless_rows_from_prior_processes() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + runtime + .insert_logs(&[ + LogEntry { + ts: 1, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("old-process-threadless".to_string()), + thread_id: None, + process_uuid: Some("proc-old".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 2, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("old-process-thread".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-old".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 3, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("new-process-thread".to_string()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-new".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 4, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some("new-process-threadless".to_string()), + thread_id: None, + process_uuid: Some("proc-new".to_string()), + file: None, + line: None, + module_path: None, + }, + ]) + .await + .expect("insert test logs"); + + let bytes = runtime + .query_feedback_logs("thread-1") + .await + .expect("query feedback logs"); + + assert_eq!( + String::from_utf8(bytes).expect("valid utf-8"), + " INFO old-process-thread\n INFO new-process-thread\n INFO new-process-threadless\n" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn query_feedback_logs_keeps_newest_suffix_across_thread_and_threadless_logs() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + let thread_marker = "thread-scoped-oldest"; + let threadless_older_marker = "threadless-older"; + let threadless_newer_marker = "threadless-newer"; + let five_mebibytes = format!("{threadless_older_marker} {}", "a".repeat(5 * 1024 * 1024)); + let four_and_half_mebibytes = format!( + "{threadless_newer_marker} {}", + "b".repeat((9 * 1024 * 1024) / 2) + ); + let one_mebibyte = format!("{thread_marker} {}", "c".repeat(1024 * 1024)); + + runtime + .insert_logs(&[ + LogEntry { + ts: 1, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some(one_mebibyte.clone()), + thread_id: Some("thread-1".to_string()), + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 2, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some(five_mebibytes), + thread_id: None, + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + LogEntry { + ts: 3, + ts_nanos: 0, + level: "INFO".to_string(), + target: "cli".to_string(), + message: Some(four_and_half_mebibytes), + thread_id: None, + process_uuid: Some("proc-1".to_string()), + file: None, + line: None, + module_path: None, + }, + ]) + .await + .expect("insert test logs"); + + let bytes = runtime + .query_feedback_logs("thread-1") + .await + .expect("query feedback logs"); + let logs = String::from_utf8(bytes).expect("valid utf-8"); + + assert!(!logs.contains(thread_marker)); + assert!(logs.contains(threadless_older_marker)); + assert!(logs.contains(threadless_newer_marker)); + assert_eq!(logs.matches('\n').count(), 2); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } } diff --git a/codex-rs/tui/src/bottom_pane/feedback_view.rs b/codex-rs/tui/src/bottom_pane/feedback_view.rs index 4144c87f97..33368faa45 100644 --- a/codex-rs/tui/src/bottom_pane/feedback_view.rs +++ b/codex-rs/tui/src/bottom_pane/feedback_view.rs @@ -102,6 +102,7 @@ impl FeedbackNoteView { self.include_logs, &log_file_paths, Some(SessionSource::Cli), + None, ); match result { From 39f00f2a06d9c8a5b5fb14998d8880ceb489f9ff Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Tue, 3 Mar 2026 11:44:54 -0800 Subject: [PATCH 08/17] chore: rm --all-features flag from rust-analyzer (#13381) follows up on #12429; rm `--all-features` from flags used with `rust-analyzer` on save to prevent disk space bloat under `target/`. --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5adec04a83..1e857367cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "rust-analyzer.checkOnSave": true, "rust-analyzer.check.command": "clippy", - "rust-analyzer.check.extraArgs": ["--all-features", "--tests"], + "rust-analyzer.check.extraArgs": ["--tests"], "rust-analyzer.rustfmt.extraArgs": ["--config", "imports_granularity=Item"], "rust-analyzer.cargo.targetDir": "${workspaceFolder}/codex-rs/target/rust-analyzer", "[rust]": { From c2d008aca59697aff3a65949e7ffed06bd715dea Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:45:34 -0800 Subject: [PATCH 09/17] Collapse parsed command summaries when any stage is unknown (#13043) ## Summary - collapse parsed command output to a single `Unknown` whenever the normal parse includes any unknown entry - preserve the existing parsing flow and existing `cd` handling, including the current `cd && ...` collapse behavior - trim redundant tests and add focused coverage for collapse-on-unknown cases ## Testing - `cargo test -p codex-shell-command` --- codex-rs/shell-command/src/parse_command.rs | 163 +++++++------------- 1 file changed, 53 insertions(+), 110 deletions(-) diff --git a/codex-rs/shell-command/src/parse_command.rs b/codex-rs/shell-command/src/parse_command.rs index 71018a53d1..1c8ac99485 100644 --- a/codex-rs/shell-command/src/parse_command.rs +++ b/codex-rs/shell-command/src/parse_command.rs @@ -37,7 +37,26 @@ pub fn parse_command(command: &[String]) -> Vec { } deduped.push(cmd); } - deduped + if deduped + .iter() + .any(|cmd| matches!(cmd, ParsedCommand::Unknown { .. })) + { + vec![single_unknown_for_command(command)] + } else { + deduped + } +} + +fn single_unknown_for_command(command: &[String]) -> ParsedCommand { + if let Some((_, shell_command)) = extract_shell_command(command) { + ParsedCommand::Unknown { + cmd: shell_command.to_string(), + } + } else { + ParsedCommand::Unknown { + cmd: shlex_join(command), + } + } } #[cfg(test)] @@ -119,7 +138,7 @@ mod tests { assert_parsed( &vec_str(&["bash", "-lc", inner]), vec![ParsedCommand::Unknown { - cmd: "git status".to_string(), + cmd: inner.to_string(), }], ); } @@ -141,24 +160,9 @@ mod tests { "rg --version && node -v && pnpm -v && rg --files | wc -l && rg --files | head -n 40"; assert_parsed( &vec_str(&["bash", "-lc", inner]), - vec![ - // Expect commands in left-to-right execution order - ParsedCommand::Search { - cmd: "rg --version".to_string(), - query: None, - path: None, - }, - ParsedCommand::Unknown { - cmd: "node -v".to_string(), - }, - ParsedCommand::Unknown { - cmd: "pnpm -v".to_string(), - }, - ParsedCommand::ListFiles { - cmd: "rg --files".to_string(), - path: None, - }, - ], + vec![ParsedCommand::Unknown { + cmd: inner.to_string(), + }], ); } @@ -218,16 +222,33 @@ mod tests { let inner = r#"rg -l QkBindingController presentation/src/main/java | xargs perl -pi -e 's/QkBindingController/QkController/g'"#; assert_parsed( &vec_str(&["bash", "-lc", inner]), - vec![ - ParsedCommand::Search { - cmd: "rg -l QkBindingController presentation/src/main/java".to_string(), - query: Some("QkBindingController".to_string()), - path: Some("java".to_string()), - }, - ParsedCommand::Unknown { - cmd: "xargs perl -pi -e s/QkBindingController/QkController/g".to_string(), - }, - ], + vec![ParsedCommand::Unknown { + cmd: inner.to_string(), + }], + ); + } + + #[test] + fn collapses_plain_pipeline_when_any_stage_is_unknown() { + let command = shlex_split_safe( + "rg -l QkBindingController presentation/src/main/java | xargs perl -pi -e 's/QkBindingController/QkController/g'", + ); + assert_parsed( + &command, + vec![ParsedCommand::Unknown { + cmd: shlex_join(&command), + }], + ); + } + + #[test] + fn collapses_pipeline_with_helper_when_later_stage_is_unknown() { + let command = shlex_split_safe("rg --files | nl -ba | foo"); + assert_parsed( + &command, + vec![ParsedCommand::Unknown { + cmd: shlex_join(&command), + }], ); } @@ -395,7 +416,7 @@ mod tests { assert_parsed( &shlex_split_safe("bash -lc 'cd foo && bar'"), vec![ParsedCommand::Unknown { - cmd: "bar".to_string(), + cmd: "cd foo && bar".to_string(), }], ); } @@ -922,85 +943,6 @@ mod tests { ); } - #[test] - fn trim_on_semicolon() { - assert_parsed( - &shlex_split_safe("rg foo ; echo done"), - vec![ - ParsedCommand::Search { - cmd: "rg foo".to_string(), - query: Some("foo".to_string()), - path: None, - }, - ParsedCommand::Unknown { - cmd: "echo done".to_string(), - }, - ], - ); - } - - #[test] - fn split_on_or_connector() { - // Ensure we split commands on the logical OR operator as well. - assert_parsed( - &shlex_split_safe("rg foo || echo done"), - vec![ - ParsedCommand::Search { - cmd: "rg foo".to_string(), - query: Some("foo".to_string()), - path: None, - }, - ParsedCommand::Unknown { - cmd: "echo done".to_string(), - }, - ], - ); - } - - #[test] - fn parses_mixed_sequence_with_pipes_semicolons_and_or() { - // Provided long command sequence combining sequencing, pipelines, and ORs. - let inner = "pwd; ls -la; rg --files -g '!target' | wc -l; rg -n '^\\[workspace\\]' -n Cargo.toml || true; rg -n '^\\[package\\]' -n */Cargo.toml || true; cargo --version; rustc --version; cargo clippy --workspace --all-targets --all-features -q"; - let args = vec_str(&["bash", "-lc", inner]); - - let expected = vec![ - ParsedCommand::Unknown { - cmd: "pwd".to_string(), - }, - ParsedCommand::ListFiles { - cmd: shlex_join(&shlex_split_safe("ls -la")), - path: None, - }, - ParsedCommand::ListFiles { - cmd: shlex_join(&shlex_split_safe("rg --files -g '!target'")), - path: None, - }, - ParsedCommand::Search { - cmd: shlex_join(&shlex_split_safe("rg -n '^\\[workspace\\]' -n Cargo.toml")), - query: Some("^\\[workspace\\]".to_string()), - path: Some("Cargo.toml".to_string()), - }, - ParsedCommand::Search { - cmd: shlex_join(&shlex_split_safe("rg -n '^\\[package\\]' -n */Cargo.toml")), - query: Some("^\\[package\\]".to_string()), - path: Some("Cargo.toml".to_string()), - }, - ParsedCommand::Unknown { - cmd: shlex_join(&shlex_split_safe("cargo --version")), - }, - ParsedCommand::Unknown { - cmd: shlex_join(&shlex_split_safe("rustc --version")), - }, - ParsedCommand::Unknown { - cmd: shlex_join(&shlex_split_safe( - "cargo clippy --workspace --all-targets --all-features -q", - )), - }, - ]; - - assert_parsed(&args, expected); - } - #[test] fn strips_true_in_sequence() { // `true` should be dropped from parsed sequences @@ -1926,6 +1868,7 @@ fn parse_shell_lc_commands(original: &[String]) -> Option> { }; commands.push(parsed); } + if commands.len() > 1 { commands.retain(|pc| !matches!(pc, ParsedCommand::Unknown { cmd } if cmd == "true")); // Apply the same simplifications used for non-bash parsing, e.g., drop leading `cd`. From 61d77d8dafb4bb43aeeb5746c21ebdc848cad0c2 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 20:01:31 +0000 Subject: [PATCH 10/17] nit --- .../artifact-presentation/src/presentation_artifact/pptx.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index ba53ca788b..66e3e7a2e3 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -1063,6 +1063,7 @@ fn export_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( + // codespell:ignore lIns,rIns,tIns,bIns r#"{auto_fit}{paragraphs}"# ) } @@ -1115,6 +1116,7 @@ fn export_table_cell_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( + // codespell:ignore lIns,rIns,tIns,bIns r#" lIns="{left_inset}" rIns="{right_inset}" tIns="{top_inset}" bIns="{bottom_inset}""#, ) }); From 59a1058b3008f8efae8789fd498822f7fa62cbc4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 20:16:57 +0000 Subject: [PATCH 11/17] go further --- .../src/presentation_artifact/manager.rs | 69 +++++++++++++ .../src/presentation_artifact/model.rs | 4 +- .../src/presentation_artifact/parsing.rs | 3 + .../src/presentation_artifact/pptx.rs | 29 ++++++ .../src/presentation_artifact/proto.rs | 4 +- .../src/presentation_artifact/render.rs | 4 +- .../src/presentation_artifact/snapshot.rs | 7 ++ codex-rs/artifact-presentation/src/tests.rs | 98 ++++++++++++++++++- .../templates/tools/presentation_artifact.md | 13 +++ 9 files changed, 224 insertions(+), 7 deletions(-) diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs index aabd63ebb7..fa21fb4d30 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs @@ -104,6 +104,7 @@ impl PresentationArtifactManager { "get_summary" => self.get_summary(request), "list_slides" => self.list_slides(request), "list_layouts" => self.list_layouts(request), + "list_masters" => self.list_masters(request), "list_layout_placeholders" => self.list_layout_placeholders(request), "list_slide_placeholders" => self.list_slide_placeholders(request), "inspect" => self.inspect(request), @@ -149,6 +150,8 @@ impl PresentationArtifactManager { "insert_text_after" => self.insert_text_after(request), "set_hyperlink" => self.set_hyperlink(request), "set_comment_author" => self.set_comment_author(request), + "list_comment_threads" => self.list_comment_threads(request), + "get_comment_thread" => self.get_comment_thread(request), "add_comment_thread" => self.add_comment_thread(request), "add_comment_reply" => self.add_comment_reply(request), "toggle_comment_reaction" => self.toggle_comment_reaction(request), @@ -453,6 +456,24 @@ impl PresentationArtifactManager { Ok(response) } + fn list_masters( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let masters = master_layout_list(document); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Listed {} masters", masters.len()), + snapshot_for_document(document), + ); + response.layout_list = Some(masters); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + fn list_layout_placeholders( &mut self, request: PresentationArtifactRequest, @@ -2512,6 +2533,54 @@ impl PresentationArtifactManager { )) } + fn list_comment_threads( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Listed {} comment threads", document.comment_threads.len()), + snapshot_for_document(document), + ); + response.resolved_record = Some(serde_json::json!({ + "commentSelf": document.comment_self.as_ref().map(comment_author_to_proto), + "commentThreads": document + .comment_threads + .iter() + .map(comment_thread_to_proto) + .collect::>(), + })); + Ok(response) + } + + fn get_comment_thread( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: CommentThreadIdArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let thread = document + .comment_threads + .iter() + .find(|thread| thread.thread_id == args.thread_id) + .ok_or_else(|| PresentationArtifactError::InvalidArgs { + action: request.action.clone(), + message: format!("unknown comment thread `{}`", args.thread_id), + })?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Retrieved comment thread `{}`", args.thread_id), + snapshot_for_document(document), + ); + response.resolved_record = Some(comment_thread_to_proto(thread)); + Ok(response) + } + fn add_comment_thread( &mut self, request: PresentationArtifactRequest, diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs index e81e158122..f5cfc41afa 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs @@ -285,7 +285,7 @@ enum TextVerticalAlignment { } #[derive(Debug, Clone, Serialize, Deserialize)] -struct CommentAuthorProfile { +pub(super) struct CommentAuthorProfile { display_name: String, initials: String, email: Option, @@ -332,7 +332,7 @@ enum CommentTarget { } #[derive(Debug, Clone, Serialize, Deserialize)] -struct CommentThread { +pub(super) struct CommentThread { thread_id: String, target: CommentTarget, position: Option, diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/parsing.rs b/codex-rs/artifact-presentation/src/presentation_artifact/parsing.rs index 58bfa43b91..431be0ffda 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/parsing.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/parsing.rs @@ -25,8 +25,11 @@ fn is_read_only_action(action: &str) -> bool { "get_summary" | "list_slides" | "list_layouts" + | "list_masters" | "list_layout_placeholders" | "list_slide_placeholders" + | "list_comment_threads" + | "get_comment_thread" | "inspect" | "resolve" | "to_proto" diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index 66e3e7a2e3..e297e03ac6 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -1,4 +1,9 @@ const CODEX_METADATA_ENTRY: &str = "ppt/codex-document.json"; +const DEFAULT_SLIDE_MASTER_TEXT_STYLES: &str = r#" + + + +"#; fn import_codex_metadata_document(path: &Path) -> Result, String> { let file = std::fs::File::open(path).map_err(|error| error.to_string())?; @@ -333,6 +338,12 @@ fn patch_pptx_package( .map_err(|error| error.to_string())?; continue; } + if name == "ppt/slideMasters/slideMaster1.xml" { + writer + .write_all(update_slide_master_xml(bytes)?.as_bytes()) + .map_err(|error| error.to_string())?; + continue; + } if let Some(slide_number) = parse_slide_xml_path(&name) { writer .write_all( @@ -452,6 +463,24 @@ fn update_presentation_xml_dimensions( ) } +fn update_slide_master_xml(existing_bytes: Vec) -> Result { + let existing = String::from_utf8(existing_bytes).map_err(|error| error.to_string())?; + if existing.contains("") { + return Ok(existing); + } + + let closing_tag = ""; + let start = existing + .find(closing_tag) + .ok_or_else(|| "slide master xml is missing ``".to_string())?; + Ok(format!( + "{}{}{}", + &existing[..start], + DEFAULT_SLIDE_MASTER_TEXT_STYLES, + &existing[start..] + )) +} + fn replace_self_closing_xml_tag(xml: &str, tag: &str, replacement: &str) -> Result { let start = xml .find(&format!("<{tag} ")) diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/proto.rs b/codex-rs/artifact-presentation/src/presentation_artifact/proto.rs index 05741303ac..c7cd4cabda 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/proto.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/proto.rs @@ -472,7 +472,7 @@ fn chart_data_label_override_to_proto(override_spec: &ChartDataLabelOverride) -> }) } -fn comment_author_to_proto(author: &CommentAuthorProfile) -> Value { +pub(super) fn comment_author_to_proto(author: &CommentAuthorProfile) -> Value { serde_json::json!({ "displayName": author.display_name, "initials": author.initials, @@ -480,7 +480,7 @@ fn comment_author_to_proto(author: &CommentAuthorProfile) -> Value { }) } -fn comment_thread_to_proto(thread: &CommentThread) -> Value { +pub(super) fn comment_thread_to_proto(thread: &CommentThread) -> Value { serde_json::json!({ "kind": "comment", "threadId": thread.thread_id, diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs index 7592136e62..d4f1e10101 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs @@ -1184,7 +1184,9 @@ fn push_text_line( if !matches!(glyph.ch, ' ' | '\t') { break; } - let trimmed = current.pop().expect("last glyph must exist"); + let Some(trimmed) = current.pop() else { + break; + }; *current_width = current_width.saturating_sub(measure_glyph_width(&trimmed, font_px)); if trimmed.ch == ' ' { *current_spaces = current_spaces.saturating_sub(1); diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs b/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs index 8606649c38..df3eb8af2c 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs @@ -69,6 +69,13 @@ fn layout_list(document: &PresentationDocument) -> Vec { .collect() } +fn master_layout_list(document: &PresentationDocument) -> Vec { + layout_list(document) + .into_iter() + .filter(|layout| layout.kind == "master") + .collect() +} + fn points_to_emu(points: u32) -> u32 { points.saturating_mul(POINT_TO_EMU) } diff --git a/codex-rs/artifact-presentation/src/tests.rs b/codex-rs/artifact-presentation/src/tests.rs index 6f468b586c..17ad741e7d 100644 --- a/codex-rs/artifact-presentation/src/tests.rs +++ b/codex-rs/artifact-presentation/src/tests.rs @@ -132,7 +132,7 @@ fn manager_can_import_exported_presentation() -> Result<(), Box Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Slide Master Styles" }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + + let export_path = temp_dir.path().join("slide-master-styles.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let slide_master_xml = zip_entry_text( + &temp_dir.path().join("slide-master-styles.pptx"), + "ppt/slideMasters/slideMaster1.xml", + )?; + assert!(slide_master_xml.contains("")); + assert!(slide_master_xml.contains("")); + assert!(slide_master_xml.contains("")); + assert!(slide_master_xml.contains("")); + Ok(()) +} + #[test] fn exported_charts_are_real_pictures_with_media_parts() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -489,7 +531,7 @@ fn exported_text_shapes_preserve_text_styling() -> Result<(), Box Result<(), Box`, and comment records appear in both `inspect` and `to_proto`. +Use `list_comment_threads` for an explicit collection payload and `get_comment_thread` when you already know the thread id. + +Example list comment threads: +`{"artifact_id":"presentation_x","actions":[{"action":"list_comment_threads","args":{}}]}` + +Example get comment thread: +`{"artifact_id":"presentation_x","actions":[{"action":"get_comment_thread","args":{"thread_id":"thread_1"}}]}` + Charts support richer series metadata plus `update_chart` and `add_chart_series`, including legend, axis, data-label, marker, fill, and per-point override state. Exported PPTX files embed Codex metadata so rich text, comment threads, and advanced table/chart state round-trip through `export_pptx` and `import_pptx` even when the base OOXML representation is lossy. From 8afe2127dcc022bf8fdc0d6a80f957a50652c113 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 3 Mar 2026 12:30:37 -0800 Subject: [PATCH 12/17] Revert "realtime prompt changes" (#13385) Reverts openai/codex#13376 --- codex-rs/protocol/src/prompts/realtime/realtime_end.md | 4 +++- codex-rs/protocol/src/prompts/realtime/realtime_start.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/codex-rs/protocol/src/prompts/realtime/realtime_end.md b/codex-rs/protocol/src/prompts/realtime/realtime_end.md index abec16da03..07ccae5286 100644 --- a/codex-rs/protocol/src/prompts/realtime/realtime_end.md +++ b/codex-rs/protocol/src/prompts/realtime/realtime_end.md @@ -1,3 +1,5 @@ Realtime conversation ended. -Subsequent user input will return to typed text rather than transcript-style text. Do not assume recognition errors or missing punctuation once realtime has ended. Resume normal chat behavior. +You are still operating behind an intermediary rather than speaking to the user directly. Use the conversation transcript and current context to decide whether backend help is actually needed, and avoid verbose responses that only add latency. + +Subsequent user input may return to typed text rather than transcript-style text. Do not assume recognition errors or missing punctuation once realtime has ended. Resume normal chat behavior. diff --git a/codex-rs/protocol/src/prompts/realtime/realtime_start.md b/codex-rs/protocol/src/prompts/realtime/realtime_start.md index 3159a2b07f..c7d363eff7 100644 --- a/codex-rs/protocol/src/prompts/realtime/realtime_start.md +++ b/codex-rs/protocol/src/prompts/realtime/realtime_start.md @@ -6,4 +6,5 @@ When invoked, you receive the latest conversation transcript and any relevant mo When user text is routed from realtime, treat it as a transcript. It may be unpunctuated or contain recognition errors. -- Keep responses concise and action-oriented. Your updates should help the intermediary respond to the user. +- Ask brief clarification questions when needed. +- Keep responses concise and action-oriented. From 1452f52d2c657d037bf72dcf3d1e8ed26f96c1d7 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 20:42:08 +0000 Subject: [PATCH 13/17] go further 2 --- .../src/presentation_artifact/api.rs | 20 +- .../src/presentation_artifact/manager.rs | 152 ++- .../src/presentation_artifact/model.rs | 153 +++ .../src/presentation_artifact/pptx.rs | 6 +- .../src/presentation_artifact/render.rs | 99 +- .../src/presentation_artifact/snapshot.rs | 54 +- codex-rs/artifact-presentation/src/tests.rs | 1121 ++++++++++++++++- 7 files changed, 1428 insertions(+), 177 deletions(-) diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/api.rs b/codex-rs/artifact-presentation/src/presentation_artifact/api.rs index 1b9127e998..24ef78bcc7 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/api.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/api.rs @@ -161,8 +161,16 @@ impl PresentationArtifactRequest { }], ImageInputSource::DataUrl(_) | ImageInputSource::Blob(_) - | ImageInputSource::Uri(_) | ImageInputSource::Placeholder => Vec::new(), + ImageInputSource::Uri(uri) => { + return Err(PresentationArtifactError::UnsupportedFeature { + action: self.action.clone(), + message: format!( + "remote image URIs are not supported for `{}`; download the image locally or provide `data_url`/`blob` instead (`{uri}`)", + self.action + ), + }); + } } } "replace_image" => { @@ -181,8 +189,16 @@ impl PresentationArtifactRequest { }], (None, Some(_), None, None, None) | (None, None, Some(_), None, None) - | (None, None, None, Some(_), None) | (None, None, None, None, Some(_)) => Vec::new(), + (None, None, None, Some(uri), None) => { + return Err(PresentationArtifactError::UnsupportedFeature { + action: self.action.clone(), + message: format!( + "remote image URIs are not supported for `{}`; download the image locally or provide `data_url`/`blob` instead (`{uri}`)", + self.action + ), + }); + } _ => { return Err(PresentationArtifactError::InvalidArgs { action: self.action.clone(), diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs index fa21fb4d30..629f0e332e 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/manager.rs @@ -219,6 +219,9 @@ impl PresentationArtifactManager { } })?; let mut document = PresentationDocument::from_ppt_rs(imported); + if let Some(slide_size) = import_pptx_slide_size(&path)? { + document.slide_size = slide_size; + } import_pptx_images(&path, &mut document, &request.action)?; document }; @@ -1589,10 +1592,6 @@ impl PresentationArtifactManager { ImageInputSource::Uri(uri) => Some(load_image_payload_from_uri(&uri, "replace_image")?), ImageInputSource::Placeholder => None, }; - let fit_mode = args.fit.unwrap_or(ImageFitMode::Stretch); - let lock_aspect_ratio = args - .lock_aspect_ratio - .unwrap_or(fit_mode != ImageFitMode::Stretch); let crop = args .crop .map(|crop| normalize_image_crop(crop, &request.action)) @@ -1606,8 +1605,10 @@ impl PresentationArtifactManager { }); }; image.payload = image_payload; - image.fit_mode = fit_mode; - image.crop = crop; + image.fit_mode = args.fit.unwrap_or(image.fit_mode); + if let Some(crop) = crop { + image.crop = Some(crop); + } if let Some(rotation) = args.rotation { image.rotation_degrees = Some(rotation); } @@ -1617,9 +1618,15 @@ impl PresentationArtifactManager { if let Some(flip_vertical) = args.flip_vertical { image.flip_vertical = flip_vertical; } - image.lock_aspect_ratio = lock_aspect_ratio; - image.alt_text = args.alt; - image.prompt = args.prompt; + if let Some(lock_aspect_ratio) = args.lock_aspect_ratio { + image.lock_aspect_ratio = lock_aspect_ratio; + } + if let Some(alt) = args.alt { + image.alt_text = Some(alt); + } + if let Some(prompt) = args.prompt { + image.prompt = Some(prompt); + } image.is_placeholder = is_placeholder; Ok(PresentationArtifactResponse::new( artifact_id, @@ -1871,12 +1878,43 @@ impl PresentationArtifactManager { message: format!("element `{}` is not a table", args.element_id), }); }; + let row_count = table.rows.len(); + let column_count = table.rows.first().map(Vec::len).unwrap_or(0); + let start_row = args.start_row as usize; + let end_row = args.end_row as usize; + let start_column = args.start_column as usize; + let end_column = args.end_column as usize; + if start_row > end_row || start_column > end_column { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: "merge bounds must be ordered from top-left to bottom-right".to_string(), + }); + } + if end_row >= row_count || end_column >= column_count { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: format!( + "merge bounds [{start_row},{start_column}]..=[{end_row},{end_column}] exceed table size {row_count}x{column_count}" + ), + }); + } let region = TableMergeRegion { - start_row: args.start_row as usize, - end_row: args.end_row as usize, - start_column: args.start_column as usize, - end_column: args.end_column as usize, + start_row, + end_row, + start_column, + end_column, }; + if table.merges.iter().any(|merge| { + merge.start_row <= region.end_row + && region.start_row <= merge.end_row + && merge.start_column <= region.end_column + && region.start_column <= merge.end_column + }) { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: format!("merge region overlaps an existing merge in `{}`", args.element_id), + }); + } table.merges.push(region); Ok(PresentationArtifactResponse::new( artifact_id, @@ -2811,9 +2849,40 @@ impl PresentationArtifactManager { || text_layout.wrap.is_some() || text_layout.auto_fit.is_some() || text_layout.vertical_alignment.is_some(); + let position_rotation = args + .position + .as_ref() + .and_then(|position| position.rotation); + let position_flip_horizontal = args + .position + .as_ref() + .and_then(|position| position.flip_horizontal); + let position_flip_vertical = args + .position + .as_ref() + .and_then(|position| position.flip_vertical); + let has_position_transform = position_rotation.is_some() + || position_flip_horizontal.is_some() + || position_flip_vertical.is_some(); + let has_image_fields = + args.fit.is_some() || args.crop.is_some() || args.lock_aspect_ratio.is_some(); let element = document.find_element_mut(&args.element_id, &request.action)?; match element { PresentationElement::Text(text) => { + if args.stroke.is_some() + || args.rotation.is_some() + || args.flip_horizontal.is_some() + || args.flip_vertical.is_some() + || has_position_transform + || has_image_fields + { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: + "text elements support only `position`, `z_order`, `fill`, and `text_layout` updates" + .to_string(), + }); + } if let Some(position) = args.position { text.frame = apply_partial_position(text.frame, position); } @@ -2823,32 +2892,23 @@ impl PresentationArtifactManager { if has_text_layout { text.rich_text.layout = text_layout; } - if args.stroke.is_some() - || args.rotation.is_some() - || args.flip_horizontal.is_some() - || args.flip_vertical.is_some() - { + } + PresentationElement::Shape(shape) => { + if has_image_fields { return Err(PresentationArtifactError::UnsupportedFeature { action: request.action, message: - "text elements support only `position`, `z_order`, and `fill` updates" + "shape elements support only `position`, `fill`, `stroke`, `rotation`, `flip_horizontal`, `flip_vertical`, `z_order`, and `text_layout` updates" .to_string(), }); } - } - PresentationElement::Shape(shape) => { - let position_rotation = args - .position - .as_ref() - .and_then(|position| position.rotation); - let position_flip_horizontal = args - .position - .as_ref() - .and_then(|position| position.flip_horizontal); - let position_flip_vertical = args - .position - .as_ref() - .and_then(|position| position.flip_vertical); + if has_text_layout && shape.text.is_none() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: "shape elements without text do not support `text_layout` updates" + .to_string(), + }); + } if let Some(position) = args.position { shape.frame = apply_partial_position(shape.frame, position); } @@ -2879,9 +2939,9 @@ impl PresentationArtifactManager { || args.rotation.is_some() || args.flip_horizontal.is_some() || args.flip_vertical.is_some() - || args.fit.is_some() - || args.crop.is_some() - || args.lock_aspect_ratio.is_some() + || has_position_transform + || has_image_fields + || has_text_layout { return Err(PresentationArtifactError::UnsupportedFeature { action: request.action, @@ -2914,19 +2974,7 @@ impl PresentationArtifactManager { } } PresentationElement::Image(image) => { - let position_rotation = args - .position - .as_ref() - .and_then(|position| position.rotation); - let position_flip_horizontal = args - .position - .as_ref() - .and_then(|position| position.flip_horizontal); - let position_flip_vertical = args - .position - .as_ref() - .and_then(|position| position.flip_vertical); - if args.fill.is_some() || args.stroke.is_some() { + if args.fill.is_some() || args.stroke.is_some() || has_text_layout { return Err(PresentationArtifactError::UnsupportedFeature { action: request.action, message: @@ -2965,6 +3013,9 @@ impl PresentationArtifactManager { || args.rotation.is_some() || args.flip_horizontal.is_some() || args.flip_vertical.is_some() + || has_position_transform + || has_image_fields + || has_text_layout { return Err(PresentationArtifactError::UnsupportedFeature { action: request.action, @@ -2982,6 +3033,9 @@ impl PresentationArtifactManager { || args.rotation.is_some() || args.flip_horizontal.is_some() || args.flip_vertical.is_some() + || has_position_transform + || has_image_fields + || has_text_layout { return Err(PresentationArtifactError::UnsupportedFeature { action: request.action, diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs index f5cfc41afa..c2fc143fec 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/model.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/model.rs @@ -132,6 +132,44 @@ impl HyperlinkTarget { fn is_external(&self) -> bool { matches!(self, Self::Url(_) | Self::Email { .. } | Self::File(_)) } + + fn adjust_for_insert(&mut self, inserted_index: usize) { + if let Self::Slide(slide_index) = self + && *slide_index as usize >= inserted_index + { + *slide_index += 1; + } + } + + fn adjust_for_move(&mut self, from_index: usize, to_index: usize) { + let Self::Slide(slide_index_ref) = self else { + return; + }; + let slide_index = *slide_index_ref as usize; + *slide_index_ref = if slide_index == from_index { + to_index as u32 + } else if from_index < to_index && (from_index + 1..=to_index).contains(&slide_index) { + (slide_index - 1) as u32 + } else if to_index < from_index && (to_index..from_index).contains(&slide_index) { + (slide_index + 1) as u32 + } else { + *slide_index_ref + }; + } + + fn adjust_for_delete(&mut self, deleted_index: usize) -> bool { + let Self::Slide(slide_index_ref) = self else { + return false; + }; + let slide_index = *slide_index_ref as usize; + if slide_index == deleted_index { + return true; + } + if slide_index > deleted_index { + *slide_index_ref -= 1; + } + false + } } impl HyperlinkState { @@ -217,6 +255,18 @@ impl HyperlinkState { ppt_rs::escape_xml(&self.target.relationship_target()), ) } + + fn adjust_for_insert(&mut self, inserted_index: usize) { + self.target.adjust_for_insert(inserted_index); + } + + fn adjust_for_move(&mut self, from_index: usize, to_index: usize) { + self.target.adjust_for_move(from_index, to_index); + } + + fn adjust_for_delete(&mut self, deleted_index: usize) -> bool { + self.target.adjust_for_delete(deleted_index) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -714,6 +764,7 @@ impl PresentationDocument { } Some(_) => {} } + self.adjust_hyperlinks_for_insert(inserted_index); } fn adjust_active_slide_for_move(&mut self, from_index: usize, to_index: usize) { @@ -728,6 +779,7 @@ impl PresentationDocument { active_index }); } + self.adjust_hyperlinks_for_move(from_index, to_index); } fn adjust_active_slide_for_delete(&mut self, deleted_index: usize) { @@ -740,6 +792,36 @@ impl PresentationDocument { Some(active_index) if deleted_index < active_index => Some(active_index - 1), Some(active_index) => Some(active_index), }; + self.adjust_hyperlinks_for_delete(deleted_index); + } + + fn adjust_hyperlinks_for_insert(&mut self, inserted_index: usize) { + self.visit_hyperlinks_mut(|hyperlink| { + hyperlink.adjust_for_insert(inserted_index); + true + }); + } + + fn adjust_hyperlinks_for_move(&mut self, from_index: usize, to_index: usize) { + self.visit_hyperlinks_mut(|hyperlink| { + hyperlink.adjust_for_move(from_index, to_index); + true + }); + } + + fn adjust_hyperlinks_for_delete(&mut self, deleted_index: usize) { + self.visit_hyperlinks_mut(|hyperlink| !hyperlink.adjust_for_delete(deleted_index)); + } + + fn visit_hyperlinks_mut(&mut self, mut visit: F) + where + F: FnMut(&mut HyperlinkState) -> bool, + { + for slide in &mut self.slides { + for element in &mut slide.elements { + element.visit_hyperlinks_mut(&mut visit); + } + } } fn next_layout_id(&mut self) -> String { @@ -1053,6 +1135,38 @@ fn import_pptx_images( Ok(()) } +fn import_pptx_slide_size(path: &Path) -> Result, PresentationArtifactError> { + let file = std::fs::File::open(path).map_err(|error| PresentationArtifactError::ImportFailed { + path: path.to_path_buf(), + message: error.to_string(), + })?; + let mut archive = + ZipArchive::new(file).map_err(|error| PresentationArtifactError::ImportFailed { + path: path.to_path_buf(), + message: error.to_string(), + })?; + let Some(xml) = zip_entry_string_if_exists(&mut archive, "ppt/presentation.xml").map_err( + |message| PresentationArtifactError::ImportFailed { + path: path.to_path_buf(), + message, + }, + )? + else { + return Ok(None); + }; + let Some(width) = + xml_tag_attribute(&xml, "().ok()) + else { + return Ok(None); + }; + let Some(height) = + xml_tag_attribute(&xml, "().ok()) + else { + return Ok(None); + }; + Ok(Some(Rect::from_emu(0, 0, width, height))) +} + fn zip_entry_string_if_exists( archive: &mut ZipArchive, path: &str, @@ -1479,6 +1593,45 @@ impl PresentationElement { Self::Chart(element) => element.z_order = z_order, } } + + fn visit_hyperlinks_mut(&mut self, visit: &mut F) + where + F: FnMut(&mut HyperlinkState) -> bool, + { + match self { + Self::Text(element) => { + if let Some(hyperlink) = element.hyperlink.as_mut() + && !visit(hyperlink) + { + element.hyperlink = None; + } + for range in &mut element.rich_text.ranges { + if let Some(hyperlink) = range.hyperlink.as_mut() + && !visit(hyperlink) + { + range.hyperlink = None; + } + } + } + Self::Shape(element) => { + if let Some(hyperlink) = element.hyperlink.as_mut() + && !visit(hyperlink) + { + element.hyperlink = None; + } + if let Some(rich_text) = element.rich_text.as_mut() { + for range in &mut rich_text.ranges { + if let Some(hyperlink) = range.hyperlink.as_mut() + && !visit(hyperlink) + { + range.hyperlink = None; + } + } + } + } + Self::Connector(_) | Self::Image(_) | Self::Table(_) | Self::Chart(_) => {} + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index e297e03ac6..6d376e4b8d 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -1092,8 +1092,7 @@ fn export_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( - // codespell:ignore lIns,rIns,tIns,bIns - r#"{auto_fit}{paragraphs}"# + r#"{auto_fit}{paragraphs}"# // codespell:ignore lIns,rIns,tIns,bIns ) } @@ -1145,8 +1144,7 @@ fn export_table_cell_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( - // codespell:ignore lIns,rIns,tIns,bIns - r#" lIns="{left_inset}" rIns="{right_inset}" tIns="{top_inset}" bIns="{bottom_inset}""#, + r#" lIns="{left_inset}" rIns="{right_inset}" tIns="{top_inset}" bIns="{bottom_inset}""#, // codespell:ignore lIns,rIns,tIns,bIns ) }); let paragraphs = export_text_paragraphs_xml( diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs index d4f1e10101..01fab77dee 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/render.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/render.rs @@ -153,7 +153,7 @@ fn render_slide_image( } let mut ordered = slide.elements.iter().collect::>(); - ordered.sort_by_key(|element| (preview_render_layer(element), element.z_order())); + ordered.sort_by_key(|element| element.z_order()); for element in ordered { render_element(&mut canvas, document, slide, element, render_scale)?; } @@ -682,38 +682,41 @@ fn render_bar_chart( height: u32, scale: f32, ) { - let baseline = top + height; - draw_chart_axes(image, left, top, width, height); + let (min_value, max_value) = chart_value_domain(chart); + let baseline = chart_axis_y(top, height, min_value, max_value, 0.0); + draw_chart_axes(image, left, top, width, height, baseline); let series_count = chart.series.len().max(1) as u32; let category_count = chart.categories.len().max(1) as u32; - let max_value = chart - .series - .iter() - .flat_map(|series| series.values.iter().copied()) - .fold(0.0f64, f64::max) - .max(1.0); let group_width = width as f32 / category_count as f32; let bar_width = (group_width / series_count as f32 * 0.72).max(2.0); for (series_index, series) in chart.series.iter().enumerate() { let color = chart_series_color(series, series_index); for (value_index, value) in series.values.iter().enumerate() { - let bar_height = ((*value / max_value) as f32 * height as f32).round().max(0.0) as u32; let x = left as f32 + group_width * value_index as f32 + (group_width - bar_width * series_count as f32) / 2.0 + bar_width * series_index as f32; - let y = baseline.saturating_sub(bar_height); + let value_y = chart_axis_y(top, height, min_value, max_value, *value); + let y = value_y.min(baseline); + let bar_height = value_y.abs_diff(baseline).max(1); fill_rect( image, x.round() as u32, y, bar_width.round().max(1.0) as u32, - bar_height.max(1), + bar_height, color, ); } } - render_category_labels(image, &chart.categories, left, baseline + 4, width, scale); + render_category_labels( + image, + &chart.categories, + left, + top + height + 4, + width, + scale, + ); } fn render_line_chart( @@ -725,21 +728,16 @@ fn render_line_chart( height: u32, scale: f32, ) { - draw_chart_axes(image, left, top, width, height); - let baseline = top + height; + let (min_value, max_value) = chart_value_domain(chart); + let baseline = chart_axis_y(top, height, min_value, max_value, 0.0); + draw_chart_axes(image, left, top, width, height, baseline); let point_count = chart.categories.len().max(2); - let max_value = chart - .series - .iter() - .flat_map(|series| series.values.iter().copied()) - .fold(0.0f64, f64::max) - .max(1.0); for (series_index, series) in chart.series.iter().enumerate() { let color = chart_series_color(series, series_index); let mut points = Vec::new(); for (value_index, value) in series.values.iter().enumerate() { let x = left as f32 + width as f32 * value_index as f32 / (point_count - 1) as f32; - let y = baseline as f32 - ((*value / max_value) as f32 * height as f32); + let y = chart_axis_y(top, height, min_value, max_value, *value) as f32; points.push((x, y)); } if points.len() >= 2 @@ -770,7 +768,38 @@ fn render_line_chart( ); } } - render_category_labels(image, &chart.categories, left, baseline + 4, width, scale); + render_category_labels( + image, + &chart.categories, + left, + top + height + 4, + width, + scale, + ); +} + +fn chart_value_domain(chart: &ChartElement) -> (f64, f64) { + let mut min_value = 0.0f64; + let mut max_value = 0.0f64; + for value in chart + .series + .iter() + .flat_map(|series| series.values.iter().copied()) + { + min_value = min_value.min(value); + max_value = max_value.max(value); + } + if (max_value - min_value).abs() < f64::EPSILON { + max_value += 1.0; + } + (min_value, max_value) +} + +fn chart_axis_y(top: u32, height: u32, min_value: f64, max_value: f64, value: f64) -> u32 { + let span = (max_value - min_value).max(f64::EPSILON); + let normalized = ((value - min_value) / span).clamp(0.0, 1.0); + let bottom = top + height; + bottom.saturating_sub((normalized * height as f64).round() as u32) } fn render_pie_chart( @@ -1675,17 +1704,6 @@ fn parse_rgba(hex: &str, alpha: u8) -> Rgba { Rgba([0, 0, 0, alpha]) } -fn preview_render_layer(element: &PresentationElement) -> usize { - match element { - PresentationElement::Text(_) - | PresentationElement::Shape(_) - | PresentationElement::Connector(_) - | PresentationElement::Image(ImageElement { payload: None, .. }) => 0, - PresentationElement::Image(_) | PresentationElement::Chart(_) => 1, - PresentationElement::Table(_) => 2, - } -} - fn shape_path( geometry: ShapeGeometry, width: u32, @@ -2161,10 +2179,17 @@ fn draw_vertical_line(image: &mut RgbaImage, x: u32, color: Rgba, thickness: fill_rect(image, x.saturating_sub(thickness / 2), 0, thickness.max(1), image.height(), color); } -fn draw_chart_axes(image: &mut RgbaImage, left: u32, top: u32, width: u32, height: u32) { +fn draw_chart_axes( + image: &mut RgbaImage, + left: u32, + top: u32, + width: u32, + plot_height: u32, + baseline: u32, +) { let axis_color = parse_rgba(DEFAULT_TABLE_GRID_HEX, 255); - fill_rect(image, left, top, 1, height, axis_color); - fill_rect(image, left, top + height, width, 1, axis_color); + fill_rect(image, left, top, 1, plot_height, axis_color); + fill_rect(image, left, baseline, width, 1, axis_color); } fn render_category_labels( diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs b/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs index df3eb8af2c..eb85685078 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/snapshot.rs @@ -230,54 +230,12 @@ fn load_image_payload_from_uri( uri: &str, action: &str, ) -> Result { - let response = - reqwest::blocking::get(uri).map_err(|error| PresentationArtifactError::InvalidArgs { - action: action.to_string(), - message: format!("failed to fetch image `{uri}`: {error}"), - })?; - let status = response.status(); - if !status.is_success() { - return Err(PresentationArtifactError::InvalidArgs { - action: action.to_string(), - message: format!("failed to fetch image `{uri}`: HTTP {status}"), - }); - } - let content_type = response - .headers() - .get(reqwest::header::CONTENT_TYPE) - .and_then(|value| value.to_str().ok()) - .map(|value| value.split(';').next().unwrap_or(value).trim().to_string()); - let bytes = response - .bytes() - .map_err(|error| PresentationArtifactError::InvalidArgs { - action: action.to_string(), - message: format!("failed to read image `{uri}`: {error}"), - })?; - build_image_payload( - bytes.to_vec(), - infer_remote_image_filename(uri, content_type.as_deref()), - action, - ) -} - -fn infer_remote_image_filename(uri: &str, content_type: Option<&str>) -> String { - let path_name = reqwest::Url::parse(uri) - .ok() - .and_then(|url| { - url.path_segments() - .and_then(Iterator::last) - .map(str::to_owned) - }) - .filter(|segment| !segment.is_empty()); - match (path_name, content_type) { - (Some(path_name), _) if Path::new(&path_name).extension().is_some() => path_name, - (Some(path_name), Some(content_type)) => { - format!("{path_name}.{}", image_extension_from_mime(content_type)) - } - (Some(path_name), None) => path_name, - (None, Some(content_type)) => format!("image.{}", image_extension_from_mime(content_type)), - (None, None) => "image.png".to_string(), - } + Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!( + "remote image URIs are not supported for `{action}`; download the image locally or provide `data_url`/`blob` instead (`{uri}`)" + ), + }) } fn build_image_payload( diff --git a/codex-rs/artifact-presentation/src/tests.rs b/codex-rs/artifact-presentation/src/tests.rs index 17ad741e7d..4646ce5c5b 100644 --- a/codex-rs/artifact-presentation/src/tests.rs +++ b/codex-rs/artifact-presentation/src/tests.rs @@ -3,6 +3,7 @@ use base64::Engine; use image::GenericImageView; use pretty_assertions::assert_eq; use std::io::Read; +use std::io::Write; fn zip_entry_text( path: &std::path::Path, @@ -22,6 +23,63 @@ fn zip_entry_names(path: &std::path::Path) -> Result, Box Result<(), Box> { + let input = std::fs::File::open(source)?; + let mut archive = zip::ZipArchive::new(input)?; + let output = std::fs::File::create(target)?; + let mut writer = zip::ZipWriter::new(output); + for index in 0..archive.len() { + let mut entry = archive.by_index(index)?; + let name = entry.name().to_string(); + if name == "ppt/codex-document.json" { + continue; + } + let mut bytes = Vec::new(); + entry.read_to_end(&mut bytes)?; + if name == "ppt/presentation.xml" { + let xml = String::from_utf8(bytes)?; + bytes = replace_slide_size_in_presentation_xml(&xml, slide_width_emu, slide_height_emu) + .into_bytes(); + } + writer.start_file(name, zip::write::SimpleFileOptions::default())?; + writer.write_all(&bytes)?; + } + writer.finish()?; + Ok(()) +} + +fn replace_slide_size_in_presentation_xml(xml: &str, width_emu: u32, height_emu: u32) -> String { + let Some(tag_start) = xml.find("") else { + return xml.to_string(); + }; + let tag_end = tag_start + tag_end_offset; + let tag = &xml[tag_start..tag_end]; + let tag = replace_xml_attribute(tag, "cx", &width_emu.to_string()); + let tag = replace_xml_attribute(&tag, "cy", &height_emu.to_string()); + format!("{}{tag}/>{}", &xml[..tag_start], &xml[tag_end + 2..]) +} + +fn replace_xml_attribute(tag: &str, attribute: &str, value: &str) -> String { + let needle = format!(r#"{attribute}=""#); + let Some(start) = tag.find(&needle) else { + return tag.to_string(); + }; + let value_start = start + needle.len(); + let Some(value_end_offset) = tag[value_start..].find('"') else { + return tag.to_string(); + }; + let value_end = value_start + value_end_offset; + format!("{}{}{}", &tag[..value_start], value, &tag[value_end..]) +} + fn parse_ndjson_lines(ndjson: &str) -> Result, serde_json::Error> { ndjson .lines() @@ -52,6 +110,27 @@ fn rect_contains_pixel( false } +fn rect_contains_pixel_where( + image: &image::DynamicImage, + left: u32, + top: u32, + width: u32, + height: u32, + predicate: F, +) -> bool +where + F: Fn([u8; 4]) -> bool, +{ + for y in top..top.saturating_add(height) { + for x in left..left.saturating_add(width) { + if predicate(image.get_pixel(x, y).0) { + return true; + } + } + } + false +} + #[test] fn manager_can_create_add_text_and_export() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -1022,29 +1101,10 @@ fn preview_image_writer_supports_jpeg_scale_and_svg() -> Result<(), Box Result<(), Box> { - let mut image_bytes = std::io::Cursor::new(Vec::new()); - image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel( - 16, - 8, - image::Rgba([0x11, 0x88, 0xCC, 0xFF]), - )) - .write_to(&mut image_bytes, image::ImageFormat::Png)?; - let png = image_bytes.into_inner(); - - let server = tiny_http::Server::http("127.0.0.1:0").expect("server"); - let port = server.server_addr().to_ip().expect("ip addr").port(); - let server_thread = std::thread::spawn(move || { - for request in server.incoming_requests().take(2) { - let response = tiny_http::Response::from_data(png.clone()).with_header( - tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"image/png"[..]) - .expect("header"), - ); - request.respond(response).expect("respond"); - } - }); - +fn image_uris_are_rejected_for_add_and_replace_images() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; + let image_path = temp_dir.path().join("local.png"); + image::RgbaImage::from_pixel(16, 8, image::Rgba([0x11, 0x88, 0xCC, 0xFF])).save(&image_path)?; let mut manager = PresentationArtifactManager::default(); let created = manager.execute( PresentationArtifactRequest { @@ -1063,14 +1123,32 @@ fn image_uris_can_add_and_replace_images() -> Result<(), Box Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let original_path = temp_dir.path().join("original.png"); + let replacement_path = temp_dir.path().join("replacement.png"); + image::RgbaImage::from_pixel(24, 12, image::Rgba([0x11, 0x88, 0xCC, 0xFF])) + .save(&original_path)?; + image::RgbaImage::from_pixel(32, 16, image::Rgba([0xD0, 0x44, 0x55, 0xFF])) + .save(&replacement_path)?; + let replacement_blob = + base64::engine::general_purpose::STANDARD.encode(std::fs::read(&replacement_path)?); + + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Replace image state" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "path": original_path, + "position": { "left": 24, "top": 24, "width": 200, "height": 120 }, + "fit": "contain", + "alt": "Existing alt text" + }), + }, + temp_dir.path(), + )?; + let image_id = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("image id"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("im/{image_id}"), + "fit": "cover", + "crop": { "left": 0.15, "top": 0.05, "right": 0.10, "bottom": 0.0 }, + "lock_aspect_ratio": true + }), + }, + temp_dir.path(), + )?; manager.execute( PresentationArtifactRequest { artifact_id: Some(artifact_id.clone()), action: "replace_image".to_string(), args: serde_json::json!({ - "element_id": format!("im/{element_id}"), - "uri": format!("http://127.0.0.1:{port}/updated.png"), - "fit": "contain" + "element_id": format!("im/{image_id}"), + "blob": replacement_blob }), }, temp_dir.path(), )?; - let inspect = manager.execute( + let resolved = manager.execute( PresentationArtifactRequest { artifact_id: Some(artifact_id), - action: "inspect".to_string(), - args: serde_json::json!({ "kind": "image" }), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("im/{image_id}") }), }, temp_dir.path(), )?; - assert!( - inspect - .inspect_ndjson - .expect("image inspect") - .contains("\"fit\":\"Contain\"") + let record = resolved.resolved_record.expect("resolved image"); + assert_eq!(record["fit"], "Cover"); + assert_eq!(record["alt"], "Existing alt text"); + assert_eq!(record["lockAspectRatio"], true); + assert_eq!(record["isPlaceholder"], false); + assert_eq!( + record + .get("crop") + .and_then(serde_json::Value::as_object) + .and_then(|crop| crop.get("left")) + .and_then(serde_json::Value::as_f64), + Some(0.15) ); - server_thread.join().expect("server thread"); Ok(()) } @@ -1591,6 +1758,141 @@ fn hyperlinks_are_inspectable_and_exported() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Slide hyperlinks" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + for slide_index in 0..3 { + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({ "notes": format!("slide {slide_index}") }), + }, + temp_dir.path(), + )?; + } + + let shape = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rectangle", + "position": { "left": 24, "top": 24, "width": 180, "height": 60 }, + "text": "Go" + }), + }, + temp_dir.path(), + )?; + let shape_id = shape + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("shape id"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "set_hyperlink".to_string(), + args: serde_json::json!({ + "element_id": format!("sh/{shape_id}"), + "link_type": "slide", + "slide_index": 2 + }), + }, + temp_dir.path(), + )?; + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "insert_slide".to_string(), + args: serde_json::json!({ "index": 0 }), + }, + temp_dir.path(), + )?; + let after_insert = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("sh/{shape_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + after_insert + .resolved_record + .as_ref() + .and_then(|record| record.get("hyperlink")) + .and_then(|hyperlink| hyperlink.get("slideIndex")) + .and_then(serde_json::Value::as_u64), + Some(3) + ); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "move_slide".to_string(), + args: serde_json::json!({ "from_index": 3, "to_index": 1 }), + }, + temp_dir.path(), + )?; + let after_move = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("sh/{shape_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + after_move + .resolved_record + .as_ref() + .and_then(|record| record.get("hyperlink")) + .and_then(|hyperlink| hyperlink.get("slideIndex")) + .and_then(serde_json::Value::as_u64), + Some(1) + ); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "delete_slide".to_string(), + args: serde_json::json!({ "slide_index": 1 }), + }, + temp_dir.path(), + )?; + let after_delete = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("sh/{shape_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + after_delete + .resolved_record + .as_ref() + .and_then(|record| record.get("hyperlink")), + None + ); + Ok(()) +} + #[test] fn manager_supports_layout_theme_notes_and_inspect() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -2666,6 +2968,74 @@ fn notes_visibility_controls_exported_notes() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Custom size", + "slide_size": { "width": 960, "height": 540 } + }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + + let codex_path = temp_dir.path().join("codex-sized.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": codex_path }), + }, + temp_dir.path(), + )?; + + let imported_source = temp_dir.path().join("non-codex-sized.pptx"); + rewrite_pptx_without_codex_metadata( + &temp_dir.path().join("codex-sized.pptx"), + &imported_source, + 12_192_000, + 6_858_000, + )?; + + let imported = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "import_pptx".to_string(), + args: serde_json::json!({ "path": imported_source }), + }, + temp_dir.path(), + )?; + let roundtrip_path = temp_dir.path().join("roundtrip-sized.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(imported.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": roundtrip_path }), + }, + temp_dir.path(), + )?; + let presentation_xml = zip_entry_text( + &temp_dir.path().join("roundtrip-sized.pptx"), + "ppt/presentation.xml", + )?; + assert!(presentation_xml.contains(r#" Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -3008,6 +3378,378 @@ fn connectors_support_arrows_and_inspect() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Atomic text style" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "Atomic", + "position": { "left": 40, "top": 40, "width": 180, "height": 60 } + }), + }, + temp_dir.path(), + )?; + let text_id = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("text id"); + let error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("sh/{text_id}"), + "fill": "#00AA00", + "stroke": { "color": "#222222", "width": 2 } + }), + }, + temp_dir.path(), + ) + .expect_err("unsupported stroke should reject the request"); + assert!(matches!( + error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + let resolved = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("sh/{text_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + resolved + .resolved_record + .as_ref() + .and_then(|record| record.get("fill")) + .cloned(), + None + ); + Ok(()) +} + +#[test] +fn update_shape_style_rejects_silently_ignored_text_shape_and_image_fields() +-> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Unsupported style args" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let text = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "Unsupported", + "position": { "left": 24, "top": 24, "width": 160, "height": 60 } + }), + }, + temp_dir.path(), + )?; + let text_id = text + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("text id"); + let added_shape = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rectangle", + "position": { "left": 24, "top": 120, "width": 160, "height": 80 }, + "fill": "#cccccc" + }), + }, + temp_dir.path(), + )?; + let shape_id = added_shape + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.get(1)) + .cloned() + .expect("shape id"); + let added_image = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 220, "top": 24, "width": 120, "height": 80 }, + "prompt": "Placeholder image" + }), + }, + temp_dir.path(), + )?; + let image_id = added_image + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.last()) + .cloned() + .expect("image id"); + + let text_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("sh/{text_id}"), + "position": { "flip_horizontal": true } + }), + }, + temp_dir.path(), + ) + .expect_err("text nested transforms should be rejected"); + assert!(matches!( + text_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + + let shape_fit_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("sh/{shape_id}"), + "fit": "contain" + }), + }, + temp_dir.path(), + ) + .expect_err("shape fit should be rejected"); + assert!(matches!( + shape_fit_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + + let shape_layout_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("sh/{shape_id}"), + "text_layout": { "wrap": "none" } + }), + }, + temp_dir.path(), + ) + .expect_err("shape text_layout without text should be rejected"); + assert!(matches!( + shape_layout_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + + let image_layout_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("im/{image_id}"), + "text_layout": { "wrap": "none" } + }), + }, + temp_dir.path(), + ) + .expect_err("image text_layout should be rejected"); + assert!(matches!( + image_layout_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + Ok(()) +} + +#[test] +fn update_shape_style_rejects_silently_ignored_connector_table_and_chart_fields() +-> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Unsupported style args 2" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let connector = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_connector".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "connector_type": "straight", + "start": { "left": 20, "top": 20 }, + "end": { "left": 180, "top": 160 } + }), + }, + temp_dir.path(), + )?; + let connector_id = connector + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("connector id"); + let table = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_table".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 220, "top": 20, "width": 180, "height": 100 }, + "rows": [["A", "B"], ["C", "D"]] + }), + }, + temp_dir.path(), + )?; + let table_id = table + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.get(1)) + .cloned() + .expect("table id"); + let chart = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_chart".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "chart_type": "bar", + "position": { "left": 24, "top": 220, "width": 220, "height": 140 }, + "categories": ["A", "B"], + "series": [{ "name": "Actual", "values": [1, 2] }] + }), + }, + temp_dir.path(), + )?; + let chart_id = chart + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.last()) + .cloned() + .expect("chart id"); + + let connector_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("cn/{connector_id}"), + "position": { "rotation": 10 } + }), + }, + temp_dir.path(), + ) + .expect_err("connector nested transforms should be rejected"); + assert!(matches!( + connector_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + + let table_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("tb/{table_id}"), + "position": { "flip_vertical": true } + }), + }, + temp_dir.path(), + ) + .expect_err("table nested transforms should be rejected"); + assert!(matches!( + table_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + + let chart_error = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("ch/{chart_id}"), + "text_layout": { "wrap": "none" } + }), + }, + temp_dir.path(), + ) + .expect_err("chart text_layout should be rejected"); + assert!(matches!( + chart_error, + PresentationArtifactError::UnsupportedFeature { .. } + )); + Ok(()) +} + #[test] fn shapes_support_stroke_dash_styles() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; @@ -3404,6 +4146,109 @@ fn manager_supports_table_cell_updates_and_merges() -> Result<(), Box Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Merge validation" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_table".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 32, "top": 48, "width": 240, "height": 120 }, + "rows": [ + ["A1", "B1"], + ["A2", "B2"] + ] + }), + }, + temp_dir.path(), + )?; + let table_id = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .map(|id| format!("tb/{id}")) + .expect("table id"); + + let reversed = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "merge_table_cells".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "start_row": 1, + "end_row": 0, + "start_column": 0, + "end_column": 1 + }), + }, + temp_dir.path(), + ) + .expect_err("reversed merge should be rejected"); + assert!(matches!( + reversed, + PresentationArtifactError::InvalidArgs { .. } + )); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "merge_table_cells".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "start_row": 0, + "end_row": 0, + "start_column": 0, + "end_column": 1 + }), + }, + temp_dir.path(), + )?; + + let overlapping = manager + .execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "merge_table_cells".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "start_row": 0, + "end_row": 1, + "start_column": 1, + "end_column": 1 + }), + }, + temp_dir.path(), + ) + .expect_err("overlapping merge should be rejected"); + assert!(matches!( + overlapping, + PresentationArtifactError::InvalidArgs { .. } + )); + Ok(()) +} + #[test] fn rich_text_comments_tables_and_charts_roundtrip_through_metadata() -> Result<(), Box> { @@ -4342,6 +5187,111 @@ fn render_preview_renders_chart_primitives() -> Result<(), Box Result<(), Box> +{ + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Negative charts" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + for _ in 0..2 { + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + } + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_chart".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "chart_type": "bar", + "position": { "left": 120, "top": 80, "width": 360, "height": 220 }, + "categories": ["Loss", "Gain"], + "series": [ + { "name": "Actual", "values": [-4, 6], "fill": "#4E79A7" } + ] + }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_chart".to_string(), + args: serde_json::json!({ + "slide_index": 1, + "chart_type": "line", + "position": { "left": 120, "top": 80, "width": 360, "height": 220 }, + "categories": ["Loss", "Gain"], + "series": [ + { "name": "Actual", "values": [-4, 6], "fill": "#4E79A7" } + ] + }), + }, + temp_dir.path(), + )?; + + let bar_preview = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "render_preview".to_string(), + args: serde_json::json!({ "slide_index": 0 }), + }, + temp_dir.path(), + )?; + let bar_image = load_preview(&bar_preview.rendered_preview.expect("bar preview").png_bytes)?; + assert!( + rect_contains_pixel(&bar_image, 150, 210, 90, 80, [0x4E, 0x79, 0xA7, 0xFF]), + "expected negative bar fill in the lower plot half" + ); + assert!( + rect_contains_pixel(&bar_image, 280, 110, 90, 80, [0x4E, 0x79, 0xA7, 0xFF]), + "expected positive bar fill in the upper plot half" + ); + + let line_preview = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "render_preview".to_string(), + args: serde_json::json!({ "slide_index": 1 }), + }, + temp_dir.path(), + )?; + let line_image = load_preview( + &line_preview + .rendered_preview + .expect("line preview") + .png_bytes, + )?; + let is_series_pixel = |pixel: [u8; 4]| { + pixel[3] == 0xFF + && u16::from(pixel[2]) > u16::from(pixel[0]) + 20 + && u16::from(pixel[2]) > u16::from(pixel[1]) + 20 + }; + assert!( + rect_contains_pixel_where(&line_image, 0, 220, 360, 140, is_series_pixel), + "expected negative line point inside the plot area" + ); + assert!( + rect_contains_pixel_where(&line_image, 240, 0, 480, 220, is_series_pixel), + "expected positive line point inside the plot area" + ); + Ok(()) +} + #[test] fn export_preview_uses_native_renderer_for_single_slide() -> Result<(), Box> { @@ -4479,12 +5429,109 @@ fn export_preview_matches_pptx_stacking_and_rounded_shapes() .expect("exported preview path present"), )?; - assert_eq!(preview.get_pixel(200, 220).0, [0x2A, 0x80, 0xD7, 0xFF]); + assert_eq!(preview.get_pixel(200, 220).0, [0xFF, 0xFF, 0xFF, 0xFF]); assert_eq!(preview.get_pixel(200, 290).0, [0xFF, 0xFF, 0xFF, 0xFF]); assert_eq!(preview.get_pixel(122, 202).0, [0xD9, 0xEA, 0xF7, 0xFF]); Ok(()) } +#[test] +fn render_preview_respects_cross_element_z_order() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let image_path = temp_dir.path().join("z-order-source.png"); + image::RgbaImage::from_pixel(40, 40, image::Rgba([0x2A, 0x80, 0xD7, 0xFF])) + .save(&image_path)?; + + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Cross element z-order" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added_image = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "path": image_path, + "position": { "left": 120, "top": 120, "width": 180, "height": 180 } + }), + }, + temp_dir.path(), + )?; + let image_id = added_image + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("image id"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rectangle", + "position": { "left": 140, "top": 140, "width": 180, "height": 180 }, + "fill": "#FFFFFF" + }), + }, + temp_dir.path(), + )?; + let preview = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "render_preview".to_string(), + args: serde_json::json!({ "slide_index": 0 }), + }, + temp_dir.path(), + )?; + let rendered = load_preview(&preview.rendered_preview.expect("preview").png_bytes)?; + assert_eq!(rendered.get_pixel(180, 180).0, [0xFF, 0xFF, 0xFF, 0xFF]); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": format!("im/{image_id}"), + "z_order": 1 + }), + }, + temp_dir.path(), + )?; + let updated_preview = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "render_preview".to_string(), + args: serde_json::json!({ "slide_index": 0 }), + }, + temp_dir.path(), + )?; + let updated = load_preview( + &updated_preview + .rendered_preview + .expect("updated preview") + .png_bytes, + )?; + assert_eq!(updated.get_pixel(180, 180).0, [0x2A, 0x80, 0xD7, 0xFF]); + Ok(()) +} + #[test] fn export_preview_writes_all_slides_with_stable_names() -> Result<(), Box> { let temp_dir = tempfile::tempdir()?; From 72d368e03aab573ff03785720da28e05337fd9c8 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 3 Mar 2026 12:48:16 -0800 Subject: [PATCH 14/17] fix (#13389) # External (non-OpenAI) Pull Request Requirements Before opening this Pull Request, please read the dedicated "Contributing" markdown file or your PR may be closed: https://github.com/openai/codex/blob/main/docs/contributing.md If your PR conforms to our contribution guidelines, replace this text with a detailed and high quality description of your changes. Include a link to a bug report or enhancement request. --- .../codex_app_server_protocol.v2.schemas.json | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 0f8e83d572..1bd5733364 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -2499,6 +2499,16 @@ } ] }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "tools": { "anyOf": [ { @@ -3839,6 +3849,16 @@ ], "description": "How to sandbox commands executed in the system" }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "session_id": { "$ref": "#/definitions/ThreadId" }, @@ -8218,6 +8238,16 @@ } ] }, + "service_tier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "web_search": { "anyOf": [ { @@ -10647,6 +10677,12 @@ "title": "ServerRequestResolvedNotification", "type": "object" }, + "ServiceTier": { + "enum": [ + "fast" + ], + "type": "string" + }, "SessionNetworkProxyRuntime": { "properties": { "admin_addr": { @@ -11489,6 +11525,23 @@ } ] }, + "serviceTier": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + }, "threadId": { "type": "string" } @@ -11527,6 +11580,16 @@ "sandbox": { "$ref": "#/definitions/SandboxPolicy" }, + "serviceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "thread": { "$ref": "#/definitions/Thread" } @@ -12472,6 +12535,23 @@ } ] }, + "serviceTier": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + }, "threadId": { "type": "string" } @@ -12510,6 +12590,16 @@ "sandbox": { "$ref": "#/definitions/SandboxPolicy" }, + "serviceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "thread": { "$ref": "#/definitions/Thread" } @@ -12688,6 +12778,23 @@ "string", "null" ] + }, + "serviceTier": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] } }, "title": "ThreadStartParams", @@ -12721,6 +12828,16 @@ "sandbox": { "$ref": "#/definitions/SandboxPolicy" }, + "serviceTier": { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, "thread": { "$ref": "#/definitions/Thread" } @@ -13526,6 +13643,24 @@ ], "description": "Override the sandbox policy for this turn and subsequent turns." }, + "serviceTier": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/ServiceTier" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ], + "description": "Override the service tier for this turn and subsequent turns." + }, "summary": { "anyOf": [ { From a99525eb00f660804d3dab3245d82e327be49f2e Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 20:50:11 +0000 Subject: [PATCH 15/17] fixes --- codex-rs/Cargo.lock | 2 -- codex-rs/artifact-presentation/Cargo.toml | 2 -- .../src/presentation_artifact/pptx.rs | 12 ++++++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index b98dc9da09..8d0b4947cc 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1556,13 +1556,11 @@ dependencies = [ "image", "ppt-rs", "pretty_assertions", - "reqwest 0.12.28", "serde", "serde_json", "tempfile", "thiserror 2.0.18", "tiny-skia", - "tiny_http", "uuid", "zip 2.4.2", ] diff --git a/codex-rs/artifact-presentation/Cargo.toml b/codex-rs/artifact-presentation/Cargo.toml index 786b8fb256..7c7bf6b10f 100644 --- a/codex-rs/artifact-presentation/Cargo.toml +++ b/codex-rs/artifact-presentation/Cargo.toml @@ -17,7 +17,6 @@ font8x8 = { workspace = true } fontdue = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } ppt-rs = { workspace = true } -reqwest = { workspace = true, features = ["blocking"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tiny-skia = { workspace = true } @@ -28,4 +27,3 @@ zip = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } tempfile = { workspace = true } -tiny_http = { workspace = true } diff --git a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs index 6d376e4b8d..6fa9ea98ab 100644 --- a/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs +++ b/codex-rs/artifact-presentation/src/presentation_artifact/pptx.rs @@ -1056,6 +1056,10 @@ fn export_text_body_xml( style: &TextStyle, rich_text: &RichTextState, ) -> String { + let left_inset_attr = concat!("l", "Ins"); + let right_inset_attr = concat!("r", "Ins"); + let top_inset_attr = concat!("t", "Ins"); + let bottom_inset_attr = concat!("b", "Ins"); let insets = rich_text.layout.insets.unwrap_or(TextInsets { left: 6, right: 6, @@ -1092,7 +1096,7 @@ fn export_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( - r#"{auto_fit}{paragraphs}"# // codespell:ignore lIns,rIns,tIns,bIns + r#"{auto_fit}{paragraphs}"# ) } @@ -1114,6 +1118,10 @@ fn export_table_cell_text_body_xml( element_id: &str, cell: &TableCellSpec, ) -> String { + let left_inset_attr = concat!("l", "Ins"); + let right_inset_attr = concat!("r", "Ins"); + let top_inset_attr = concat!("t", "Ins"); + let bottom_inset_attr = concat!("b", "Ins"); let wrap = match cell.rich_text.layout.wrap.unwrap_or(TextWrapMode::Square) { TextWrapMode::Square => "square", TextWrapMode::None => "none", @@ -1144,7 +1152,7 @@ fn export_table_cell_text_body_xml( let top_inset = points_to_emu(insets.top); let bottom_inset = points_to_emu(insets.bottom); format!( - r#" lIns="{left_inset}" rIns="{right_inset}" tIns="{top_inset}" bIns="{bottom_inset}""#, // codespell:ignore lIns,rIns,tIns,bIns + r#" {left_inset_attr}="{left_inset}" {right_inset_attr}="{right_inset}" {top_inset_attr}="{top_inset}" {bottom_inset_attr}="{bottom_inset}""#, ) }); let paragraphs = export_text_paragraphs_xml( From 167158f93ce96cc554ab0a75aaed97f743ecb1ed Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Tue, 3 Mar 2026 13:18:25 -0800 Subject: [PATCH 16/17] chore(app-server): delete v1 RPC methods and notifications (#13375) ## Summary This removes the old app-server v1 methods and notifications we no longer need, while keeping the small set the main codex app client still depends on for now. The remaining legacy surface is: - `initialize` - `getConversationSummary` - `getAuthStatus` - `gitDiffToRemote` - `fuzzyFileSearch` - `fuzzyFileSearch/sessionStart` - `fuzzyFileSearch/sessionUpdate` - `fuzzyFileSearch/sessionStop` And the raw `codex/event/*` notifications emitted from core. These notifications will be removed in a followup PR. ## What changed - removed deprecated v1 request variants from the protocol and app-server dispatcher - removed deprecated typed notifications: `authStatusChange`, `loginChatGptComplete`, and `sessionConfigured` - updated the app-server test client to use v2 flows instead of deleted v1 flows - deleted legacy-only app-server test suites and added focused coverage for `getConversationSummary` - regenerated app-server schema fixtures and updated the MCP interface docs to match the remaining compatibility surface ## Testing - `just write-app-server-schema` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server` --- codex-rs/Cargo.lock | 2 - .../AddConversationListenerParams.ts | 6 - .../AddConversationSubscriptionResponse.ts | 5 - .../schema/typescript/AgentMessageContent.ts | 5 - .../AgentMessageContentDeltaEvent.ts | 5 - .../typescript/AgentMessageDeltaEvent.ts | 5 - .../schema/typescript/AgentMessageEvent.ts | 6 - .../schema/typescript/AgentMessageItem.ts | 21 - .../typescript/AgentReasoningDeltaEvent.ts | 5 - .../schema/typescript/AgentReasoningEvent.ts | 5 - .../AgentReasoningRawContentDeltaEvent.ts | 5 - .../AgentReasoningRawContentEvent.ts | 5 - .../AgentReasoningSectionBreakEvent.ts | 5 - .../schema/typescript/AgentStatus.ts | 8 - .../ApplyPatchApprovalRequestEvent.ts | 23 - .../typescript/ArchiveConversationParams.ts | 6 - .../typescript/ArchiveConversationResponse.ts | 5 - .../schema/typescript/AskForApproval.ts | 10 - .../AuthStatusChangeNotification.ts | 9 - .../schema/typescript/BackgroundEventEvent.ts | 5 - .../schema/typescript/ByteRange.ts | 13 - .../schema/typescript/CallToolResult.ts | 9 - .../typescript/CancelLoginChatGptParams.ts | 5 - .../typescript/CancelLoginChatGptResponse.ts | 5 - .../schema/typescript/ClientRequest.ts | 16 +- .../schema/typescript/CodexErrorInfo.ts | 8 - .../CollabAgentInteractionBeginEvent.ts | 23 - .../CollabAgentInteractionEndEvent.ts | 36 - .../schema/typescript/CollabAgentRef.ts | 18 - .../typescript/CollabAgentSpawnBeginEvent.ts | 19 - .../typescript/CollabAgentSpawnEndEvent.ts | 36 - .../typescript/CollabAgentStatusEntry.ts | 23 - .../typescript/CollabCloseBeginEvent.ts | 18 - .../schema/typescript/CollabCloseEndEvent.ts | 32 - .../typescript/CollabResumeBeginEvent.ts | 26 - .../schema/typescript/CollabResumeEndEvent.ts | 32 - .../typescript/CollabWaitingBeginEvent.ts | 23 - .../typescript/CollabWaitingEndEvent.ts | 24 - .../typescript/ContextCompactedEvent.ts | 5 - .../typescript/ContextCompactionItem.ts | 5 - .../schema/typescript/CreditsSnapshot.ts | 5 - .../schema/typescript/CustomPrompt.ts | 5 - .../typescript/DeprecationNoticeEvent.ts | 13 - .../DynamicToolCallOutputContentItem.ts | 5 - .../typescript/DynamicToolCallRequest.ts | 6 - .../DynamicToolCallResponseEvent.ts | 39 - .../typescript/ElicitationRequestEvent.ts | 5 - .../schema/typescript/ErrorEvent.ts | 6 - .../schema/typescript/EventMsg.ts | 82 - .../typescript/ExecApprovalRequestEvent.ts | 62 - .../typescript/ExecCommandBeginEvent.ts | 35 - .../schema/typescript/ExecCommandEndEvent.ts | 64 - .../typescript/ExecCommandOutputDeltaEvent.ts | 18 - .../schema/typescript/ExecCommandSource.ts | 5 - .../schema/typescript/ExecCommandStatus.ts | 5 - .../typescript/ExecOneOffCommandParams.ts | 6 - .../typescript/ExecOneOffCommandResponse.ts | 5 - .../schema/typescript/ExecOutputStream.ts | 5 - .../typescript/ExitedReviewModeEvent.ts | 6 - .../typescript/FileSystemPermissions.ts | 6 - .../typescript/ForkConversationParams.ts | 7 - .../typescript/ForkConversationResponse.ts | 7 - .../GetHistoryEntryResponseEvent.ts | 10 - .../schema/typescript/GetUserAgentResponse.ts | 5 - .../typescript/GetUserSavedConfigResponse.ts | 6 - .../schema/typescript/HistoryEntry.ts | 5 - .../schema/typescript/InputItem.ts | 10 - .../typescript/InterruptConversationParams.ts | 6 - .../InterruptConversationResponse.ts | 6 - .../schema/typescript/ItemCompletedEvent.ts | 7 - .../schema/typescript/ItemStartedEvent.ts | 7 - .../typescript/ListConversationsParams.ts | 5 - .../typescript/ListConversationsResponse.ts | 6 - .../ListCustomPromptsResponseEvent.ts | 9 - .../ListRemoteSkillsResponseEvent.ts | 9 - .../typescript/ListSkillsResponseEvent.ts | 9 - .../schema/typescript/LoginApiKeyParams.ts | 5 - .../schema/typescript/LoginApiKeyResponse.ts | 5 - .../LoginChatGptCompleteNotification.ts | 8 - .../schema/typescript/LoginChatGptResponse.ts | 5 - .../typescript/LogoutChatGptResponse.ts | 5 - .../schema/typescript/MacOsPermissions.ts | 7 - .../schema/typescript/McpAuthStatus.ts | 5 - .../schema/typescript/McpInvocation.ts | 18 - .../typescript/McpListToolsResponseEvent.ts | 25 - .../typescript/McpStartupCompleteEvent.ts | 6 - .../schema/typescript/McpStartupFailure.ts | 5 - .../schema/typescript/McpStartupStatus.ts | 5 - .../typescript/McpStartupUpdateEvent.ts | 14 - .../typescript/McpToolCallBeginEvent.ts | 10 - .../schema/typescript/McpToolCallEndEvent.ts | 15 - .../schema/typescript/ModelRerouteEvent.ts | 6 - .../schema/typescript/ModelRerouteReason.ts | 5 - .../schema/typescript/NetworkAccess.ts | 8 - .../typescript/NetworkApprovalContext.ts | 6 - .../typescript/NetworkApprovalProtocol.ts | 5 - .../typescript/NewConversationParams.ts | 8 - .../typescript/NewConversationResponse.ts | 7 - .../schema/typescript/PatchApplyBeginEvent.ts | 23 - .../schema/typescript/PatchApplyEndEvent.ts | 36 - .../schema/typescript/PatchApplyStatus.ts | 5 - .../schema/typescript/PermissionProfile.ts | 7 - .../schema/typescript/PlanDeltaEvent.ts | 5 - .../schema/typescript/PlanItem.ts | 5 - .../schema/typescript/PlanItemArg.ts | 6 - .../schema/typescript/Profile.ts | 9 - .../schema/typescript/RateLimitSnapshot.ts | 8 - .../schema/typescript/RateLimitWindow.ts | 17 - .../schema/typescript/RawResponseItemEvent.ts | 6 - .../schema/typescript/ReadOnlyAccess.ts | 19 - .../schema/typescript/RealtimeAudioFrame.ts | 5 - .../RealtimeConversationClosedEvent.ts | 5 - .../RealtimeConversationRealtimeEvent.ts | 6 - .../RealtimeConversationStartedEvent.ts | 5 - .../schema/typescript/RealtimeEvent.ts | 8 - .../typescript/RealtimeHandoffMessage.ts | 5 - .../typescript/RealtimeHandoffRequested.ts | 6 - .../typescript/ReasoningContentDeltaEvent.ts | 5 - .../schema/typescript/ReasoningItem.ts | 5 - .../ReasoningRawContentDeltaEvent.ts | 5 - .../schema/typescript/RejectConfig.ts | 17 - .../typescript/RemoteSkillDownloadedEvent.ts | 8 - .../schema/typescript/RemoteSkillSummary.ts | 5 - .../RemoveConversationListenerParams.ts | 5 - .../RemoveConversationSubscriptionResponse.ts | 5 - .../typescript/RequestUserInputEvent.ts | 15 - .../typescript/RequestUserInputQuestion.ts | 6 - .../RequestUserInputQuestionOption.ts | 5 - .../typescript/ResumeConversationParams.ts | 8 - .../typescript/ResumeConversationResponse.ts | 7 - .../schema/typescript/ReviewCodeLocation.ts | 9 - .../schema/typescript/ReviewFinding.ts | 9 - .../schema/typescript/ReviewLineRange.ts | 8 - .../schema/typescript/ReviewOutputEvent.ts | 9 - .../schema/typescript/ReviewRequest.ts | 9 - .../schema/typescript/ReviewTarget.ts | 9 - .../schema/typescript/SandboxMode.ts | 5 - .../schema/typescript/SandboxPolicy.ts | 44 - .../schema/typescript/SandboxSettings.ts | 6 - .../typescript/SendUserMessageParams.ts | 7 - .../typescript/SendUserMessageResponse.ts | 5 - .../schema/typescript/SendUserTurnParams.ts | 17 - .../schema/typescript/SendUserTurnResponse.ts | 5 - .../schema/typescript/ServerNotification.ts | 5 +- .../typescript/SessionConfiguredEvent.ts | 58 - .../SessionConfiguredNotification.ts | 9 - .../typescript/SessionNetworkProxyRuntime.ts | 5 - .../typescript/SetDefaultModelParams.ts | 6 - .../typescript/SetDefaultModelResponse.ts | 5 - .../schema/typescript/SkillDependencies.ts | 6 - .../schema/typescript/SkillErrorInfo.ts | 5 - .../schema/typescript/SkillInterface.ts | 5 - .../schema/typescript/SkillMetadata.ts | 12 - .../schema/typescript/SkillScope.ts | 5 - .../schema/typescript/SkillToolDependency.ts | 5 - .../schema/typescript/SkillsListEntry.ts | 7 - .../schema/typescript/StepStatus.ts | 5 - .../schema/typescript/StreamErrorEvent.ts | 12 - .../typescript/TerminalInteractionEvent.ts | 17 - .../schema/typescript/TextElement.ts | 14 - .../typescript/ThreadNameUpdatedEvent.ts | 6 - .../typescript/ThreadRolledBackEvent.ts | 9 - .../schema/typescript/TokenCountEvent.ts | 7 - .../schema/typescript/TokenUsage.ts | 5 - .../schema/typescript/TokenUsageInfo.ts | 6 - .../schema/typescript/Tools.ts | 5 - .../schema/typescript/TurnAbortReason.ts | 5 - .../schema/typescript/TurnAbortedEvent.ts | 6 - .../schema/typescript/TurnCompleteEvent.ts | 5 - .../schema/typescript/TurnDiffEvent.ts | 5 - .../schema/typescript/TurnItem.ts | 11 - .../schema/typescript/TurnStartedEvent.ts | 6 - .../schema/typescript/UndoCompletedEvent.ts | 5 - .../schema/typescript/UndoStartedEvent.ts | 5 - .../schema/typescript/UpdatePlanArgs.ts | 10 - .../schema/typescript/UserInfoResponse.ts | 5 - .../schema/typescript/UserInput.ts | 16 - .../schema/typescript/UserMessageEvent.ts | 22 - .../schema/typescript/UserMessageItem.ts | 6 - .../schema/typescript/UserSavedConfig.ts | 14 - .../typescript/ViewImageToolCallEvent.ts | 13 - .../schema/typescript/WarningEvent.ts | 5 - .../schema/typescript/WebSearchBeginEvent.ts | 5 - .../schema/typescript/WebSearchEndEvent.ts | 6 - .../schema/typescript/WebSearchItem.ts | 6 - .../schema/typescript/index.ts | 182 --- codex-rs/app-server-protocol/src/export.rs | 33 +- .../src/protocol/common.rs | 118 +- .../app-server-protocol/src/protocol/v1.rs | 31 - codex-rs/app-server-test-client/src/lib.rs | 221 +-- codex-rs/app-server/Cargo.toml | 2 - .../app-server/src/codex_message_processor.rs | 1379 +---------------- codex-rs/app-server/src/outgoing_message.rs | 9 +- codex-rs/app-server/src/thread_state.rs | 117 -- .../app-server/tests/common/mcp_process.rs | 163 +- .../app-server/tests/suite/archive_thread.rs | 154 -- codex-rs/app-server/tests/suite/auth.rs | 16 +- .../suite/codex_message_processor_flow.rs | 550 ------- codex-rs/app-server/tests/suite/config.rs | 158 -- .../tests/suite/conversation_summary.rs | 113 ++ .../app-server/tests/suite/create_thread.rs | 142 -- .../app-server/tests/suite/fork_thread.rs | 140 -- codex-rs/app-server/tests/suite/interrupt.rs | 163 -- .../app-server/tests/suite/list_resume.rs | 440 ------ codex-rs/app-server/tests/suite/login.rs | 161 -- codex-rs/app-server/tests/suite/mod.rs | 14 +- .../app-server/tests/suite/output_schema.rs | 372 ----- .../app-server/tests/suite/send_message.rs | 645 -------- .../tests/suite/set_default_model.rs | 64 - codex-rs/app-server/tests/suite/user_agent.rs | 43 - codex-rs/app-server/tests/suite/user_info.rs | 46 - .../app-server/tests/suite/v2/rate_limits.rs | 13 +- .../tests/suite/v2/realtime_conversation.rs | 13 +- codex-rs/docs/codex_mcp_interface.md | 151 +- 214 files changed, 241 insertions(+), 7413 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ByteRange.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/EventMsg.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/InputItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Profile.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillScope.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/StepStatus.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TextElement.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/Tools.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UserInput.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts delete mode 100644 codex-rs/app-server/tests/suite/archive_thread.rs delete mode 100644 codex-rs/app-server/tests/suite/codex_message_processor_flow.rs delete mode 100644 codex-rs/app-server/tests/suite/config.rs create mode 100644 codex-rs/app-server/tests/suite/conversation_summary.rs delete mode 100644 codex-rs/app-server/tests/suite/create_thread.rs delete mode 100644 codex-rs/app-server/tests/suite/fork_thread.rs delete mode 100644 codex-rs/app-server/tests/suite/interrupt.rs delete mode 100644 codex-rs/app-server/tests/suite/list_resume.rs delete mode 100644 codex-rs/app-server/tests/suite/login.rs delete mode 100644 codex-rs/app-server/tests/suite/output_schema.rs delete mode 100644 codex-rs/app-server/tests/suite/send_message.rs delete mode 100644 codex-rs/app-server/tests/suite/set_default_model.rs delete mode 100644 codex-rs/app-server/tests/suite/user_agent.rs delete mode 100644 codex-rs/app-server/tests/suite/user_info.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 62d2ffae3b..d9ee0f608b 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1434,7 +1434,6 @@ dependencies = [ "codex-chatgpt", "codex-cloud-requirements", "codex-core", - "codex-execpolicy", "codex-feedback", "codex-file-search", "codex-login", @@ -1449,7 +1448,6 @@ dependencies = [ "codex-utils-json-to-toml", "core_test_support", "futures", - "os_info", "owo-colors", "pretty_assertions", "rmcp", diff --git a/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts b/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts deleted file mode 100644 index 6441bed68a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AddConversationListenerParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type AddConversationListenerParams = { conversationId: ThreadId, experimentalRawEvents: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts b/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts deleted file mode 100644 index f7e34ef658..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AddConversationSubscriptionResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AddConversationSubscriptionResponse = { subscriptionId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts deleted file mode 100644 index dc2cfb77e3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentMessageContent = { "type": "Text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts deleted file mode 100644 index 1473a4f2bc..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentMessageContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts deleted file mode 100644 index 1e12d85fbb..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentMessageDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts deleted file mode 100644 index b32680055f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { MessagePhase } from "./MessagePhase"; - -export type AgentMessageEvent = { message: string, phase: MessagePhase | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts deleted file mode 100644 index ee67a3e23b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentMessageContent } from "./AgentMessageContent"; -import type { MessagePhase } from "./MessagePhase"; - -/** - * Assistant-authored message payload used in turn-item streams. - * - * `phase` is optional because not all providers/models emit it. Consumers - * should use it when present, but retain legacy completion semantics when it - * is `None`. - */ -export type AgentMessageItem = { id: string, content: Array, -/** - * Optional phase metadata carried through from `ResponseItem::Message`. - * - * This is currently used by TUI rendering to distinguish mid-turn - * commentary from a final answer and avoid status-indicator jitter. - */ -phase?: MessagePhase, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts deleted file mode 100644 index fc2c221937..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentReasoningDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts deleted file mode 100644 index bf0062cd43..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentReasoningEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts deleted file mode 100644 index fcfa816f5d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentReasoningRawContentDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts deleted file mode 100644 index 364c278229..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentReasoningRawContentEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts deleted file mode 100644 index 604aceed93..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type AgentReasoningSectionBreakEvent = { item_id: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts deleted file mode 100644 index ddf6789c78..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Agent lifecycle status, derived from emitted events. - */ -export type AgentStatus = "pending_init" | "running" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts deleted file mode 100644 index 0c53cf50b8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FileChange } from "./FileChange"; - -export type ApplyPatchApprovalRequestEvent = { -/** - * Responses API call id for the associated patch apply call, if available. - */ -call_id: string, -/** - * Turn ID that this patch belongs to. - * Uses `#[serde(default)]` for backwards compatibility with older senders. - */ -turn_id: string, changes: { [key in string]?: FileChange }, -/** - * Optional explanatory reason (e.g. request for extra write access). - */ -reason: string | null, -/** - * When set, the agent is asking the user to allow writes under this root for the remainder of the session. - */ -grant_root: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts deleted file mode 100644 index 61fbcc9fc8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type ArchiveConversationParams = { conversationId: ThreadId, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts deleted file mode 100644 index 24900592b2..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ArchiveConversationResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ArchiveConversationResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts b/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts deleted file mode 100644 index 227eb44e77..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RejectConfig } from "./RejectConfig"; - -/** - * Determines the conditions under which the user is consulted to approve - * running the command proposed by Codex. - */ -export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "reject": RejectConfig } | "never"; diff --git a/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts b/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts deleted file mode 100644 index 17cb442fe0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AuthStatusChangeNotification.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AuthMode } from "./AuthMode"; - -/** - * Deprecated notification. Use AccountUpdatedNotification instead. - */ -export type AuthStatusChangeNotification = { authMethod: AuthMode | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts b/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts deleted file mode 100644 index 236b1dd888..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type BackgroundEventEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts b/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts deleted file mode 100644 index ab36a79acd..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts +++ /dev/null @@ -1,13 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ByteRange = { -/** - * Start byte offset (inclusive) within the UTF-8 text buffer. - */ -start: number, -/** - * End byte offset (exclusive) within the UTF-8 text buffer. - */ -end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts deleted file mode 100644 index e7a471d465..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -/** - * The server's response to a tool call. - */ -export type CallToolResult = { content: Array, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts deleted file mode 100644 index dae8e8c784..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type CancelLoginChatGptParams = { loginId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts deleted file mode 100644 index 004e6f8ea2..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CancelLoginChatGptResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type CancelLoginChatGptResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 9707644002..f6df83b6e4 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -1,26 +1,12 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AddConversationListenerParams } from "./AddConversationListenerParams"; -import type { ArchiveConversationParams } from "./ArchiveConversationParams"; -import type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; -import type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams"; -import type { ForkConversationParams } from "./ForkConversationParams"; import type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams"; import type { GetAuthStatusParams } from "./GetAuthStatusParams"; import type { GetConversationSummaryParams } from "./GetConversationSummaryParams"; import type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; import type { InitializeParams } from "./InitializeParams"; -import type { InterruptConversationParams } from "./InterruptConversationParams"; -import type { ListConversationsParams } from "./ListConversationsParams"; -import type { LoginApiKeyParams } from "./LoginApiKeyParams"; -import type { NewConversationParams } from "./NewConversationParams"; -import type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams"; import type { RequestId } from "./RequestId"; -import type { ResumeConversationParams } from "./ResumeConversationParams"; -import type { SendUserMessageParams } from "./SendUserMessageParams"; -import type { SendUserTurnParams } from "./SendUserTurnParams"; -import type { SetDefaultModelParams } from "./SetDefaultModelParams"; import type { AppsListParams } from "./v2/AppsListParams"; import type { CancelLoginAccountParams } from "./v2/CancelLoginAccountParams"; import type { CommandExecParams } from "./v2/CommandExecParams"; @@ -61,4 +47,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts deleted file mode 100644 index 522b91ce20..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Codex errors that we expose to clients. - */ -export type CodexErrorInfo = "context_window_exceeded" | "usage_limit_exceeded" | "server_overloaded" | { "http_connection_failed": { http_status_code: number | null, } } | { "response_stream_connection_failed": { http_status_code: number | null, } } | "internal_server_error" | "unauthorized" | "bad_request" | "sandbox_error" | { "response_stream_disconnected": { http_status_code: number | null, } } | { "response_too_many_failed_attempts": { http_status_code: number | null, } } | "thread_rollback_failed" | "other"; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts deleted file mode 100644 index 7109741999..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentInteractionBeginEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, -/** - * Prompt sent from the sender to the receiver. Can be empty to prevent CoT - * leaking at the beginning. - */ -prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts deleted file mode 100644 index 5458e06dce..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentInteractionEndEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, -/** - * Optional nickname assigned to the receiver agent. - */ -receiver_agent_nickname?: string | null, -/** - * Optional role assigned to the receiver agent. - */ -receiver_agent_role?: string | null, -/** - * Prompt sent from the sender to the receiver. Can be empty to prevent CoT - * leaking at the beginning. - */ -prompt: string, -/** - * Last known status of the receiver agent reported to the sender agent. - */ -status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts deleted file mode 100644 index cae7bf88b8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentRef = { -/** - * Thread ID of the receiver/new agent. - */ -thread_id: ThreadId, -/** - * Optional nickname assigned to an AgentControl-spawned sub-agent. - */ -agent_nickname?: string | null, -/** - * Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. - */ -agent_role?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts deleted file mode 100644 index a86598e20c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts +++ /dev/null @@ -1,19 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentSpawnBeginEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the - * beginning. - */ -prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts deleted file mode 100644 index 34753c8e08..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentSpawnEndEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the newly spawned agent, if it was created. - */ -new_thread_id: ThreadId | null, -/** - * Optional nickname assigned to the new agent. - */ -new_agent_nickname?: string | null, -/** - * Optional role assigned to the new agent. - */ -new_agent_role?: string | null, -/** - * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the - * beginning. - */ -prompt: string, -/** - * Last known status of the new agent reported to the sender agent. - */ -status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts deleted file mode 100644 index 286d19423d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { ThreadId } from "./ThreadId"; - -export type CollabAgentStatusEntry = { -/** - * Thread ID of the receiver/new agent. - */ -thread_id: ThreadId, -/** - * Optional nickname assigned to an AgentControl-spawned sub-agent. - */ -agent_nickname?: string | null, -/** - * Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. - */ -agent_role?: string | null, -/** - * Last known status of the agent. - */ -status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts deleted file mode 100644 index 355d59523a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type CollabCloseBeginEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts deleted file mode 100644 index 171886f1ef..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { ThreadId } from "./ThreadId"; - -export type CollabCloseEndEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, -/** - * Optional nickname assigned to the receiver agent. - */ -receiver_agent_nickname?: string | null, -/** - * Optional role assigned to the receiver agent. - */ -receiver_agent_role?: string | null, -/** - * Last known status of the receiver agent reported to the sender agent before - * the close. - */ -status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts deleted file mode 100644 index e6c1c3d5cd..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type CollabResumeBeginEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, -/** - * Optional nickname assigned to the receiver agent. - */ -receiver_agent_nickname?: string | null, -/** - * Optional role assigned to the receiver agent. - */ -receiver_agent_role?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts deleted file mode 100644 index caf970ec28..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { ThreadId } from "./ThreadId"; - -export type CollabResumeEndEvent = { -/** - * Identifier for the collab tool call. - */ -call_id: string, -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receiver. - */ -receiver_thread_id: ThreadId, -/** - * Optional nickname assigned to the receiver agent. - */ -receiver_agent_nickname?: string | null, -/** - * Optional role assigned to the receiver agent. - */ -receiver_agent_role?: string | null, -/** - * Last known status of the receiver agent reported to the sender agent after - * resume. - */ -status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts deleted file mode 100644 index f2f07f87ea..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CollabAgentRef } from "./CollabAgentRef"; -import type { ThreadId } from "./ThreadId"; - -export type CollabWaitingBeginEvent = { -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * Thread ID of the receivers. - */ -receiver_thread_ids: Array, -/** - * Optional nicknames/roles for receivers. - */ -receiver_agents?: Array, -/** - * ID of the waiting call. - */ -call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts deleted file mode 100644 index 929d59c611..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts +++ /dev/null @@ -1,24 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentStatus } from "./AgentStatus"; -import type { CollabAgentStatusEntry } from "./CollabAgentStatusEntry"; -import type { ThreadId } from "./ThreadId"; - -export type CollabWaitingEndEvent = { -/** - * Thread ID of the sender. - */ -sender_thread_id: ThreadId, -/** - * ID of the waiting call. - */ -call_id: string, -/** - * Optional receiver metadata paired with final statuses. - */ -agent_statuses?: Array, -/** - * Last known status of the receiver agents reported to the sender agent. - */ -statuses: { [key in ThreadId]?: AgentStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts deleted file mode 100644 index 538ca7a1bc..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ContextCompactedEvent = null; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts deleted file mode 100644 index dc3ab6388e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ContextCompactionItem = { id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts deleted file mode 100644 index 737bf99bef..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type CreditsSnapshot = { has_credits: boolean, unlimited: boolean, balance: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts b/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts deleted file mode 100644 index 96fe75e969..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type CustomPrompt = { name: string, path: string, content: string, description: string | null, argument_hint: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts deleted file mode 100644 index c1a7d81314..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type DeprecationNoticeEvent = { -/** - * Concise summary of what is deprecated. - */ -summary: string, -/** - * Optional extra guidance, such as migration steps or rationale. - */ -details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts deleted file mode 100644 index 8f432109d1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type DynamicToolCallOutputContentItem = { "type": "inputText", text: string, } | { "type": "inputImage", imageUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts deleted file mode 100644 index 94b0c65c66..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -export type DynamicToolCallRequest = { callId: string, turnId: string, tool: string, arguments: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts deleted file mode 100644 index 442c0ce6f3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts +++ /dev/null @@ -1,39 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; -import type { JsonValue } from "./serde_json/JsonValue"; - -export type DynamicToolCallResponseEvent = { -/** - * Identifier for the corresponding DynamicToolCallRequest. - */ -call_id: string, -/** - * Turn ID that this dynamic tool call belongs to. - */ -turn_id: string, -/** - * Dynamic tool name. - */ -tool: string, -/** - * Dynamic tool call arguments. - */ -arguments: JsonValue, -/** - * Dynamic tool response content items. - */ -content_items: Array, -/** - * Whether the tool call succeeded. - */ -success: boolean, -/** - * Optional error text when the tool call failed before producing a response. - */ -error: string | null, -/** - * The duration of the dynamic tool call. - */ -duration: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts deleted file mode 100644 index 045e304bdc..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts deleted file mode 100644 index fafde767e0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CodexErrorInfo } from "./CodexErrorInfo"; - -export type ErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts deleted file mode 100644 index 5ccbe1e697..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts +++ /dev/null @@ -1,82 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; -import type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; -import type { AgentMessageEvent } from "./AgentMessageEvent"; -import type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; -import type { AgentReasoningEvent } from "./AgentReasoningEvent"; -import type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; -import type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; -import type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; -import type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; -import type { BackgroundEventEvent } from "./BackgroundEventEvent"; -import type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; -import type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; -import type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; -import type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; -import type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; -import type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; -import type { CollabResumeBeginEvent } from "./CollabResumeBeginEvent"; -import type { CollabResumeEndEvent } from "./CollabResumeEndEvent"; -import type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; -import type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; -import type { ContextCompactedEvent } from "./ContextCompactedEvent"; -import type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; -import type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; -import type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent"; -import type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; -import type { ErrorEvent } from "./ErrorEvent"; -import type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; -import type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; -import type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; -import type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; -import type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; -import type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; -import type { ItemCompletedEvent } from "./ItemCompletedEvent"; -import type { ItemStartedEvent } from "./ItemStartedEvent"; -import type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; -import type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; -import type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; -import type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; -import type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; -import type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; -import type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; -import type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; -import type { ModelRerouteEvent } from "./ModelRerouteEvent"; -import type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; -import type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; -import type { PlanDeltaEvent } from "./PlanDeltaEvent"; -import type { RawResponseItemEvent } from "./RawResponseItemEvent"; -import type { RealtimeConversationClosedEvent } from "./RealtimeConversationClosedEvent"; -import type { RealtimeConversationRealtimeEvent } from "./RealtimeConversationRealtimeEvent"; -import type { RealtimeConversationStartedEvent } from "./RealtimeConversationStartedEvent"; -import type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; -import type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; -import type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; -import type { RequestUserInputEvent } from "./RequestUserInputEvent"; -import type { ReviewRequest } from "./ReviewRequest"; -import type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; -import type { StreamErrorEvent } from "./StreamErrorEvent"; -import type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; -import type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; -import type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; -import type { TokenCountEvent } from "./TokenCountEvent"; -import type { TurnAbortedEvent } from "./TurnAbortedEvent"; -import type { TurnCompleteEvent } from "./TurnCompleteEvent"; -import type { TurnDiffEvent } from "./TurnDiffEvent"; -import type { TurnStartedEvent } from "./TurnStartedEvent"; -import type { UndoCompletedEvent } from "./UndoCompletedEvent"; -import type { UndoStartedEvent } from "./UndoStartedEvent"; -import type { UpdatePlanArgs } from "./UpdatePlanArgs"; -import type { UserMessageEvent } from "./UserMessageEvent"; -import type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; -import type { WarningEvent } from "./WarningEvent"; -import type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; -import type { WebSearchEndEvent } from "./WebSearchEndEvent"; - -/** - * Response event from the agent - * NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen. - */ -export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "dynamic_tool_call_response" } & DynamicToolCallResponseEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts deleted file mode 100644 index cc13400900..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts +++ /dev/null @@ -1,62 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; -import type { NetworkApprovalContext } from "./NetworkApprovalContext"; -import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; -import type { ParsedCommand } from "./ParsedCommand"; -import type { PermissionProfile } from "./PermissionProfile"; -import type { ReviewDecision } from "./ReviewDecision"; - -export type ExecApprovalRequestEvent = { -/** - * Identifier for the associated command execution item. - */ -call_id: string, -/** - * Identifier for this specific approval callback. - * - * When absent, the approval is for the command item itself (`call_id`). - * This is present for subcommand approvals (via execve intercept). - */ -approval_id?: string, -/** - * Turn ID that this command belongs to. - * Uses `#[serde(default)]` for backwards compatibility. - */ -turn_id: string, -/** - * The command to be executed. - */ -command: Array, -/** - * The command's working directory. - */ -cwd: string, -/** - * Optional human-readable reason for the approval (e.g. retry without sandbox). - */ -reason: string | null, -/** - * Optional network context for a blocked request that can be approved. - */ -network_approval_context?: NetworkApprovalContext, -/** - * Proposed execpolicy amendment that can be applied to allow future runs. - */ -proposed_execpolicy_amendment?: ExecPolicyAmendment, -/** - * Proposed network policy amendments (for example allow/deny this host in future). - */ -proposed_network_policy_amendments?: Array, -/** - * Optional additional filesystem permissions requested for this command. - */ -additional_permissions?: PermissionProfile, -/** - * Ordered list of decisions the client may present for this prompt. - * - * When absent, clients should derive the legacy default set from the - * other fields on this request. - */ -available_decisions?: Array, parsed_cmd: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts deleted file mode 100644 index a9b4bc9393..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts +++ /dev/null @@ -1,35 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ExecCommandSource } from "./ExecCommandSource"; -import type { ParsedCommand } from "./ParsedCommand"; - -export type ExecCommandBeginEvent = { -/** - * Identifier so this can be paired with the ExecCommandEnd event. - */ -call_id: string, -/** - * Identifier for the underlying PTY process (when available). - */ -process_id?: string, -/** - * Turn ID that this command belongs to. - */ -turn_id: string, -/** - * The command to be executed. - */ -command: Array, -/** - * The command's working directory if not the default cwd for the agent. - */ -cwd: string, parsed_cmd: Array, -/** - * Where the command originated. Defaults to Agent for backward compatibility. - */ -source: ExecCommandSource, -/** - * Raw input sent to a unified exec session (if this is an interaction event). - */ -interaction_input?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts deleted file mode 100644 index 0bfc41ea81..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts +++ /dev/null @@ -1,64 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ExecCommandSource } from "./ExecCommandSource"; -import type { ExecCommandStatus } from "./ExecCommandStatus"; -import type { ParsedCommand } from "./ParsedCommand"; - -export type ExecCommandEndEvent = { -/** - * Identifier for the ExecCommandBegin that finished. - */ -call_id: string, -/** - * Identifier for the underlying PTY process (when available). - */ -process_id?: string, -/** - * Turn ID that this command belongs to. - */ -turn_id: string, -/** - * The command that was executed. - */ -command: Array, -/** - * The command's working directory if not the default cwd for the agent. - */ -cwd: string, parsed_cmd: Array, -/** - * Where the command originated. Defaults to Agent for backward compatibility. - */ -source: ExecCommandSource, -/** - * Raw input sent to a unified exec session (if this is an interaction event). - */ -interaction_input?: string, -/** - * Captured stdout - */ -stdout: string, -/** - * Captured stderr - */ -stderr: string, -/** - * Captured aggregated output - */ -aggregated_output: string, -/** - * The command's exit code. - */ -exit_code: number, -/** - * The duration of the command execution. - */ -duration: string, -/** - * Formatted output from the command, as seen by the model. - */ -formatted_output: string, -/** - * Completion status for this command execution. - */ -status: ExecCommandStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts deleted file mode 100644 index 0930bdd827..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ExecOutputStream } from "./ExecOutputStream"; - -export type ExecCommandOutputDeltaEvent = { -/** - * Identifier for the ExecCommandBegin that produced this chunk. - */ -call_id: string, -/** - * Which stream produced this chunk. - */ -stream: ExecOutputStream, -/** - * Raw bytes from the stream (may not be valid UTF-8). - */ -chunk: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts deleted file mode 100644 index b665441bc2..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ExecCommandSource = "agent" | "user_shell" | "unified_exec_startup" | "unified_exec_interaction"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts deleted file mode 100644 index d8d91fb19f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ExecCommandStatus = "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts deleted file mode 100644 index ca28ad775c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { SandboxPolicy } from "./SandboxPolicy"; - -export type ExecOneOffCommandParams = { command: Array, timeoutMs: bigint | null, cwd: string | null, sandboxPolicy: SandboxPolicy | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts deleted file mode 100644 index ff43ec4ca2..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecOneOffCommandResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ExecOneOffCommandResponse = { exitCode: number, stdout: string, stderr: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts deleted file mode 100644 index 96aa74483d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ExecOutputStream = "stdout" | "stderr"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts deleted file mode 100644 index 7271f07a3f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReviewOutputEvent } from "./ReviewOutputEvent"; - -export type ExitedReviewModeEvent = { review_output: ReviewOutputEvent | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts deleted file mode 100644 index aedf84de80..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "./AbsolutePathBuf"; - -export type FileSystemPermissions = { read: Array | null, write: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts deleted file mode 100644 index 4ca548fbff..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ForkConversationParams.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { NewConversationParams } from "./NewConversationParams"; -import type { ThreadId } from "./ThreadId"; - -export type ForkConversationParams = { path: string | null, conversationId: ThreadId | null, overrides: NewConversationParams | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts deleted file mode 100644 index 80d6e7947c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ForkConversationResponse.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { EventMsg } from "./EventMsg"; -import type { ThreadId } from "./ThreadId"; - -export type ForkConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts deleted file mode 100644 index d46019c1dc..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { HistoryEntry } from "./HistoryEntry"; - -export type GetHistoryEntryResponseEvent = { offset: number, log_id: bigint, -/** - * The entry at the requested offset, if available and parseable. - */ -entry: HistoryEntry | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts deleted file mode 100644 index a74aba5da6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/GetUserAgentResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type GetUserAgentResponse = { userAgent: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts b/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts deleted file mode 100644 index f8dcf2e67c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/GetUserSavedConfigResponse.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { UserSavedConfig } from "./UserSavedConfig"; - -export type GetUserSavedConfigResponse = { config: UserSavedConfig, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts b/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts deleted file mode 100644 index da5bc37c21..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type HistoryEntry = { conversation_id: string, ts: bigint, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InputItem.ts b/codex-rs/app-server-protocol/schema/typescript/InputItem.ts deleted file mode 100644 index 3ac72d31d8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/InputItem.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TextElement } from "./TextElement"; - -export type InputItem = { "type": "text", "data": { text: string, -/** - * UI-defined spans within `text` used to render or persist special elements. - */ -text_elements: Array, } } | { "type": "image", "data": { image_url: string, } } | { "type": "localImage", "data": { path: string, } }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts deleted file mode 100644 index 8db162c97c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type InterruptConversationParams = { conversationId: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts deleted file mode 100644 index 375604eef3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/InterruptConversationResponse.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TurnAbortReason } from "./TurnAbortReason"; - -export type InterruptConversationResponse = { abortReason: TurnAbortReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts deleted file mode 100644 index 97de348dff..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; -import type { TurnItem } from "./TurnItem"; - -export type ItemCompletedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts deleted file mode 100644 index e82f78f965..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; -import type { TurnItem } from "./TurnItem"; - -export type ItemStartedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts b/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts deleted file mode 100644 index 27c9f3172a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ListConversationsParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ListConversationsParams = { pageSize: number | null, cursor: string | null, modelProviders: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts deleted file mode 100644 index 0e26443a5f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ListConversationsResponse.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ConversationSummary } from "./ConversationSummary"; - -export type ListConversationsResponse = { items: Array, nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts deleted file mode 100644 index 9ebb43afb7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CustomPrompt } from "./CustomPrompt"; - -/** - * Response payload for `Op::ListCustomPrompts`. - */ -export type ListCustomPromptsResponseEvent = { custom_prompts: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts deleted file mode 100644 index e3b277f4d6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RemoteSkillSummary } from "./RemoteSkillSummary"; - -/** - * Response payload for `Op::ListRemoteSkills`. - */ -export type ListRemoteSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts deleted file mode 100644 index efdd547596..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { SkillsListEntry } from "./SkillsListEntry"; - -/** - * Response payload for `Op::ListSkills`. - */ -export type ListSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts deleted file mode 100644 index 3638553d3e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LoginApiKeyParams = { apiKey: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts deleted file mode 100644 index a67347aeb7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/LoginApiKeyResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LoginApiKeyResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts deleted file mode 100644 index 82c07bfa2d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptCompleteNotification.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Deprecated in favor of AccountLoginCompletedNotification. - */ -export type LoginChatGptCompleteNotification = { loginId: string, success: boolean, error: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts deleted file mode 100644 index 4147280117..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/LoginChatGptResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LoginChatGptResponse = { loginId: string, authUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts b/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts deleted file mode 100644 index ad5dbd9105..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/LogoutChatGptResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type LogoutChatGptResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts deleted file mode 100644 index 5c0792412c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { MacOsAutomationValue } from "./MacOsAutomationValue"; -import type { MacOsPreferencesValue } from "./MacOsPreferencesValue"; - -export type MacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts deleted file mode 100644 index 919ae85fd0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type McpAuthStatus = "unsupported" | "not_logged_in" | "bearer_token" | "o_auth"; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts b/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts deleted file mode 100644 index 5b7103a60c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts +++ /dev/null @@ -1,18 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "./serde_json/JsonValue"; - -export type McpInvocation = { -/** - * Name of the MCP server as defined in the config. - */ -server: string, -/** - * Name of the tool as given by the MCP server. - */ -tool: string, -/** - * Arguments to the tool call. - */ -arguments: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts deleted file mode 100644 index 945959431a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { McpAuthStatus } from "./McpAuthStatus"; -import type { Resource } from "./Resource"; -import type { ResourceTemplate } from "./ResourceTemplate"; -import type { Tool } from "./Tool"; - -export type McpListToolsResponseEvent = { -/** - * Fully qualified tool name -> tool definition. - */ -tools: { [key in string]?: Tool }, -/** - * Known resources grouped by server name. - */ -resources: { [key in string]?: Array }, -/** - * Known resource templates grouped by server name. - */ -resource_templates: { [key in string]?: Array }, -/** - * Authentication status for each configured MCP server. - */ -auth_statuses: { [key in string]?: McpAuthStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts deleted file mode 100644 index 67354adfbe..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { McpStartupFailure } from "./McpStartupFailure"; - -export type McpStartupCompleteEvent = { ready: Array, failed: Array, cancelled: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts deleted file mode 100644 index b12009b15b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type McpStartupFailure = { server: string, error: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts deleted file mode 100644 index 48c08226f4..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type McpStartupStatus = { "state": "starting" } | { "state": "ready" } | { "state": "failed", error: string, } | { "state": "cancelled" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts deleted file mode 100644 index ecfe7d551e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts +++ /dev/null @@ -1,14 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { McpStartupStatus } from "./McpStartupStatus"; - -export type McpStartupUpdateEvent = { -/** - * Server name being started. - */ -server: string, -/** - * Current startup status. - */ -status: McpStartupStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts deleted file mode 100644 index feb7ca7c21..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { McpInvocation } from "./McpInvocation"; - -export type McpToolCallBeginEvent = { -/** - * Identifier so this can be paired with the McpToolCallEnd event. - */ -call_id: string, invocation: McpInvocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts deleted file mode 100644 index 0ca82b2bc6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CallToolResult } from "./CallToolResult"; -import type { McpInvocation } from "./McpInvocation"; - -export type McpToolCallEndEvent = { -/** - * Identifier for the corresponding McpToolCallBegin that finished. - */ -call_id: string, invocation: McpInvocation, duration: string, -/** - * Result of the tool call. Note this could be an error. - */ -result: { Ok : CallToolResult } | { Err : string }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts deleted file mode 100644 index 23a4e1efb6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ModelRerouteReason } from "./ModelRerouteReason"; - -export type ModelRerouteEvent = { from_model: string, to_model: string, reason: ModelRerouteReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts deleted file mode 100644 index f5e1abf1e3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ModelRerouteReason = "high_risk_cyber_activity"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts deleted file mode 100644 index f259e67b99..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Represents whether outbound network access is available to the agent. - */ -export type NetworkAccess = "restricted" | "enabled"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts deleted file mode 100644 index b4b78e473c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol"; - -export type NetworkApprovalContext = { host: string, protocol: NetworkApprovalProtocol, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts deleted file mode 100644 index a33eab566f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type NetworkApprovalProtocol = "http" | "https" | "socks5_tcp" | "socks5_udp"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts deleted file mode 100644 index e1113c27e2..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/NewConversationParams.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AskForApproval } from "./AskForApproval"; -import type { SandboxMode } from "./SandboxMode"; -import type { JsonValue } from "./serde_json/JsonValue"; - -export type NewConversationParams = { model: string | null, modelProvider: string | null, profile: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, compactPrompt: string | null, includeApplyPatchTool: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts deleted file mode 100644 index 608c2ac110..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/NewConversationResponse.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { ThreadId } from "./ThreadId"; - -export type NewConversationResponse = { conversationId: ThreadId, model: string, reasoningEffort: ReasoningEffort | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts deleted file mode 100644 index 19ff0d5754..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FileChange } from "./FileChange"; - -export type PatchApplyBeginEvent = { -/** - * Identifier so this can be paired with the PatchApplyEnd event. - */ -call_id: string, -/** - * Turn ID that this patch belongs to. - * Uses `#[serde(default)]` for backwards compatibility. - */ -turn_id: string, -/** - * If true, there was no ApplyPatchApprovalRequest for this patch. - */ -auto_approved: boolean, -/** - * The changes to be applied. - */ -changes: { [key in string]?: FileChange }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts deleted file mode 100644 index 9dacb00e44..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FileChange } from "./FileChange"; -import type { PatchApplyStatus } from "./PatchApplyStatus"; - -export type PatchApplyEndEvent = { -/** - * Identifier for the PatchApplyBegin that finished. - */ -call_id: string, -/** - * Turn ID that this patch belongs to. - * Uses `#[serde(default)]` for backwards compatibility. - */ -turn_id: string, -/** - * Captured stdout (summary printed by apply_patch). - */ -stdout: string, -/** - * Captured stderr (parser errors, IO failures, etc.). - */ -stderr: string, -/** - * Whether the patch was applied successfully. - */ -success: boolean, -/** - * The changes that were applied (mirrors PatchApplyBeginEvent::changes). - */ -changes: { [key in string]?: FileChange }, -/** - * Completion status for this patch application. - */ -status: PatchApplyStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts deleted file mode 100644 index 721fcd9b1f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type PatchApplyStatus = "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts deleted file mode 100644 index bfc0de27b7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { FileSystemPermissions } from "./FileSystemPermissions"; -import type { MacOsPermissions } from "./MacOsPermissions"; - -export type PermissionProfile = { network: boolean | null, file_system: FileSystemPermissions | null, macos: MacOsPermissions | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts deleted file mode 100644 index f2ff588442..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type PlanDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts deleted file mode 100644 index 909ab40e64..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type PlanItem = { id: string, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts deleted file mode 100644 index a9c8acfa75..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { StepStatus } from "./StepStatus"; - -export type PlanItemArg = { step: string, status: StepStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Profile.ts b/codex-rs/app-server-protocol/schema/typescript/Profile.ts deleted file mode 100644 index 53d16e4a33..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Profile.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AskForApproval } from "./AskForApproval"; -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { ReasoningSummary } from "./ReasoningSummary"; -import type { Verbosity } from "./Verbosity"; - -export type Profile = { model: string | null, modelProvider: string | null, approvalPolicy: AskForApproval | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, chatgptBaseUrl: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts deleted file mode 100644 index 8604128b4e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CreditsSnapshot } from "./CreditsSnapshot"; -import type { PlanType } from "./PlanType"; -import type { RateLimitWindow } from "./RateLimitWindow"; - -export type RateLimitSnapshot = { limit_id: string | null, limit_name: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, plan_type: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts deleted file mode 100644 index 4a85062bf7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RateLimitWindow = { -/** - * Percentage (0-100) of the window that has been consumed. - */ -used_percent: number, -/** - * Rolling window duration, in minutes. - */ -window_minutes: number | null, -/** - * Unix timestamp (seconds since epoch) when the window resets. - */ -resets_at: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts deleted file mode 100644 index 62dd4f0018..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ResponseItem } from "./ResponseItem"; - -export type RawResponseItemEvent = { item: ResponseItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts b/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts deleted file mode 100644 index c01bdd37c6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts +++ /dev/null @@ -1,19 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "./AbsolutePathBuf"; - -/** - * Determines how read-only file access is granted inside a restricted - * sandbox. - */ -export type ReadOnlyAccess = { "type": "restricted", -/** - * Include built-in platform read roots required for basic process - * execution. - */ -include_platform_defaults: boolean, -/** - * Additional absolute roots that should be readable. - */ -readable_roots?: Array, } | { "type": "full-access" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts deleted file mode 100644 index 99c0c1063c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RealtimeAudioFrame = { data: string, sample_rate: number, num_channels: number, samples_per_channel: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts deleted file mode 100644 index c73e6833ae..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RealtimeConversationClosedEvent = { reason: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts deleted file mode 100644 index 4ff24a8281..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RealtimeEvent } from "./RealtimeEvent"; - -export type RealtimeConversationRealtimeEvent = { payload: RealtimeEvent, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts deleted file mode 100644 index f2894fcb1e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RealtimeConversationStartedEvent = { session_id: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts deleted file mode 100644 index 8286f34cad..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RealtimeAudioFrame } from "./RealtimeAudioFrame"; -import type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested"; -import type { JsonValue } from "./serde_json/JsonValue"; - -export type RealtimeEvent = { "SessionUpdated": { session_id: string, instructions: string | null, } } | { "AudioOut": RealtimeAudioFrame } | { "ConversationItemAdded": JsonValue } | { "ConversationItemDone": { item_id: string, } } | { "HandoffRequested": RealtimeHandoffRequested } | { "Error": string }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts deleted file mode 100644 index 39b77cfdf1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RealtimeHandoffMessage = { role: string, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts deleted file mode 100644 index f3426706aa..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage"; - -export type RealtimeHandoffRequested = { handoff_id: string, item_id: string, input_transcript: string, messages: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts deleted file mode 100644 index 70dfc01d24..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ReasoningContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts deleted file mode 100644 index 80bcb65fd1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ReasoningItem = { id: string, summary_text: Array, raw_content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts deleted file mode 100644 index ef3a792caf..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ReasoningRawContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, content_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts b/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts deleted file mode 100644 index 320f1096b0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RejectConfig = { -/** - * Reject approval prompts related to sandbox escalation. - */ -sandbox_approval: boolean, -/** - * Reject prompts triggered by execpolicy `prompt` rules. - */ -rules: boolean, -/** - * Reject MCP elicitation prompts. - */ -mcp_elicitations: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts deleted file mode 100644 index 83082f2a57..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Response payload for `Op::DownloadRemoteSkill`. - */ -export type RemoteSkillDownloadedEvent = { id: string, name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts deleted file mode 100644 index 7bf57b3b09..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RemoteSkillSummary = { id: string, name: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts deleted file mode 100644 index e9628b6341..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationListenerParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RemoveConversationListenerParams = { subscriptionId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts b/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts deleted file mode 100644 index 8053d7e4b4..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RemoveConversationSubscriptionResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RemoveConversationSubscriptionResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts deleted file mode 100644 index 8ea6453de9..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; - -export type RequestUserInputEvent = { -/** - * Responses API call id for the associated tool call, if available. - */ -call_id: string, -/** - * Turn ID that this request belongs to. - * Uses `#[serde(default)]` for backwards compatibility. - */ -turn_id: string, questions: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts deleted file mode 100644 index 2a68f7b4c8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; - -export type RequestUserInputQuestion = { id: string, header: string, question: string, isOther: boolean, isSecret: boolean, options: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts deleted file mode 100644 index b2d2a0db48..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type RequestUserInputQuestionOption = { label: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts deleted file mode 100644 index f2fe9d47c8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationParams.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { NewConversationParams } from "./NewConversationParams"; -import type { ResponseItem } from "./ResponseItem"; -import type { ThreadId } from "./ThreadId"; - -export type ResumeConversationParams = { path: string | null, conversationId: ThreadId | null, history: Array | null, overrides: NewConversationParams | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts b/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts deleted file mode 100644 index 1af5b68599..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ResumeConversationResponse.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { EventMsg } from "./EventMsg"; -import type { ThreadId } from "./ThreadId"; - -export type ResumeConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts deleted file mode 100644 index 752589fe55..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReviewLineRange } from "./ReviewLineRange"; - -/** - * Location of the code related to a review finding. - */ -export type ReviewCodeLocation = { absolute_file_path: string, line_range: ReviewLineRange, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts deleted file mode 100644 index e7c96bd170..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReviewCodeLocation } from "./ReviewCodeLocation"; - -/** - * A single review finding describing an observed issue or recommendation. - */ -export type ReviewFinding = { title: string, body: string, confidence_score: number, priority: number, code_location: ReviewCodeLocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts deleted file mode 100644 index c57ec6ed60..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Inclusive line range in a file associated with the finding. - */ -export type ReviewLineRange = { start: number, end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts deleted file mode 100644 index c45747424b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReviewFinding } from "./ReviewFinding"; - -/** - * Structured review result produced by a child review session. - */ -export type ReviewOutputEvent = { findings: Array, overall_correctness: string, overall_explanation: string, overall_confidence_score: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts deleted file mode 100644 index 1e9b8ad2ee..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReviewTarget } from "./ReviewTarget"; - -/** - * Review request sent to the review session. - */ -export type ReviewRequest = { target: ReviewTarget, user_facing_hint?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts deleted file mode 100644 index a79f1e993c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ReviewTarget = { "type": "uncommittedChanges" } | { "type": "baseBranch", branch: string, } | { "type": "commit", sha: string, -/** - * Optional human-readable label (e.g., commit subject) for UIs. - */ -title: string | null, } | { "type": "custom", instructions: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts deleted file mode 100644 index b8cf4326b9..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SandboxMode.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts deleted file mode 100644 index 743ad22229..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "./AbsolutePathBuf"; -import type { NetworkAccess } from "./NetworkAccess"; -import type { ReadOnlyAccess } from "./ReadOnlyAccess"; - -/** - * Determines execution restrictions for model shell commands. - */ -export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only", -/** - * Read access granted while running under this policy. - */ -access?: ReadOnlyAccess, } | { "type": "external-sandbox", -/** - * Whether the external sandbox permits outbound network traffic. - */ -network_access: NetworkAccess, } | { "type": "workspace-write", -/** - * Additional folders (beyond cwd and possibly TMPDIR) that should be - * writable from within the sandbox. - */ -writable_roots?: Array, -/** - * Read access granted while running under this policy. - */ -read_only_access?: ReadOnlyAccess, -/** - * When set to `true`, outbound network access is allowed. `false` by - * default. - */ -network_access: boolean, -/** - * When set to `true`, will NOT include the per-user `TMPDIR` - * environment variable among the default writable roots. Defaults to - * `false`. - */ -exclude_tmpdir_env_var: boolean, -/** - * When set to `true`, will NOT include the `/tmp` among the default - * writable roots on UNIX. Defaults to `false`. - */ -exclude_slash_tmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts deleted file mode 100644 index 94139b0e5d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SandboxSettings.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "./AbsolutePathBuf"; - -export type SandboxSettings = { writableRoots: Array, networkAccess: boolean | null, excludeTmpdirEnvVar: boolean | null, excludeSlashTmp: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts deleted file mode 100644 index 6aee538eb0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageParams.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { InputItem } from "./InputItem"; -import type { ThreadId } from "./ThreadId"; - -export type SendUserMessageParams = { conversationId: ThreadId, items: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts deleted file mode 100644 index 1a03e043a6..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SendUserMessageResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SendUserMessageResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts deleted file mode 100644 index 063191649a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnParams.ts +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AskForApproval } from "./AskForApproval"; -import type { InputItem } from "./InputItem"; -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { ReasoningSummary } from "./ReasoningSummary"; -import type { SandboxPolicy } from "./SandboxPolicy"; -import type { ServiceTier } from "./ServiceTier"; -import type { ThreadId } from "./ThreadId"; -import type { JsonValue } from "./serde_json/JsonValue"; - -export type SendUserTurnParams = { conversationId: ThreadId, items: Array, cwd: string, approvalPolicy: AskForApproval, sandboxPolicy: SandboxPolicy, model: string, serviceTier?: ServiceTier | null | null, effort: ReasoningEffort | null, summary: ReasoningSummary, -/** - * Optional JSON Schema used to constrain the final assistant message for this turn. - */ -outputSchema: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts deleted file mode 100644 index cffd0ac398..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SendUserTurnResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SendUserTurnResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts index 43345d9093..d7f70d3558 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts @@ -1,11 +1,8 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; import type { FuzzyFileSearchSessionCompletedNotification } from "./FuzzyFileSearchSessionCompletedNotification"; import type { FuzzyFileSearchSessionUpdatedNotification } from "./FuzzyFileSearchSessionUpdatedNotification"; -import type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification"; -import type { SessionConfiguredNotification } from "./SessionConfiguredNotification"; import type { AccountLoginCompletedNotification } from "./v2/AccountLoginCompletedNotification"; import type { AccountRateLimitsUpdatedNotification } from "./v2/AccountRateLimitsUpdatedNotification"; import type { AccountUpdatedNotification } from "./v2/AccountUpdatedNotification"; @@ -51,4 +48,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW /** * Notification sent from the server to the client. */ -export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification }; +export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts deleted file mode 100644 index b4696a0e4c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts +++ /dev/null @@ -1,58 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AskForApproval } from "./AskForApproval"; -import type { EventMsg } from "./EventMsg"; -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { SandboxPolicy } from "./SandboxPolicy"; -import type { ServiceTier } from "./ServiceTier"; -import type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime"; -import type { ThreadId } from "./ThreadId"; - -export type SessionConfiguredEvent = { session_id: ThreadId, forked_from_id: ThreadId | null, -/** - * Optional user-facing thread name (may be unset). - */ -thread_name?: string, -/** - * Tell the client what model is being queried. - */ -model: string, model_provider_id: string, service_tier: ServiceTier | null, -/** - * When to escalate for approval for execution - */ -approval_policy: AskForApproval, -/** - * How to sandbox commands executed in the system - */ -sandbox_policy: SandboxPolicy, -/** - * Working directory that should be treated as the *root* of the - * session. - */ -cwd: string, -/** - * The effort the model is putting into reasoning about the user's request. - */ -reasoning_effort: ReasoningEffort | null, -/** - * Identifier of the history log file (inode on Unix, 0 otherwise). - */ -history_log_id: bigint, -/** - * Current number of entries in the history log. - */ -history_entry_count: number, -/** - * Optional initial messages (as events) for resumed sessions. - * When present, UIs can use these to seed the history. - */ -initial_messages: Array | null, -/** - * Runtime proxy bind addresses, when the managed proxy was started for this session. - */ -network_proxy?: SessionNetworkProxyRuntime, -/** - * Path in which the rollout is stored. Can be `None` for ephemeral threads - */ -rollout_path: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts deleted file mode 100644 index 0fd5edcb3b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredNotification.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { EventMsg } from "./EventMsg"; -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { ServiceTier } from "./ServiceTier"; -import type { ThreadId } from "./ThreadId"; - -export type SessionConfiguredNotification = { sessionId: ThreadId, model: string, serviceTier: ServiceTier | null, reasoningEffort: ReasoningEffort | null, historyLogId: bigint, historyEntryCount: number, initialMessages: Array | null, rolloutPath: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts b/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts deleted file mode 100644 index 3f0c6d857e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, admin_addr: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts deleted file mode 100644 index b9e4e7d901..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ReasoningEffort } from "./ReasoningEffort"; - -export type SetDefaultModelParams = { model: string | null, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts b/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts deleted file mode 100644 index 1639601e0c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SetDefaultModelResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SetDefaultModelResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts b/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts deleted file mode 100644 index e2dd4f4241..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { SkillToolDependency } from "./SkillToolDependency"; - -export type SkillDependencies = { tools: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts deleted file mode 100644 index 6eaf035d8c..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SkillErrorInfo = { path: string, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts b/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts deleted file mode 100644 index 30250b9383..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SkillInterface = { display_name?: string, short_description?: string, icon_small?: string, icon_large?: string, brand_color?: string, default_prompt?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts deleted file mode 100644 index 088abc406a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts +++ /dev/null @@ -1,12 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { SkillDependencies } from "./SkillDependencies"; -import type { SkillInterface } from "./SkillInterface"; -import type { SkillScope } from "./SkillScope"; - -export type SkillMetadata = { name: string, description: string, -/** - * Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description. - */ -short_description?: string, interface?: SkillInterface, dependencies?: SkillDependencies, path: string, scope: SkillScope, enabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts b/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts deleted file mode 100644 index 997006f5b8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SkillScope = "user" | "repo" | "system" | "admin"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts b/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts deleted file mode 100644 index a5da45e178..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type SkillToolDependency = { type: string, value: string, description?: string, transport?: string, command?: string, url?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts b/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts deleted file mode 100644 index 3f46c98a4a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { SkillErrorInfo } from "./SkillErrorInfo"; -import type { SkillMetadata } from "./SkillMetadata"; - -export type SkillsListEntry = { cwd: string, skills: Array, errors: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts b/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts deleted file mode 100644 index 8494a76e0b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type StepStatus = "pending" | "in_progress" | "completed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts deleted file mode 100644 index b88993a344..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts +++ /dev/null @@ -1,12 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { CodexErrorInfo } from "./CodexErrorInfo"; - -export type StreamErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, -/** - * Optional details about the underlying stream failure (often the same - * human-readable message that is surfaced as the terminal error if retries - * are exhausted). - */ -additional_details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts deleted file mode 100644 index 5f300e6ca5..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TerminalInteractionEvent = { -/** - * Identifier for the ExecCommandBegin that produced this chunk. - */ -call_id: string, -/** - * Process id associated with the running command. - */ -process_id: string, -/** - * Stdin sent to the running session. - */ -stdin: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextElement.ts b/codex-rs/app-server-protocol/schema/typescript/TextElement.ts deleted file mode 100644 index 8841d00499..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TextElement.ts +++ /dev/null @@ -1,14 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ByteRange } from "./ByteRange"; - -export type TextElement = { -/** - * Byte range in the parent `text` buffer that this element occupies. - */ -byteRange: ByteRange, -/** - * Optional human-readable placeholder for the element, displayed in the UI. - */ -placeholder: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts deleted file mode 100644 index 639e29f9d7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ThreadId } from "./ThreadId"; - -export type ThreadNameUpdatedEvent = { thread_id: ThreadId, thread_name?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts deleted file mode 100644 index 30bc64c9c1..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts +++ /dev/null @@ -1,9 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ThreadRolledBackEvent = { -/** - * Number of user turns that were removed from context. - */ -num_turns: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts deleted file mode 100644 index f58b574641..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts +++ /dev/null @@ -1,7 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { RateLimitSnapshot } from "./RateLimitSnapshot"; -import type { TokenUsageInfo } from "./TokenUsageInfo"; - -export type TokenCountEvent = { info: TokenUsageInfo | null, rate_limits: RateLimitSnapshot | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts deleted file mode 100644 index 41186b25b9..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TokenUsage = { input_tokens: number, cached_input_tokens: number, output_tokens: number, reasoning_output_tokens: number, total_tokens: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts deleted file mode 100644 index cb15de42e7..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TokenUsage } from "./TokenUsage"; - -export type TokenUsageInfo = { total_token_usage: TokenUsage, last_token_usage: TokenUsage, model_context_window: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/Tools.ts b/codex-rs/app-server-protocol/schema/typescript/Tools.ts deleted file mode 100644 index 0387022966..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/Tools.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type Tools = { webSearch: boolean | null, viewImage: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts deleted file mode 100644 index f07cde6292..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TurnAbortReason = "interrupted" | "replaced" | "review_ended"; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts deleted file mode 100644 index 0b4e9075b3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TurnAbortReason } from "./TurnAbortReason"; - -export type TurnAbortedEvent = { turn_id: string | null, reason: TurnAbortReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts deleted file mode 100644 index 6987d59f98..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TurnCompleteEvent = { turn_id: string, last_agent_message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts deleted file mode 100644 index 52e3df09b0..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type TurnDiffEvent = { unified_diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts b/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts deleted file mode 100644 index 0f2ea12a21..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts +++ /dev/null @@ -1,11 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AgentMessageItem } from "./AgentMessageItem"; -import type { ContextCompactionItem } from "./ContextCompactionItem"; -import type { PlanItem } from "./PlanItem"; -import type { ReasoningItem } from "./ReasoningItem"; -import type { UserMessageItem } from "./UserMessageItem"; -import type { WebSearchItem } from "./WebSearchItem"; - -export type TurnItem = { "type": "UserMessage" } & UserMessageItem | { "type": "AgentMessage" } & AgentMessageItem | { "type": "Plan" } & PlanItem | { "type": "Reasoning" } & ReasoningItem | { "type": "WebSearch" } & WebSearchItem | { "type": "ContextCompaction" } & ContextCompactionItem; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts deleted file mode 100644 index 14c0d76707..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ModeKind } from "./ModeKind"; - -export type TurnStartedEvent = { turn_id: string, model_context_window: bigint | null, collaboration_mode_kind: ModeKind, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts deleted file mode 100644 index 2d94e2e18d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type UndoCompletedEvent = { success: boolean, message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts deleted file mode 100644 index 712082adff..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type UndoStartedEvent = { message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts b/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts deleted file mode 100644 index 61613fcb5f..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { PlanItemArg } from "./PlanItemArg"; - -export type UpdatePlanArgs = { -/** - * Arguments for the `update_plan` todo/checklist tool (not plan mode). - */ -explanation: string | null, plan: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts b/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts deleted file mode 100644 index 3d257a1c5e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UserInfoResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type UserInfoResponse = { allegedUserEmail: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/UserInput.ts deleted file mode 100644 index e6a9c3a580..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UserInput.ts +++ /dev/null @@ -1,16 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TextElement } from "./TextElement"; - -/** - * User input - */ -export type UserInput = { "type": "text", text: string, -/** - * UI-defined spans within `text` that should be treated as special elements. - * These are byte ranges into the UTF-8 `text` buffer and are used to render - * or persist rich input markers (e.g., image placeholders) across history - * and resume without mutating the literal text. - */ -text_elements: Array, } | { "type": "image", image_url: string, } | { "type": "local_image", path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts deleted file mode 100644 index 2fde364d67..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts +++ /dev/null @@ -1,22 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { TextElement } from "./TextElement"; - -export type UserMessageEvent = { message: string, -/** - * Image URLs sourced from `UserInput::Image`. These are safe - * to replay in legacy UI history events and correspond to images sent to - * the model. - */ -images: Array | null, -/** - * Local file paths sourced from `UserInput::LocalImage`. These are kept so - * the UI can reattach images when editing history, and should not be sent - * to the model or treated as API-ready URLs. - */ -local_images: Array, -/** - * UI-defined spans within `message` used to render or persist special elements. - */ -text_elements: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts deleted file mode 100644 index df856287a5..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { UserInput } from "./UserInput"; - -export type UserMessageItem = { id: string, content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts b/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts deleted file mode 100644 index e70107f31e..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/UserSavedConfig.ts +++ /dev/null @@ -1,14 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AskForApproval } from "./AskForApproval"; -import type { ForcedLoginMethod } from "./ForcedLoginMethod"; -import type { Profile } from "./Profile"; -import type { ReasoningEffort } from "./ReasoningEffort"; -import type { ReasoningSummary } from "./ReasoningSummary"; -import type { SandboxMode } from "./SandboxMode"; -import type { SandboxSettings } from "./SandboxSettings"; -import type { Tools } from "./Tools"; -import type { Verbosity } from "./Verbosity"; - -export type UserSavedConfig = { approvalPolicy: AskForApproval | null, sandboxMode: SandboxMode | null, sandboxSettings: SandboxSettings | null, forcedChatgptWorkspaceId: string | null, forcedLoginMethod: ForcedLoginMethod | null, model: string | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, tools: Tools | null, profile: string | null, profiles: { [key in string]?: Profile }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts deleted file mode 100644 index 76541a773a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ViewImageToolCallEvent = { -/** - * Identifier for the originating tool call. - */ -call_id: string, -/** - * Local filesystem path provided to the tool. - */ -path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts deleted file mode 100644 index 35ec40f7cd..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type WarningEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts deleted file mode 100644 index 4a8d881914..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type WebSearchBeginEvent = { call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts deleted file mode 100644 index 5b8b67c28b..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { WebSearchAction } from "./WebSearchAction"; - -export type WebSearchEndEvent = { call_id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts deleted file mode 100644 index 46b1406519..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { WebSearchAction } from "./WebSearchAction"; - -export type WebSearchItem = { id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 81e08b1596..fa1284843d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -1,81 +1,21 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! export type { AbsolutePathBuf } from "./AbsolutePathBuf"; -export type { AddConversationListenerParams } from "./AddConversationListenerParams"; -export type { AddConversationSubscriptionResponse } from "./AddConversationSubscriptionResponse"; -export type { AgentMessageContent } from "./AgentMessageContent"; -export type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; -export type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; -export type { AgentMessageEvent } from "./AgentMessageEvent"; -export type { AgentMessageItem } from "./AgentMessageItem"; -export type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; -export type { AgentReasoningEvent } from "./AgentReasoningEvent"; -export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; -export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; -export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; -export type { AgentStatus } from "./AgentStatus"; export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; -export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; -export type { ArchiveConversationParams } from "./ArchiveConversationParams"; -export type { ArchiveConversationResponse } from "./ArchiveConversationResponse"; -export type { AskForApproval } from "./AskForApproval"; export type { AuthMode } from "./AuthMode"; -export type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification"; -export type { BackgroundEventEvent } from "./BackgroundEventEvent"; -export type { ByteRange } from "./ByteRange"; -export type { CallToolResult } from "./CallToolResult"; -export type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams"; -export type { CancelLoginChatGptResponse } from "./CancelLoginChatGptResponse"; export type { ClientInfo } from "./ClientInfo"; export type { ClientNotification } from "./ClientNotification"; export type { ClientRequest } from "./ClientRequest"; -export type { CodexErrorInfo } from "./CodexErrorInfo"; -export type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; -export type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; -export type { CollabAgentRef } from "./CollabAgentRef"; -export type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; -export type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; -export type { CollabAgentStatusEntry } from "./CollabAgentStatusEntry"; -export type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; -export type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; -export type { CollabResumeBeginEvent } from "./CollabResumeBeginEvent"; -export type { CollabResumeEndEvent } from "./CollabResumeEndEvent"; -export type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; -export type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; export type { CollaborationMode } from "./CollaborationMode"; export type { ContentItem } from "./ContentItem"; -export type { ContextCompactedEvent } from "./ContextCompactedEvent"; -export type { ContextCompactionItem } from "./ContextCompactionItem"; export type { ConversationGitInfo } from "./ConversationGitInfo"; export type { ConversationSummary } from "./ConversationSummary"; -export type { CreditsSnapshot } from "./CreditsSnapshot"; -export type { CustomPrompt } from "./CustomPrompt"; -export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; -export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; -export type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; -export type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent"; -export type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; -export type { ErrorEvent } from "./ErrorEvent"; -export type { EventMsg } from "./EventMsg"; -export type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; export type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams"; export type { ExecCommandApprovalResponse } from "./ExecCommandApprovalResponse"; -export type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; -export type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; -export type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; -export type { ExecCommandSource } from "./ExecCommandSource"; -export type { ExecCommandStatus } from "./ExecCommandStatus"; -export type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams"; -export type { ExecOneOffCommandResponse } from "./ExecOneOffCommandResponse"; -export type { ExecOutputStream } from "./ExecOutputStream"; export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; -export type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; export type { FileChange } from "./FileChange"; -export type { FileSystemPermissions } from "./FileSystemPermissions"; export type { ForcedLoginMethod } from "./ForcedLoginMethod"; -export type { ForkConversationParams } from "./ForkConversationParams"; -export type { ForkConversationResponse } from "./ForkConversationResponse"; export type { FunctionCallOutputBody } from "./FunctionCallOutputBody"; export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload"; @@ -88,166 +28,44 @@ export type { GetAuthStatusParams } from "./GetAuthStatusParams"; export type { GetAuthStatusResponse } from "./GetAuthStatusResponse"; export type { GetConversationSummaryParams } from "./GetConversationSummaryParams"; export type { GetConversationSummaryResponse } from "./GetConversationSummaryResponse"; -export type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; -export type { GetUserAgentResponse } from "./GetUserAgentResponse"; -export type { GetUserSavedConfigResponse } from "./GetUserSavedConfigResponse"; export type { GhostCommit } from "./GhostCommit"; export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; export type { GitSha } from "./GitSha"; -export type { HistoryEntry } from "./HistoryEntry"; export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; -export type { InputItem } from "./InputItem"; export type { InputModality } from "./InputModality"; -export type { InterruptConversationParams } from "./InterruptConversationParams"; -export type { InterruptConversationResponse } from "./InterruptConversationResponse"; -export type { ItemCompletedEvent } from "./ItemCompletedEvent"; -export type { ItemStartedEvent } from "./ItemStartedEvent"; -export type { ListConversationsParams } from "./ListConversationsParams"; -export type { ListConversationsResponse } from "./ListConversationsResponse"; -export type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; -export type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; -export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; export type { LocalShellAction } from "./LocalShellAction"; export type { LocalShellExecAction } from "./LocalShellExecAction"; export type { LocalShellStatus } from "./LocalShellStatus"; -export type { LoginApiKeyParams } from "./LoginApiKeyParams"; -export type { LoginApiKeyResponse } from "./LoginApiKeyResponse"; -export type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification"; -export type { LoginChatGptResponse } from "./LoginChatGptResponse"; -export type { LogoutChatGptResponse } from "./LogoutChatGptResponse"; export type { MacOsAutomationValue } from "./MacOsAutomationValue"; -export type { MacOsPermissions } from "./MacOsPermissions"; export type { MacOsPreferencesValue } from "./MacOsPreferencesValue"; -export type { McpAuthStatus } from "./McpAuthStatus"; -export type { McpInvocation } from "./McpInvocation"; -export type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; -export type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; -export type { McpStartupFailure } from "./McpStartupFailure"; -export type { McpStartupStatus } from "./McpStartupStatus"; -export type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; -export type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; -export type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; export type { MessagePhase } from "./MessagePhase"; export type { ModeKind } from "./ModeKind"; -export type { ModelRerouteEvent } from "./ModelRerouteEvent"; -export type { ModelRerouteReason } from "./ModelRerouteReason"; -export type { NetworkAccess } from "./NetworkAccess"; -export type { NetworkApprovalContext } from "./NetworkApprovalContext"; -export type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol"; export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction"; -export type { NewConversationParams } from "./NewConversationParams"; -export type { NewConversationResponse } from "./NewConversationResponse"; export type { ParsedCommand } from "./ParsedCommand"; -export type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; -export type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; -export type { PatchApplyStatus } from "./PatchApplyStatus"; -export type { PermissionProfile } from "./PermissionProfile"; export type { Personality } from "./Personality"; -export type { PlanDeltaEvent } from "./PlanDeltaEvent"; -export type { PlanItem } from "./PlanItem"; -export type { PlanItemArg } from "./PlanItemArg"; export type { PlanType } from "./PlanType"; -export type { Profile } from "./Profile"; -export type { RateLimitSnapshot } from "./RateLimitSnapshot"; -export type { RateLimitWindow } from "./RateLimitWindow"; -export type { RawResponseItemEvent } from "./RawResponseItemEvent"; -export type { ReadOnlyAccess } from "./ReadOnlyAccess"; -export type { RealtimeAudioFrame } from "./RealtimeAudioFrame"; -export type { RealtimeConversationClosedEvent } from "./RealtimeConversationClosedEvent"; -export type { RealtimeConversationRealtimeEvent } from "./RealtimeConversationRealtimeEvent"; -export type { RealtimeConversationStartedEvent } from "./RealtimeConversationStartedEvent"; -export type { RealtimeEvent } from "./RealtimeEvent"; -export type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage"; -export type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested"; -export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; export type { ReasoningEffort } from "./ReasoningEffort"; -export type { ReasoningItem } from "./ReasoningItem"; export type { ReasoningItemContent } from "./ReasoningItemContent"; export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; -export type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; export type { ReasoningSummary } from "./ReasoningSummary"; -export type { RejectConfig } from "./RejectConfig"; -export type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; -export type { RemoteSkillSummary } from "./RemoteSkillSummary"; -export type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams"; -export type { RemoveConversationSubscriptionResponse } from "./RemoveConversationSubscriptionResponse"; export type { RequestId } from "./RequestId"; -export type { RequestUserInputEvent } from "./RequestUserInputEvent"; -export type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; -export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; export type { Resource } from "./Resource"; export type { ResourceTemplate } from "./ResourceTemplate"; export type { ResponseItem } from "./ResponseItem"; -export type { ResumeConversationParams } from "./ResumeConversationParams"; -export type { ResumeConversationResponse } from "./ResumeConversationResponse"; -export type { ReviewCodeLocation } from "./ReviewCodeLocation"; export type { ReviewDecision } from "./ReviewDecision"; -export type { ReviewFinding } from "./ReviewFinding"; -export type { ReviewLineRange } from "./ReviewLineRange"; -export type { ReviewOutputEvent } from "./ReviewOutputEvent"; -export type { ReviewRequest } from "./ReviewRequest"; -export type { ReviewTarget } from "./ReviewTarget"; -export type { SandboxMode } from "./SandboxMode"; -export type { SandboxPolicy } from "./SandboxPolicy"; -export type { SandboxSettings } from "./SandboxSettings"; -export type { SendUserMessageParams } from "./SendUserMessageParams"; -export type { SendUserMessageResponse } from "./SendUserMessageResponse"; -export type { SendUserTurnParams } from "./SendUserTurnParams"; -export type { SendUserTurnResponse } from "./SendUserTurnResponse"; export type { ServerNotification } from "./ServerNotification"; export type { ServerRequest } from "./ServerRequest"; export type { ServiceTier } from "./ServiceTier"; -export type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; -export type { SessionConfiguredNotification } from "./SessionConfiguredNotification"; -export type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime"; export type { SessionSource } from "./SessionSource"; -export type { SetDefaultModelParams } from "./SetDefaultModelParams"; -export type { SetDefaultModelResponse } from "./SetDefaultModelResponse"; export type { Settings } from "./Settings"; -export type { SkillDependencies } from "./SkillDependencies"; -export type { SkillErrorInfo } from "./SkillErrorInfo"; -export type { SkillInterface } from "./SkillInterface"; -export type { SkillMetadata } from "./SkillMetadata"; -export type { SkillScope } from "./SkillScope"; -export type { SkillToolDependency } from "./SkillToolDependency"; -export type { SkillsListEntry } from "./SkillsListEntry"; -export type { StepStatus } from "./StepStatus"; -export type { StreamErrorEvent } from "./StreamErrorEvent"; export type { SubAgentSource } from "./SubAgentSource"; -export type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; -export type { TextElement } from "./TextElement"; export type { ThreadId } from "./ThreadId"; -export type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; -export type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; -export type { TokenCountEvent } from "./TokenCountEvent"; -export type { TokenUsage } from "./TokenUsage"; -export type { TokenUsageInfo } from "./TokenUsageInfo"; export type { Tool } from "./Tool"; -export type { Tools } from "./Tools"; -export type { TurnAbortReason } from "./TurnAbortReason"; -export type { TurnAbortedEvent } from "./TurnAbortedEvent"; -export type { TurnCompleteEvent } from "./TurnCompleteEvent"; -export type { TurnDiffEvent } from "./TurnDiffEvent"; -export type { TurnItem } from "./TurnItem"; -export type { TurnStartedEvent } from "./TurnStartedEvent"; -export type { UndoCompletedEvent } from "./UndoCompletedEvent"; -export type { UndoStartedEvent } from "./UndoStartedEvent"; -export type { UpdatePlanArgs } from "./UpdatePlanArgs"; -export type { UserInfoResponse } from "./UserInfoResponse"; -export type { UserInput } from "./UserInput"; -export type { UserMessageEvent } from "./UserMessageEvent"; -export type { UserMessageItem } from "./UserMessageItem"; -export type { UserSavedConfig } from "./UserSavedConfig"; export type { Verbosity } from "./Verbosity"; -export type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; -export type { WarningEvent } from "./WarningEvent"; export type { WebSearchAction } from "./WebSearchAction"; -export type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; -export type { WebSearchEndEvent } from "./WebSearchEndEvent"; -export type { WebSearchItem } from "./WebSearchItem"; export type { WebSearchMode } from "./WebSearchMode"; export * as v2 from "./v2"; diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 78d5507277..2802a87480 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -45,36 +45,9 @@ const SPECIAL_DEFINITIONS: &[&str] = &[ "ServerRequest", ]; const FLAT_V2_SHARED_DEFINITIONS: &[&str] = &["ClientRequest", "EventMsg", "ServerNotification"]; -const V1_CLIENT_REQUEST_METHODS: &[&str] = &[ - "newConversation", - "getConversationSummary", - "listConversations", - "resumeConversation", - "forkConversation", - "archiveConversation", - "sendUserMessage", - "sendUserTurn", - "interruptConversation", - "addConversationListener", - "removeConversationListener", - "gitDiffToRemote", - "loginApiKey", - "loginChatGpt", - "cancelLoginChatGpt", - "logoutChatGpt", - "getAuthStatus", - "getUserSavedConfig", - "setDefaultModel", - "getUserAgent", - "userInfo", - "execOneOffCommand", -]; -const EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON: &[&str] = &[ - "authStatusChange", - "loginChatGptComplete", - "sessionConfigured", - "rawResponseItem/completed", -]; +const V1_CLIENT_REQUEST_METHODS: &[&str] = + &["getConversationSummary", "gitDiffToRemote", "getAuthStatus"]; +const EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON: &[&str] = &["rawResponseItem/completed"]; #[derive(Clone)] pub struct GeneratedSchema { diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index f63ebd37a2..98efcdd290 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -403,95 +403,19 @@ client_request_definitions! { }, /// DEPRECATED APIs below - NewConversation { - params: v1::NewConversationParams, - response: v1::NewConversationResponse, - }, GetConversationSummary { params: v1::GetConversationSummaryParams, response: v1::GetConversationSummaryResponse, }, - /// List recorded Codex conversations (rollouts) with optional pagination and search. - ListConversations { - params: v1::ListConversationsParams, - response: v1::ListConversationsResponse, - }, - /// Resume a recorded Codex conversation from a rollout file. - ResumeConversation { - params: v1::ResumeConversationParams, - response: v1::ResumeConversationResponse, - }, - /// Fork a recorded Codex conversation into a new session. - ForkConversation { - params: v1::ForkConversationParams, - response: v1::ForkConversationResponse, - }, - ArchiveConversation { - params: v1::ArchiveConversationParams, - response: v1::ArchiveConversationResponse, - }, - SendUserMessage { - params: v1::SendUserMessageParams, - response: v1::SendUserMessageResponse, - }, - SendUserTurn { - params: v1::SendUserTurnParams, - response: v1::SendUserTurnResponse, - }, - InterruptConversation { - params: v1::InterruptConversationParams, - response: v1::InterruptConversationResponse, - }, - AddConversationListener { - params: v1::AddConversationListenerParams, - response: v1::AddConversationSubscriptionResponse, - }, - RemoveConversationListener { - params: v1::RemoveConversationListenerParams, - response: v1::RemoveConversationSubscriptionResponse, - }, GitDiffToRemote { params: v1::GitDiffToRemoteParams, response: v1::GitDiffToRemoteResponse, }, - LoginApiKey { - params: v1::LoginApiKeyParams, - response: v1::LoginApiKeyResponse, - }, - LoginChatGpt { - params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, - response: v1::LoginChatGptResponse, - }, - // DEPRECATED in favor of CancelLoginAccount - CancelLoginChatGpt { - params: v1::CancelLoginChatGptParams, - response: v1::CancelLoginChatGptResponse, - }, - LogoutChatGpt { - params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, - response: v1::LogoutChatGptResponse, - }, /// DEPRECATED in favor of GetAccount GetAuthStatus { params: v1::GetAuthStatusParams, response: v1::GetAuthStatusResponse, }, - GetUserSavedConfig { - params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, - response: v1::GetUserSavedConfigResponse, - }, - SetDefaultModel { - params: v1::SetDefaultModelParams, - response: v1::SetDefaultModelResponse, - }, - GetUserAgent { - params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, - response: v1::GetUserAgentResponse, - }, - UserInfo { - params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, - response: v1::UserInfoResponse, - }, FuzzyFileSearch { params: FuzzyFileSearchParams, response: FuzzyFileSearchResponse, @@ -511,11 +435,6 @@ client_request_definitions! { params: FuzzyFileSearchSessionStopParams, response: FuzzyFileSearchSessionStopResponse, }, - /// Execute a command (argv vector) under the server's sandbox. - ExecOneOffCommand { - params: v1::ExecOneOffCommandParams, - response: v1::ExecOneOffCommandResponse, - }, } /// Generates an `enum ServerRequest` where each variant is a request that the @@ -882,12 +801,6 @@ server_notification_definitions! { #[strum(serialize = "account/login/completed")] AccountLoginCompleted(v2::AccountLoginCompletedNotification), - /// DEPRECATED NOTIFICATIONS below - AuthStatusChange(v1::AuthStatusChangeNotification), - - /// Deprecated: use `account/login/completed` instead. - LoginChatGptComplete(v1::LoginChatGptCompleteNotification), - SessionConfigured(v1::SessionConfiguredNotification), } client_notification_definitions! { @@ -901,7 +814,6 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::parse_command::ParsedCommand; - use codex_protocol::protocol::AskForApproval; use codex_utils_absolute_path::AbsolutePathBuf; use pretty_assertions::assert_eq; use serde_json::json; @@ -912,37 +824,19 @@ mod tests { } #[test] - fn serialize_new_conversation() -> Result<()> { - let request = ClientRequest::NewConversation { + fn serialize_get_conversation_summary() -> Result<()> { + let request = ClientRequest::GetConversationSummary { request_id: RequestId::Integer(42), - params: v1::NewConversationParams { - model: Some("gpt-5.1-codex-max".to_string()), - model_provider: None, - profile: None, - cwd: None, - approval_policy: Some(AskForApproval::OnRequest), - sandbox: None, - config: None, - base_instructions: None, - developer_instructions: None, - compact_prompt: None, - include_apply_patch_tool: None, + params: v1::GetConversationSummaryParams::ThreadId { + conversation_id: ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?, }, }; assert_eq!( json!({ - "method": "newConversation", + "method": "getConversationSummary", "id": 42, "params": { - "model": "gpt-5.1-codex-max", - "modelProvider": null, - "profile": null, - "cwd": null, - "approvalPolicy": "on-request", - "sandbox": null, - "config": null, - "baseInstructions": null, - "includeApplyPatchTool": null + "conversationId": "67e55044-10b1-426f-9247-bb680e5fe0c8" } }), serde_json::to_value(&request)?, diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index ed1ca86229..d393f97f72 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -596,34 +596,3 @@ impl InputItem { } } } - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -/// Deprecated in favor of AccountLoginCompletedNotification. -pub struct LoginChatGptCompleteNotification { - #[schemars(with = "String")] - pub login_id: Uuid, - pub success: bool, - pub error: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -pub struct SessionConfiguredNotification { - pub session_id: ThreadId, - pub model: String, - pub service_tier: Option, - pub reasoning_effort: Option, - pub history_log_id: u64, - #[ts(type = "number")] - pub history_entry_count: usize, - pub initial_messages: Option>, - pub rollout_path: PathBuf, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -/// Deprecated notification. Use AccountUpdatedNotification instead. -pub struct AuthStatusChangeNotification { - pub auth_method: Option, -} diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs index d8f06be421..7baad9be66 100644 --- a/codex-rs/app-server-test-client/src/lib.rs +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -23,8 +23,7 @@ use anyhow::bail; use clap::ArgAction; use clap::Parser; use clap::Subcommand; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; +use codex_app_server_protocol::AccountLoginCompletedNotification; use codex_app_server_protocol::AskForApproval; use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::ClientRequest; @@ -40,22 +39,16 @@ use codex_app_server_protocol::GetAccountRateLimitsResponse; use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::InitializeResponse; -use codex_app_server_protocol::InputItem; use codex_app_server_protocol::JSONRPCMessage; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCRequest; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginChatGptCompleteNotification; -use codex_app_server_protocol::LoginChatGptResponse; +use codex_app_server_protocol::LoginAccountResponse; use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; use codex_app_server_protocol::ReadOnlyAccess; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxPolicy; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::ThreadItem; @@ -69,9 +62,6 @@ use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::UserInput as V2UserInput; -use codex_protocol::ThreadId; -use codex_protocol::protocol::Event; -use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::W3cTraceContext; use serde::Serialize; use serde::de::DeserializeOwned; @@ -502,26 +492,16 @@ fn send_message( config_overrides: &[String], user_message: String, ) -> Result<()> { - with_client(endpoint, config_overrides, |client| { - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let conversation = client.start_thread()?; - println!("< newConversation response: {conversation:?}"); - - let subscription = client.add_conversation_listener(&conversation.conversation_id)?; - println!("< addConversationListener response: {subscription:?}"); - - let send_response = - client.send_user_message(&conversation.conversation_id, &user_message)?; - println!("< sendUserMessage response: {send_response:?}"); - - client.stream_conversation(&conversation.conversation_id)?; - - client.remove_thread_listener(subscription.subscription_id)?; - - Ok(()) - }) + let dynamic_tools = None; + send_message_v2_with_policies( + endpoint, + config_overrides, + user_message, + false, + None, + None, + &dynamic_tools, + ) } pub fn send_message_v2( @@ -877,15 +857,15 @@ fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> { let initialize = client.initialize()?; println!("< initialize response: {initialize:?}"); - let login_response = client.login_chat_gpt()?; - println!("< loginChatGpt response: {login_response:?}"); - println!( - "Open the following URL in your browser to continue:\n{}", - login_response.auth_url - ); + let login_response = client.login_account_chatgpt()?; + println!("< account/login/start response: {login_response:?}"); + let LoginAccountResponse::Chatgpt { login_id, auth_url } = login_response else { + bail!("expected chatgpt login response"); + }; + println!("Open the following URL in your browser to continue:\n{auth_url}"); - let completion = client.wait_for_login_completion(&login_response.login_id)?; - println!("< loginChatGptComplete notification: {completion:?}"); + let completion = client.wait_for_account_login_completion(&login_id)?; + println!("< account/login/completed notification: {completion:?}"); if completion.success { println!("Login succeeded."); @@ -896,7 +876,7 @@ fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> { completion .error .as_deref() - .unwrap_or("unknown error from loginChatGptComplete") + .unwrap_or("unknown error from account/login/completed") ); } }) @@ -1142,69 +1122,6 @@ impl CodexClient { Ok(response) } - fn start_thread(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::NewConversation { - request_id: request_id.clone(), - params: NewConversationParams::default(), - }; - - self.send_request(request, request_id, "newConversation") - } - - fn add_conversation_listener( - &mut self, - conversation_id: &ThreadId, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::AddConversationListener { - request_id: request_id.clone(), - params: AddConversationListenerParams { - conversation_id: *conversation_id, - experimental_raw_events: false, - }, - }; - - self.send_request(request, request_id, "addConversationListener") - } - - fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> { - let request_id = self.request_id(); - let request = ClientRequest::RemoveConversationListener { - request_id: request_id.clone(), - params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id }, - }; - - self.send_request::( - request, - request_id, - "removeConversationListener", - )?; - - Ok(()) - } - - fn send_user_message( - &mut self, - conversation_id: &ThreadId, - message: &str, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::SendUserMessage { - request_id: request_id.clone(), - params: SendUserMessageParams { - conversation_id: *conversation_id, - items: vec![InputItem::Text { - text: message.to_string(), - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - }, - }; - - self.send_request(request, request_id, "sendUserMessage") - } - fn thread_start(&mut self, params: ThreadStartParams) -> Result { let request_id = self.request_id(); let request = ClientRequest::ThreadStart { @@ -1235,14 +1152,14 @@ impl CodexClient { self.send_request(request, request_id, "turn/start") } - fn login_chat_gpt(&mut self) -> Result { + fn login_account_chatgpt(&mut self) -> Result { let request_id = self.request_id(); - let request = ClientRequest::LoginChatGpt { + let request = ClientRequest::LoginAccount { request_id: request_id.clone(), - params: None, + params: codex_app_server_protocol::LoginAccountParams::Chatgpt, }; - self.send_request(request, request_id, "loginChatGpt") + self.send_request(request, request_id, "account/login/start") } fn get_account_rate_limits(&mut self) -> Result { @@ -1275,77 +1192,31 @@ impl CodexClient { self.send_request(request, request_id, "thread/list") } - fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> { - loop { - let notification = self.next_notification()?; - - if !notification.method.starts_with("codex/event/") { - continue; - } - - if let Some(event) = self.extract_event(notification, conversation_id)? { - match &event.msg { - EventMsg::AgentMessage(event) => { - println!("{}", event.message); - } - EventMsg::AgentMessageDelta(event) => { - print!("{}", event.delta); - std::io::stdout().flush().ok(); - } - EventMsg::TurnComplete(event) => { - println!("\n[task complete: {event:?}]"); - break; - } - EventMsg::TurnAborted(event) => { - println!("\n[turn aborted: {:?}]", event.reason); - break; - } - EventMsg::Error(event) => { - println!("[error] {event:?}"); - } - _ => { - println!("[UNKNOWN EVENT] {:?}", event.msg); - } - } - } - } - - Ok(()) - } - - fn wait_for_login_completion( + fn wait_for_account_login_completion( &mut self, - expected_login_id: &Uuid, - ) -> Result { + expected_login_id: &str, + ) -> Result { loop { let notification = self.next_notification()?; if let Ok(server_notification) = ServerNotification::try_from(notification) { match server_notification { - ServerNotification::LoginChatGptComplete(completion) => { - if &completion.login_id == expected_login_id { + ServerNotification::AccountLoginCompleted(completion) => { + if completion.login_id.as_deref() == Some(expected_login_id) { return Ok(completion); } println!( - "[ignoring loginChatGptComplete for unexpected login_id: {}]", + "[ignoring account/login/completed for unexpected login_id: {:?}]", completion.login_id ); } - ServerNotification::AuthStatusChange(status) => { - println!("< authStatusChange notification: {status:?}"); - } ServerNotification::AccountRateLimitsUpdated(snapshot) => { println!("< accountRateLimitsUpdated notification: {snapshot:?}"); } - ServerNotification::SessionConfigured(_) => { - // SessionConfigured notifications are unrelated to login; skip. - } _ => {} } } - - // Not a server notification (likely a conversation event); keep waiting. } } @@ -1419,36 +1290,6 @@ impl CodexClient { } } - fn extract_event( - &self, - notification: JSONRPCNotification, - conversation_id: &ThreadId, - ) -> Result> { - let params = notification - .params - .context("event notification missing params")?; - - let mut map = match params { - Value::Object(map) => map, - other => bail!("unexpected params shape: {other:?}"), - }; - - let conversation_value = map - .remove("conversationId") - .context("event missing conversationId")?; - let notification_conversation: ThreadId = serde_json::from_value(conversation_value) - .context("conversationId was not a valid UUID")?; - - if ¬ification_conversation != conversation_id { - return Ok(None); - } - - let event_value = Value::Object(map); - let event: Event = - serde_json::from_value(event_value).context("failed to decode event payload")?; - Ok(Some(event)) - } - fn send_request( &mut self, request: ClientRequest, diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 3efc7140af..ba2355f675 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -65,11 +65,9 @@ axum = { workspace = true, default-features = false, features = [ "tokio", ] } base64 = { workspace = true } -codex-execpolicy = { workspace = true } core_test_support = { workspace = true } codex-state = { workspace = true } codex-utils-cargo-bin = { workspace = true } -os_info = { workspace = true } pretty_assertions = { workspace = true } rmcp = { workspace = true, default-features = false, features = [ "server", diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index ca9eb374c6..1c18345559 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -20,21 +20,15 @@ use chrono::Utc; use codex_app_server_protocol::Account; use codex_app_server_protocol::AccountLoginCompletedNotification; use codex_app_server_protocol::AccountUpdatedNotification; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppListUpdatedNotification; use codex_app_server_protocol::AppsListParams; use codex_app_server_protocol::AppsListResponse; -use codex_app_server_protocol::ArchiveConversationParams; -use codex_app_server_protocol::ArchiveConversationResponse; use codex_app_server_protocol::AskForApproval; use codex_app_server_protocol::AuthMode; -use codex_app_server_protocol::AuthStatusChangeNotification; use codex_app_server_protocol::CancelLoginAccountParams; use codex_app_server_protocol::CancelLoginAccountResponse; use codex_app_server_protocol::CancelLoginAccountStatus; -use codex_app_server_protocol::CancelLoginChatGptResponse; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::CollaborationModeListParams; use codex_app_server_protocol::CollaborationModeListResponse; @@ -49,8 +43,6 @@ use codex_app_server_protocol::ExperimentalFeatureListResponse; use codex_app_server_protocol::ExperimentalFeatureStage as ApiExperimentalFeatureStage; use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::FeedbackUploadResponse; -use codex_app_server_protocol::ForkConversationParams; -use codex_app_server_protocol::ForkConversationResponse; use codex_app_server_protocol::FuzzyFileSearchParams; use codex_app_server_protocol::FuzzyFileSearchResponse; use codex_app_server_protocol::FuzzyFileSearchSessionStartParams; @@ -66,26 +58,16 @@ use codex_app_server_protocol::GetAuthStatusParams; use codex_app_server_protocol::GetAuthStatusResponse; use codex_app_server_protocol::GetConversationSummaryParams; use codex_app_server_protocol::GetConversationSummaryResponse; -use codex_app_server_protocol::GetUserAgentResponse; -use codex_app_server_protocol::GetUserSavedConfigResponse; use codex_app_server_protocol::GitDiffToRemoteResponse; use codex_app_server_protocol::GitInfo as ApiGitInfo; use codex_app_server_protocol::HazelnutScope as ApiHazelnutScope; -use codex_app_server_protocol::InputItem as WireInputItem; -use codex_app_server_protocol::InterruptConversationParams; use codex_app_server_protocol::JSONRPCErrorError; -use codex_app_server_protocol::ListConversationsParams; -use codex_app_server_protocol::ListConversationsResponse; use codex_app_server_protocol::ListMcpServerStatusParams; use codex_app_server_protocol::ListMcpServerStatusResponse; use codex_app_server_protocol::LoginAccountParams; use codex_app_server_protocol::LoginAccountResponse; use codex_app_server_protocol::LoginApiKeyParams; -use codex_app_server_protocol::LoginApiKeyResponse; -use codex_app_server_protocol::LoginChatGptCompleteNotification; -use codex_app_server_protocol::LoginChatGptResponse; use codex_app_server_protocol::LogoutAccountResponse; -use codex_app_server_protocol::LogoutChatGptResponse; use codex_app_server_protocol::McpServerOauthLoginCompletedNotification; use codex_app_server_protocol::McpServerOauthLoginParams; use codex_app_server_protocol::McpServerOauthLoginResponse; @@ -95,28 +77,15 @@ use codex_app_server_protocol::MockExperimentalMethodParams; use codex_app_server_protocol::MockExperimentalMethodResponse; use codex_app_server_protocol::ModelListParams; use codex_app_server_protocol::ModelListResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; use codex_app_server_protocol::ProductSurface as ApiProductSurface; -use codex_app_server_protocol::RemoveConversationListenerParams; -use codex_app_server_protocol::RemoveConversationSubscriptionResponse; use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ResumeConversationParams; -use codex_app_server_protocol::ResumeConversationResponse; use codex_app_server_protocol::ReviewDelivery as ApiReviewDelivery; use codex_app_server_protocol::ReviewStartParams; use codex_app_server_protocol::ReviewStartResponse; use codex_app_server_protocol::ReviewTarget as ApiReviewTarget; use codex_app_server_protocol::SandboxMode; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_app_server_protocol::SendUserTurnParams; -use codex_app_server_protocol::SendUserTurnResponse; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequestResolvedNotification; -use codex_app_server_protocol::SessionConfiguredNotification; -use codex_app_server_protocol::SetDefaultModelParams; -use codex_app_server_protocol::SetDefaultModelResponse; use codex_app_server_protocol::SkillsConfigWriteParams; use codex_app_server_protocol::SkillsConfigWriteResponse; use codex_app_server_protocol::SkillsListParams; @@ -176,9 +145,7 @@ use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; -use codex_app_server_protocol::UserInfoResponse; use codex_app_server_protocol::UserInput as V2UserInput; -use codex_app_server_protocol::UserSavedConfig; use codex_app_server_protocol::WindowsSandboxSetupCompletedNotification; use codex_app_server_protocol::WindowsSandboxSetupMode; use codex_app_server_protocol::WindowsSandboxSetupStartParams; @@ -205,13 +172,11 @@ use codex_core::auth::login_with_api_key; use codex_core::auth::login_with_chatgpt_auth_tokens; use codex_core::config::Config; use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigService; use codex_core::config::NetworkProxyAuditMetadata; use codex_core::config::edit::ConfigEdit; use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::types::McpServerTransportConfig; use codex_core::config_loader::CloudRequirementsLoader; -use codex_core::default_client::get_codex_user_agent; use codex_core::default_client::set_default_client_residency_requirement; use codex_core::error::CodexErr; use codex_core::exec::ExecParams; @@ -330,7 +295,7 @@ struct ActiveLogin { #[derive(Clone, Copy, Debug)] enum CancelLoginError { - NotFound(Uuid), + NotFound, } enum AppListLoadResult { @@ -389,6 +354,7 @@ pub(crate) struct CodexMessageProcessor { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub(crate) enum ApiVersion { + #[allow(dead_code)] V1, #[default] V2, @@ -718,21 +684,10 @@ impl CodexMessageProcessor { self.review_start(to_connection_request_id(request_id), params) .await; } - ClientRequest::NewConversation { request_id, params } => { - // Do not tokio::spawn() to process new_conversation() - // asynchronously because we need to ensure the conversation is - // created before processing any subsequent messages. - self.process_new_conversation(to_connection_request_id(request_id), params) - .await; - } ClientRequest::GetConversationSummary { request_id, params } => { self.get_thread_summary(to_connection_request_id(request_id), params) .await; } - ClientRequest::ListConversations { request_id, params } => { - self.handle_list_conversations(to_connection_request_id(request_id), params) - .await; - } ClientRequest::ModelList { request_id, params } => { let outgoing = self.outgoing.clone(); let thread_manager = self.thread_manager.clone(); @@ -794,100 +749,14 @@ impl CodexMessageProcessor { self.get_account(to_connection_request_id(request_id), params) .await; } - ClientRequest::ResumeConversation { request_id, params } => { - self.handle_resume_conversation(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::ForkConversation { request_id, params } => { - self.handle_fork_conversation(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::ArchiveConversation { request_id, params } => { - self.archive_conversation(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::SendUserMessage { request_id, params } => { - self.send_user_message( - to_connection_request_id(request_id), - params, - app_server_client_name.clone(), - ) - .await; - } - ClientRequest::SendUserTurn { request_id, params } => { - self.send_user_turn( - to_connection_request_id(request_id), - params, - app_server_client_name.clone(), - ) - .await; - } - ClientRequest::InterruptConversation { request_id, params } => { - self.interrupt_conversation(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::AddConversationListener { request_id, params } => { - self.add_conversation_listener(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::RemoveConversationListener { request_id, params } => { - self.remove_thread_listener(to_connection_request_id(request_id), params) - .await; - } ClientRequest::GitDiffToRemote { request_id, params } => { self.git_diff_to_origin(to_connection_request_id(request_id), params.cwd) .await; } - ClientRequest::LoginApiKey { request_id, params } => { - self.login_api_key_v1(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::LoginChatGpt { - request_id, - params: _, - } => { - self.login_chatgpt_v1(to_connection_request_id(request_id)) - .await; - } - ClientRequest::CancelLoginChatGpt { request_id, params } => { - self.cancel_login_chatgpt(to_connection_request_id(request_id), params.login_id) - .await; - } - ClientRequest::LogoutChatGpt { - request_id, - params: _, - } => { - self.logout_v1(to_connection_request_id(request_id)).await; - } ClientRequest::GetAuthStatus { request_id, params } => { self.get_auth_status(to_connection_request_id(request_id), params) .await; } - ClientRequest::GetUserSavedConfig { - request_id, - params: _, - } => { - self.get_user_saved_config(to_connection_request_id(request_id)) - .await; - } - ClientRequest::SetDefaultModel { request_id, params } => { - self.set_default_model(to_connection_request_id(request_id), params) - .await; - } - ClientRequest::GetUserAgent { - request_id, - params: _, - } => { - self.get_user_agent(to_connection_request_id(request_id)) - .await; - } - ClientRequest::UserInfo { - request_id, - params: _, - } => { - self.get_user_info(to_connection_request_id(request_id)) - .await; - } ClientRequest::FuzzyFileSearch { request_id, params } => { self.fuzzy_file_search(to_connection_request_id(request_id), params) .await; @@ -908,10 +777,6 @@ impl CodexMessageProcessor { self.exec_one_off_command(to_connection_request_id(request_id), params) .await; } - ClientRequest::ExecOneOffCommand { request_id, params } => { - self.exec_one_off_command(to_connection_request_id(request_id), params.into()) - .await; - } ClientRequest::ConfigRead { .. } | ClientRequest::ConfigValueWrite { .. } | ClientRequest::ConfigBatchWrite { .. } => { @@ -1016,34 +881,6 @@ impl CodexMessageProcessor { } } - async fn login_api_key_v1( - &mut self, - request_id: ConnectionRequestId, - params: LoginApiKeyParams, - ) { - match self.login_api_key_common(¶ms).await { - Ok(()) => { - self.outgoing - .send_response(request_id, LoginApiKeyResponse {}) - .await; - - let payload = AuthStatusChangeNotification { - auth_method: self - .auth_manager - .auth_cached() - .as_ref() - .map(CodexAuth::api_auth_mode), - }; - self.outgoing - .send_server_notification(ServerNotification::AuthStatusChange(payload)) - .await; - } - Err(error) => { - self.outgoing.send_error(request_id, error).await; - } - } - } - async fn login_api_key_v2( &mut self, request_id: ConnectionRequestId, @@ -1106,115 +943,6 @@ impl CodexMessageProcessor { }) } - // Deprecated in favor of login_chatgpt_v2. - async fn login_chatgpt_v1(&mut self, request_id: ConnectionRequestId) { - match self.login_chatgpt_common().await { - Ok(opts) => match run_login_server(opts) { - Ok(server) => { - let login_id = Uuid::new_v4(); - let shutdown_handle = server.cancel_handle(); - - // Replace active login if present. - { - let mut guard = self.active_login.lock().await; - if let Some(existing) = guard.take() { - drop(existing); - } - *guard = Some(ActiveLogin { - shutdown_handle: shutdown_handle.clone(), - login_id, - }); - } - - // Spawn background task to monitor completion. - let outgoing_clone = self.outgoing.clone(); - let active_login = self.active_login.clone(); - let auth_manager = self.auth_manager.clone(); - let cloud_requirements = self.cloud_requirements.clone(); - let chatgpt_base_url = self.config.chatgpt_base_url.clone(); - let codex_home = self.config.codex_home.clone(); - let cli_overrides = self.cli_overrides.clone(); - let auth_url = server.auth_url.clone(); - tokio::spawn(async move { - let (success, error_msg) = match tokio::time::timeout( - LOGIN_CHATGPT_TIMEOUT, - server.block_until_done(), - ) - .await - { - Ok(Ok(())) => (true, None), - Ok(Err(err)) => (false, Some(format!("Login server error: {err}"))), - Err(_elapsed) => { - shutdown_handle.shutdown(); - (false, Some("Login timed out".to_string())) - } - }; - - let payload = LoginChatGptCompleteNotification { - login_id, - success, - error: error_msg.clone(), - }; - outgoing_clone - .send_server_notification(ServerNotification::LoginChatGptComplete( - payload, - )) - .await; - - if success { - auth_manager.reload(); - replace_cloud_requirements_loader( - cloud_requirements.as_ref(), - auth_manager.clone(), - chatgpt_base_url, - codex_home, - ); - sync_default_client_residency_requirement( - &cli_overrides, - cloud_requirements.as_ref(), - ) - .await; - - // Notify clients with the actual current auth mode. - let current_auth_method = auth_manager - .auth_cached() - .as_ref() - .map(CodexAuth::api_auth_mode); - let payload = AuthStatusChangeNotification { - auth_method: current_auth_method, - }; - outgoing_clone - .send_server_notification(ServerNotification::AuthStatusChange( - payload, - )) - .await; - } - - // Clear the active login if it matches this attempt. It may have been replaced or cancelled. - let mut guard = active_login.lock().await; - if guard.as_ref().map(|l| l.login_id) == Some(login_id) { - *guard = None; - } - }); - - let response = LoginChatGptResponse { login_id, auth_url }; - self.outgoing.send_response(request_id, response).await; - } - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("failed to start login server: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - }, - Err(err) => { - self.outgoing.send_error(request_id, err).await; - } - } - } - async fn login_chatgpt_v2(&mut self, request_id: ConnectionRequestId) { match self.login_chatgpt_common().await { Ok(opts) => match run_login_server(opts) { @@ -1335,25 +1063,7 @@ impl CodexMessageProcessor { } Ok(()) } else { - Err(CancelLoginError::NotFound(login_id)) - } - } - - async fn cancel_login_chatgpt(&mut self, request_id: ConnectionRequestId, login_id: Uuid) { - match self.cancel_login_chatgpt_common(login_id).await { - Ok(()) => { - self.outgoing - .send_response(request_id, CancelLoginChatGptResponse {}) - .await; - } - Err(CancelLoginError::NotFound(missing_login_id)) => { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("login id not found: {missing_login_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } + Err(CancelLoginError::NotFound) } } @@ -1367,7 +1077,7 @@ impl CodexMessageProcessor { Ok(uuid) => { let status = match self.cancel_login_chatgpt_common(uuid).await { Ok(()) => CancelLoginAccountStatus::Canceled, - Err(CancelLoginError::NotFound(_)) => CancelLoginAccountStatus::NotFound, + Err(CancelLoginError::NotFound) => CancelLoginAccountStatus::NotFound, }; let response = CancelLoginAccountResponse { status }; self.outgoing.send_response(request_id, response).await; @@ -1500,26 +1210,6 @@ impl CodexMessageProcessor { .map(CodexAuth::api_auth_mode)) } - async fn logout_v1(&mut self, request_id: ConnectionRequestId) { - match self.logout_common().await { - Ok(current_auth_method) => { - self.outgoing - .send_response(request_id, LogoutChatGptResponse {}) - .await; - - let payload = AuthStatusChangeNotification { - auth_method: current_auth_method, - }; - self.outgoing - .send_server_notification(ServerNotification::AuthStatusChange(payload)) - .await; - } - Err(error) => { - self.outgoing.send_error(request_id, error).await; - } - } - } - async fn logout_v2(&mut self, request_id: ConnectionRequestId) { match self.logout_common().await { Ok(current_auth_method) => { @@ -1651,12 +1341,6 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn get_user_agent(&self, request_id: ConnectionRequestId) { - let user_agent = get_codex_user_agent(); - let response = GetUserAgentResponse { user_agent }; - self.outgoing.send_response(request_id, response).await; - } - async fn get_account_rate_limits(&self, request_id: ConnectionRequestId) { match self.fetch_account_rate_limits().await { Ok((rate_limits, rate_limits_by_limit_id)) => { @@ -1746,69 +1430,6 @@ impl CodexMessageProcessor { Ok((primary, rate_limits_by_limit_id)) } - async fn get_user_saved_config(&self, request_id: ConnectionRequestId) { - let service = ConfigService::new_with_defaults(self.config.codex_home.clone()); - let user_saved_config: UserSavedConfig = match service.load_user_saved_config().await { - Ok(config) => config, - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: err.to_string(), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - let response = GetUserSavedConfigResponse { - config: user_saved_config, - }; - self.outgoing.send_response(request_id, response).await; - } - - async fn get_user_info(&self, request_id: ConnectionRequestId) { - // Read alleged user email from cached auth (best-effort; not verified). - let alleged_user_email = self - .auth_manager - .auth_cached() - .and_then(|a| a.get_account_email()); - - let response = UserInfoResponse { alleged_user_email }; - self.outgoing.send_response(request_id, response).await; - } - - async fn set_default_model( - &self, - request_id: ConnectionRequestId, - params: SetDefaultModelParams, - ) { - let SetDefaultModelParams { - model, - reasoning_effort, - } = params; - - match ConfigEditsBuilder::new(&self.config.codex_home) - .with_profile(self.config.active_profile.as_deref()) - .set_model(model.as_deref(), reasoning_effort) - .apply() - .await - { - Ok(()) => { - let response = SetDefaultModelResponse {}; - self.outgoing.send_response(request_id, response).await; - } - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("failed to persist model selection: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - } - } - async fn exec_one_off_command( &self, request_id: ConnectionRequestId, @@ -1930,119 +1551,6 @@ impl CodexMessageProcessor { }); } - async fn process_new_conversation( - &mut self, - request_id: ConnectionRequestId, - params: NewConversationParams, - ) { - let NewConversationParams { - model, - model_provider, - profile, - cwd, - approval_policy, - sandbox: sandbox_mode, - config: request_overrides, - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - } = params; - - let typesafe_overrides = ConfigOverrides { - model, - config_profile: profile, - cwd: cwd.clone().map(PathBuf::from), - approval_policy, - sandbox_mode, - model_provider, - codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(), - main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(), - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - ..Default::default() - }; - - // Persist Windows sandbox mode. - // TODO: persist default config in general. - let mut request_overrides = request_overrides.unwrap_or_default(); - if cfg!(windows) { - match WindowsSandboxLevel::from_config(&self.config) { - WindowsSandboxLevel::Elevated => { - request_overrides - .insert("windows.sandbox".to_string(), serde_json::json!("elevated")); - } - WindowsSandboxLevel::RestrictedToken => { - request_overrides.insert( - "windows.sandbox".to_string(), - serde_json::json!("unelevated"), - ); - } - WindowsSandboxLevel::Disabled => {} - } - } - - let cloud_requirements = self.current_cloud_requirements(); - let config = match derive_config_from_params( - &self.cli_overrides, - Some(request_overrides), - typesafe_overrides, - &cloud_requirements, - ) - .await - { - Ok(config) => config, - Err(err) => { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("error deriving config: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - match self.thread_manager.start_thread(config).await { - Ok(new_thread) => { - let NewThread { - thread_id, - session_configured, - .. - } = new_thread; - let rollout_path = match session_configured.rollout_path { - Some(path) => path, - None => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: "rollout path missing for v1 conversation".to_string(), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - let response = NewConversationResponse { - conversation_id: thread_id, - model: session_configured.model, - reasoning_effort: session_configured.reasoning_effort, - rollout_path, - }; - self.outgoing.send_response(request_id, response).await; - } - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("error creating conversation: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - } - } - async fn thread_start(&self, request_id: ConnectionRequestId, params: ThreadStartParams) { let ThreadStartParams { model, @@ -3814,45 +3322,6 @@ impl CodexMessageProcessor { } } - async fn handle_list_conversations( - &self, - request_id: ConnectionRequestId, - params: ListConversationsParams, - ) { - let ListConversationsParams { - page_size, - cursor, - model_providers, - } = params; - let requested_page_size = page_size - .unwrap_or(THREAD_LIST_DEFAULT_LIMIT) - .clamp(1, THREAD_LIST_MAX_LIMIT); - - match self - .list_threads_common( - requested_page_size, - cursor, - CoreThreadSortKey::UpdatedAt, - ThreadListFilters { - model_providers, - source_kinds: None, - archived: false, - cwd: None, - search_term: None, - }, - ) - .await - { - Ok((items, next_cursor)) => { - let response = ListConversationsResponse { items, next_cursor }; - self.outgoing.send_response(request_id, response).await; - } - Err(error) => { - self.outgoing.send_error(request_id, error).await; - } - }; - } - async fn list_threads_common( &self, requested_page_size: usize, @@ -4453,440 +3922,6 @@ impl CodexMessageProcessor { outgoing.send_response(request_id, response).await; } - async fn handle_resume_conversation( - &self, - request_id: ConnectionRequestId, - params: ResumeConversationParams, - ) { - let ResumeConversationParams { - path, - conversation_id, - history, - overrides, - } = params; - - let thread_history = if let Some(path) = path { - match RolloutRecorder::get_rollout_history(&path).await { - Ok(initial_history) => initial_history, - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("failed to load rollout `{}`: {err}", path.display()), - ) - .await; - return; - } - } - } else if let Some(conversation_id) = conversation_id { - match find_thread_path_by_id_str(&self.config.codex_home, &conversation_id.to_string()) - .await - { - Ok(Some(found_path)) => { - match RolloutRecorder::get_rollout_history(&found_path).await { - Ok(initial_history) => initial_history, - Err(err) => { - self.send_invalid_request_error( - request_id, - format!( - "failed to load rollout `{}` for conversation {conversation_id}: {err}", - found_path.display() - ), - ).await; - return; - } - } - } - Ok(None) => { - self.send_invalid_request_error( - request_id, - format!("no rollout found for conversation id {conversation_id}"), - ) - .await; - return; - } - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("failed to locate conversation id {conversation_id}: {err}"), - ) - .await; - return; - } - } - } else { - match history { - Some(history) if !history.is_empty() => InitialHistory::Forked( - history.into_iter().map(RolloutItem::ResponseItem).collect(), - ), - Some(_) | None => { - self.send_invalid_request_error( - request_id, - "either path, conversation id or non empty history must be provided" - .to_string(), - ) - .await; - return; - } - } - }; - - let history_cwd = thread_history.session_cwd(); - let (typesafe_overrides, request_overrides) = match overrides { - Some(overrides) => { - let NewConversationParams { - model, - model_provider, - profile, - cwd, - approval_policy, - sandbox: sandbox_mode, - config: request_overrides, - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - } = overrides; - - // Persist Windows sandbox mode. - let mut request_overrides = request_overrides.unwrap_or_default(); - if cfg!(windows) { - match WindowsSandboxLevel::from_config(&self.config) { - WindowsSandboxLevel::Elevated => { - request_overrides.insert( - "windows.sandbox".to_string(), - serde_json::json!("elevated"), - ); - } - WindowsSandboxLevel::RestrictedToken => { - request_overrides.insert( - "windows.sandbox".to_string(), - serde_json::json!("unelevated"), - ); - } - WindowsSandboxLevel::Disabled => {} - } - } - - let typesafe_overrides = ConfigOverrides { - model, - config_profile: profile, - cwd: cwd.map(PathBuf::from), - approval_policy, - sandbox_mode, - model_provider, - codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(), - main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(), - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - ..Default::default() - }; - (typesafe_overrides, Some(request_overrides)) - } - None => ( - ConfigOverrides { - codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(), - main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(), - ..Default::default() - }, - None, - ), - }; - - let cloud_requirements = self.current_cloud_requirements(); - let config = match derive_config_for_cwd( - &self.cli_overrides, - request_overrides, - typesafe_overrides, - history_cwd, - &cloud_requirements, - ) - .await - { - Ok(cfg) => cfg, - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("error deriving config: {err}"), - ) - .await; - return; - } - }; - - match self - .thread_manager - .resume_thread_with_history(config, thread_history, self.auth_manager.clone(), false) - .await - { - Ok(NewThread { - thread_id, - session_configured, - .. - }) => { - let rollout_path = match session_configured.rollout_path.clone() { - Some(path) => path, - None => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: "rollout path missing for resumed conversation".to_string(), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - self.outgoing - .send_server_notification(ServerNotification::SessionConfigured( - SessionConfiguredNotification { - session_id: session_configured.session_id, - model: session_configured.model.clone(), - service_tier: session_configured.service_tier, - reasoning_effort: session_configured.reasoning_effort, - history_log_id: session_configured.history_log_id, - history_entry_count: session_configured.history_entry_count, - initial_messages: session_configured.initial_messages.clone(), - rollout_path: rollout_path.clone(), - }, - )) - .await; - let initial_messages = session_configured - .initial_messages - .map(|msgs| msgs.into_iter().collect()); - - // Reply with thread id + model and initial messages (when present) - let response = ResumeConversationResponse { - conversation_id: thread_id, - model: session_configured.model.clone(), - initial_messages, - rollout_path, - }; - self.outgoing.send_response(request_id, response).await; - } - Err(err) => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: format!("error resuming conversation: {err}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - } - } - - async fn handle_fork_conversation( - &self, - request_id: ConnectionRequestId, - params: ForkConversationParams, - ) { - let ForkConversationParams { - path, - conversation_id, - overrides, - } = params; - - // Derive a Config using the same logic as new conversation, honoring overrides if provided. - let (rollout_path, source_thread_id) = if let Some(path) = path { - (path, None) - } else if let Some(conversation_id) = conversation_id { - match find_thread_path_by_id_str(&self.config.codex_home, &conversation_id.to_string()) - .await - { - Ok(Some(found_path)) => (found_path, Some(conversation_id)), - Ok(None) => { - self.send_invalid_request_error( - request_id, - format!("no rollout found for conversation id {conversation_id}"), - ) - .await; - return; - } - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("failed to locate conversation id {conversation_id}: {err}"), - ) - .await; - return; - } - } - } else { - self.send_invalid_request_error( - request_id, - "either path or conversation id must be provided".to_string(), - ) - .await; - return; - }; - - let history_cwd = - read_history_cwd_from_state_db(&self.config, source_thread_id, rollout_path.as_path()) - .await; - - let (typesafe_overrides, request_overrides) = match overrides { - Some(overrides) => { - let NewConversationParams { - model, - model_provider, - profile, - cwd, - approval_policy, - sandbox: sandbox_mode, - config: cli_overrides, - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - } = overrides; - - // Persist Windows sandbox mode. - let mut cli_overrides = cli_overrides.unwrap_or_default(); - if cfg!(windows) { - match WindowsSandboxLevel::from_config(&self.config) { - WindowsSandboxLevel::Elevated => { - cli_overrides.insert( - "windows.sandbox".to_string(), - serde_json::json!("elevated"), - ); - } - WindowsSandboxLevel::RestrictedToken => { - cli_overrides.insert( - "windows.sandbox".to_string(), - serde_json::json!("unelevated"), - ); - } - WindowsSandboxLevel::Disabled => {} - } - } - let request_overrides = if cli_overrides.is_empty() { - None - } else { - Some(cli_overrides) - }; - - let overrides = ConfigOverrides { - model, - config_profile: profile, - cwd: cwd.map(PathBuf::from), - approval_policy, - sandbox_mode, - model_provider, - codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(), - main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(), - base_instructions, - developer_instructions, - compact_prompt, - include_apply_patch_tool, - ..Default::default() - }; - - (overrides, request_overrides) - } - None => ( - ConfigOverrides { - codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(), - main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(), - ..Default::default() - }, - None, - ), - }; - - let cloud_requirements = self.current_cloud_requirements(); - let config = match derive_config_for_cwd( - &self.cli_overrides, - request_overrides, - typesafe_overrides, - history_cwd, - &cloud_requirements, - ) - .await - { - Ok(cfg) => cfg, - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("error deriving config: {err}"), - ) - .await; - return; - } - }; - - let NewThread { - thread_id, - session_configured, - .. - } = match self - .thread_manager - .fork_thread(usize::MAX, config, rollout_path.clone(), false) - .await - { - Ok(thread) => thread, - Err(err) => { - let (code, message) = match err { - CodexErr::Io(_) | CodexErr::Json(_) => ( - INVALID_REQUEST_ERROR_CODE, - format!("failed to load rollout `{}`: {err}", rollout_path.display()), - ), - CodexErr::InvalidRequest(message) => (INVALID_REQUEST_ERROR_CODE, message), - _ => ( - INTERNAL_ERROR_CODE, - format!("error forking conversation: {err}"), - ), - }; - let error = JSONRPCErrorError { - code, - message, - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - let rollout_path = match session_configured.rollout_path.clone() { - Some(path) => path, - None => { - let error = JSONRPCErrorError { - code: INTERNAL_ERROR_CODE, - message: "rollout path missing for forked conversation".to_string(), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - - self.outgoing - .send_server_notification(ServerNotification::SessionConfigured( - SessionConfiguredNotification { - session_id: session_configured.session_id, - model: session_configured.model.clone(), - service_tier: session_configured.service_tier, - reasoning_effort: session_configured.reasoning_effort, - history_log_id: session_configured.history_log_id, - history_entry_count: session_configured.history_entry_count, - initial_messages: session_configured.initial_messages.clone(), - rollout_path: rollout_path.clone(), - }, - )) - .await; - let initial_messages = session_configured - .initial_messages - .map(|msgs| msgs.into_iter().collect()); - - // Reply with conversation id + model and initial messages (when present) - let response = ForkConversationResponse { - conversation_id: thread_id, - model: session_configured.model.clone(), - initial_messages, - rollout_path, - }; - self.outgoing.send_response(request_id, response).await; - } - async fn send_invalid_request_error(&self, request_id: ConnectionRequestId, message: String) { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, @@ -4910,14 +3945,6 @@ impl CodexMessageProcessor { } } - fn validate_v1_input_limit(items: &[WireInputItem]) -> Result<(), JSONRPCErrorError> { - let actual_chars: usize = items.iter().map(WireInputItem::text_char_count).sum(); - if actual_chars > MAX_USER_INPUT_TEXT_CHARS { - return Err(Self::input_too_large_error(actual_chars)); - } - Ok(()) - } - fn validate_v2_input_limit(items: &[V2UserInput]) -> Result<(), JSONRPCErrorError> { let actual_chars: usize = items.iter().map(V2UserInput::text_char_count).sum(); if actual_chars > MAX_USER_INPUT_TEXT_CHARS { @@ -4935,29 +3962,6 @@ impl CodexMessageProcessor { self.outgoing.send_error(request_id, error).await; } - async fn archive_conversation( - &mut self, - request_id: ConnectionRequestId, - params: ArchiveConversationParams, - ) { - let ArchiveConversationParams { - conversation_id: thread_id, - rollout_path, - } = params; - - match self.archive_thread_common(thread_id, &rollout_path).await { - Ok(()) => { - tracing::info!("thread/archive succeeded for {thread_id}"); - let response = ArchiveConversationResponse {}; - self.outgoing.send_response(request_id, response).await; - } - Err(err) => { - tracing::warn!("thread/archive failed for {thread_id}: {}", err.message); - self.outgoing.send_error(request_id, err).await; - } - } - } - async fn wait_for_thread_shutdown(thread: &Arc) -> ThreadShutdownResult { match thread.submit(Op::Shutdown).await { Ok(_) => { @@ -5220,140 +4224,6 @@ impl CodexMessageProcessor { }) } - async fn send_user_message( - &self, - request_id: ConnectionRequestId, - params: SendUserMessageParams, - app_server_client_name: Option, - ) { - let SendUserMessageParams { - conversation_id, - items, - } = params; - if let Err(error) = Self::validate_v1_input_limit(&items) { - self.outgoing.send_error(request_id, error).await; - return; - } - let Ok(conversation) = self.thread_manager.get_thread(conversation_id).await else { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("conversation not found: {conversation_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - }; - if let Err(error) = - Self::set_app_server_client_name(conversation.as_ref(), app_server_client_name).await - { - self.outgoing.send_error(request_id, error).await; - return; - } - - let mapped_items: Vec = items - .into_iter() - .map(|item| match item { - WireInputItem::Text { - text, - text_elements, - } => CoreInputItem::Text { - text, - text_elements: text_elements.into_iter().map(Into::into).collect(), - }, - WireInputItem::Image { image_url } => CoreInputItem::Image { image_url }, - WireInputItem::LocalImage { path } => CoreInputItem::LocalImage { path }, - }) - .collect(); - - // Submit user input to the conversation. - let _ = conversation - .submit(Op::UserInput { - items: mapped_items, - final_output_json_schema: None, - }) - .await; - - // Acknowledge with an empty result. - self.outgoing - .send_response(request_id, SendUserMessageResponse {}) - .await; - } - - async fn send_user_turn( - &self, - request_id: ConnectionRequestId, - params: SendUserTurnParams, - app_server_client_name: Option, - ) { - let SendUserTurnParams { - conversation_id, - items, - cwd, - approval_policy, - sandbox_policy, - model, - service_tier, - effort, - summary, - output_schema, - } = params; - if let Err(error) = Self::validate_v1_input_limit(&items) { - self.outgoing.send_error(request_id, error).await; - return; - } - - let Ok(conversation) = self.thread_manager.get_thread(conversation_id).await else { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("conversation not found: {conversation_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - }; - if let Err(error) = - Self::set_app_server_client_name(conversation.as_ref(), app_server_client_name).await - { - self.outgoing.send_error(request_id, error).await; - return; - } - - let mapped_items: Vec = items - .into_iter() - .map(|item| match item { - WireInputItem::Text { - text, - text_elements, - } => CoreInputItem::Text { - text, - text_elements: text_elements.into_iter().map(Into::into).collect(), - }, - WireInputItem::Image { image_url } => CoreInputItem::Image { image_url }, - WireInputItem::LocalImage { path } => CoreInputItem::LocalImage { path }, - }) - .collect(); - - let _ = conversation - .submit(Op::UserTurn { - items: mapped_items, - cwd, - approval_policy, - sandbox_policy, - model, - effort, - summary: Some(summary), - service_tier, - final_output_json_schema: output_schema, - collaboration_mode: None, - personality: None, - }) - .await; - - self.outgoing - .send_response(request_id, SendUserTurnResponse {}) - .await; - } - async fn apps_list(&self, request_id: ConnectionRequestId, params: AppsListParams) { let mut config = match self.load_latest_config().await { Ok(config) => config, @@ -5808,40 +4678,6 @@ impl CodexMessageProcessor { } } - async fn interrupt_conversation( - &mut self, - request_id: ConnectionRequestId, - params: InterruptConversationParams, - ) { - let InterruptConversationParams { conversation_id } = params; - let Ok(conversation) = self.thread_manager.get_thread(conversation_id).await else { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("conversation not found: {conversation_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - }; - - let request = request_id.clone(); - - // Record the pending interrupt so we can reply when TurnAborted arrives. - { - let pending_interrupts = self - .thread_state_manager - .thread_state(conversation_id) - .await; - let mut thread_state = pending_interrupts.lock().await; - thread_state - .pending_interrupts - .push((request, ApiVersion::V1)); - } - - // Submit the interrupt; we'll respond upon TurnAborted. - let _ = conversation.submit(Op::Interrupt).await; - } - async fn turn_start( &self, request_id: ConnectionRequestId, @@ -6447,76 +5283,6 @@ impl CodexMessageProcessor { let _ = thread.submit(Op::Interrupt).await; } - async fn add_conversation_listener( - &mut self, - request_id: ConnectionRequestId, - params: AddConversationListenerParams, - ) { - let AddConversationListenerParams { - conversation_id, - experimental_raw_events, - } = params; - let conversation = match self.thread_manager.get_thread(conversation_id).await { - Ok(conv) => conv, - Err(_) => { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("thread not found: {conversation_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - return; - } - }; - let subscription_id = Uuid::new_v4(); - let thread_state = self - .thread_state_manager - .set_listener( - subscription_id, - conversation_id, - request_id.connection_id, - experimental_raw_events, - ) - .await; - self.ensure_listener_task_running( - conversation_id, - conversation, - thread_state, - ApiVersion::V1, - ) - .await; - - let response = AddConversationSubscriptionResponse { subscription_id }; - self.outgoing.send_response(request_id, response).await; - } - - async fn remove_thread_listener( - &mut self, - request_id: ConnectionRequestId, - params: RemoveConversationListenerParams, - ) { - let RemoveConversationListenerParams { subscription_id } = params; - match self - .thread_state_manager - .remove_listener(subscription_id) - .await - { - Some(thread_id) => { - info!("removed listener for thread {thread_id}"); - let response = RemoveConversationSubscriptionResponse {}; - self.outgoing.send_response(request_id, response).await; - } - None => { - let error = JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, - message: format!("subscription not found: {subscription_id}"), - data: None, - }; - self.outgoing.send_error(request_id, error).await; - } - } - } - async fn ensure_conversation_listener( &self, conversation_id: ThreadId, @@ -8290,143 +7056,6 @@ mod tests { Ok(()) } - #[tokio::test] - async fn removing_listeners_retains_thread_listener_when_last_subscriber_leaves() -> Result<()> - { - let manager = ThreadStateManager::new(); - let thread_id = ThreadId::from_string("ad7f0408-99b8-4f6e-a46f-bd0eec433370")?; - let listener_a = Uuid::new_v4(); - let listener_b = Uuid::new_v4(); - let connection_a = ConnectionId(1); - let connection_b = ConnectionId(2); - let (cancel_tx, mut cancel_rx) = oneshot::channel(); - - manager - .set_listener(listener_a, thread_id, connection_a, false) - .await; - manager - .set_listener(listener_b, thread_id, connection_b, false) - .await; - { - let state = manager.thread_state(thread_id).await; - state.lock().await.cancel_tx = Some(cancel_tx); - } - - assert_eq!(manager.remove_listener(listener_a).await, Some(thread_id)); - assert!( - tokio::time::timeout(Duration::from_millis(20), &mut cancel_rx) - .await - .is_err() - ); - assert_eq!(manager.remove_listener(listener_b).await, Some(thread_id)); - assert!( - tokio::time::timeout(Duration::from_millis(20), &mut cancel_rx) - .await - .is_err() - ); - assert!( - manager - .subscribed_connection_ids(thread_id) - .await - .is_empty() - ); - Ok(()) - } - - #[tokio::test] - async fn removing_listener_unsubscribes_its_connection() -> Result<()> { - let manager = ThreadStateManager::new(); - let thread_id = ThreadId::from_string("ad7f0408-99b8-4f6e-a46f-bd0eec433370")?; - let listener_a = Uuid::new_v4(); - let listener_b = Uuid::new_v4(); - let connection_a = ConnectionId(1); - let connection_b = ConnectionId(2); - - manager - .set_listener(listener_a, thread_id, connection_a, false) - .await; - manager - .set_listener(listener_b, thread_id, connection_b, false) - .await; - - assert_eq!(manager.remove_listener(listener_a).await, Some(thread_id)); - let subscribed_connection_ids = manager.subscribed_connection_ids(thread_id).await; - assert_eq!(subscribed_connection_ids, vec![connection_b]); - Ok(()) - } - - #[tokio::test] - async fn set_listener_uses_last_write_for_raw_events() -> Result<()> { - let manager = ThreadStateManager::new(); - let thread_id = ThreadId::from_string("ad7f0408-99b8-4f6e-a46f-bd0eec433370")?; - let listener_a = Uuid::new_v4(); - let listener_b = Uuid::new_v4(); - let connection_a = ConnectionId(1); - let connection_b = ConnectionId(2); - - manager - .set_listener(listener_a, thread_id, connection_a, true) - .await; - { - let state = manager.thread_state(thread_id).await; - assert!(state.lock().await.experimental_raw_events); - } - manager - .set_listener(listener_b, thread_id, connection_b, false) - .await; - let state = manager.thread_state(thread_id).await; - assert!(!state.lock().await.experimental_raw_events); - Ok(()) - } - - #[tokio::test] - async fn removing_connection_retains_listener_and_active_turn_when_last_subscriber_disconnects() - -> Result<()> { - let manager = ThreadStateManager::new(); - let thread_id = ThreadId::from_string("ad7f0408-99b8-4f6e-a46f-bd0eec433370")?; - let listener = Uuid::new_v4(); - let connection = ConnectionId(1); - let (cancel_tx, mut cancel_rx) = oneshot::channel(); - - manager - .set_listener(listener, thread_id, connection, false) - .await; - { - let state = manager.thread_state(thread_id).await; - let mut state = state.lock().await; - state.cancel_tx = Some(cancel_tx); - state.track_current_turn_event(&EventMsg::TurnStarted( - codex_protocol::protocol::TurnStartedEvent { - turn_id: "turn-1".to_string(), - model_context_window: None, - collaboration_mode_kind: Default::default(), - }, - )); - } - - manager.remove_connection(connection).await; - assert!( - tokio::time::timeout(Duration::from_millis(20), &mut cancel_rx) - .await - .is_err() - ); - assert_eq!(manager.remove_listener(listener).await, None); - - let state = manager.thread_state(thread_id).await; - let state = state.lock().await; - assert!( - manager - .subscribed_connection_ids(thread_id) - .await - .is_empty() - ); - assert!(state.cancel_tx.is_some()); - let active_turn = state.active_turn_snapshot().expect("active turn snapshot"); - assert_eq!(active_turn.id, "turn-1"); - assert_eq!(active_turn.status, TurnStatus::InProgress); - Ok(()) - } - #[tokio::test] async fn removing_thread_state_clears_listener_and_active_turn_history() -> Result<()> { let manager = ThreadStateManager::new(); diff --git a/codex-rs/app-server/src/outgoing_message.rs b/codex-rs/app-server/src/outgoing_message.rs index 5f56c81c57..368d5d21a0 100644 --- a/codex-rs/app-server/src/outgoing_message.rs +++ b/codex-rs/app-server/src/outgoing_message.rs @@ -500,7 +500,6 @@ mod tests { use codex_app_server_protocol::ConfigWarningNotification; use codex_app_server_protocol::DynamicToolCallParams; use codex_app_server_protocol::FileChangeRequestApprovalParams; - use codex_app_server_protocol::LoginChatGptCompleteNotification; use codex_app_server_protocol::ModelRerouteReason; use codex_app_server_protocol::ModelReroutedNotification; use codex_app_server_protocol::RateLimitSnapshot; @@ -518,8 +517,8 @@ mod tests { #[test] fn verify_server_notification_serialization() { let notification = - ServerNotification::LoginChatGptComplete(LoginChatGptCompleteNotification { - login_id: Uuid::nil(), + ServerNotification::AccountLoginCompleted(AccountLoginCompletedNotification { + login_id: Some(Uuid::nil().to_string()), success: true, error: None, }); @@ -527,9 +526,9 @@ mod tests { let jsonrpc_notification = OutgoingMessage::AppServerNotification(notification); assert_eq!( json!({ - "method": "loginChatGptComplete", + "method": "account/login/completed", "params": { - "loginId": Uuid::nil(), + "loginId": Uuid::nil().to_string(), "success": true, "error": null, }, diff --git a/codex-rs/app-server/src/thread_state.rs b/codex-rs/app-server/src/thread_state.rs index a60dc07b51..915cc0fd77 100644 --- a/codex-rs/app-server/src/thread_state.rs +++ b/codex-rs/app-server/src/thread_state.rs @@ -16,7 +16,6 @@ use std::sync::Weak; use tokio::sync::Mutex; use tokio::sync::mpsc; use tokio::sync::oneshot; -use uuid::Uuid; type PendingInterruptQueue = Vec<( ConnectionRequestId, @@ -116,12 +115,6 @@ impl ThreadState { } } -#[derive(Clone, Copy)] -struct SubscriptionState { - thread_id: ThreadId, - connection_id: ConnectionId, -} - struct ThreadEntry { state: Arc>, connection_ids: HashSet, @@ -140,7 +133,6 @@ impl Default for ThreadEntry { struct ThreadStateManagerInner { live_connections: HashSet, threads: HashMap, - subscription_state_by_id: HashMap, thread_ids_by_connection: HashMap>, } @@ -176,70 +168,6 @@ impl ThreadStateManager { state.threads.entry(thread_id).or_default().state.clone() } - pub(crate) async fn remove_listener(&self, subscription_id: Uuid) -> Option { - let (subscription_state, connection_still_subscribed_to_thread, thread_state) = { - let mut state = self.state.lock().await; - let subscription_state = state.subscription_state_by_id.remove(&subscription_id)?; - let thread_id = subscription_state.thread_id; - - let connection_still_subscribed_to_thread = state - .subscription_state_by_id - .values() - .any(|subscription_state_entry| { - subscription_state_entry.thread_id == thread_id - && subscription_state_entry.connection_id - == subscription_state.connection_id - }); - if !connection_still_subscribed_to_thread { - let mut remove_connection_entry = false; - if let Some(thread_ids) = state - .thread_ids_by_connection - .get_mut(&subscription_state.connection_id) - { - thread_ids.remove(&thread_id); - remove_connection_entry = thread_ids.is_empty(); - } - if remove_connection_entry { - state - .thread_ids_by_connection - .remove(&subscription_state.connection_id); - } - if let Some(thread_entry) = state.threads.get_mut(&thread_id) { - thread_entry - .connection_ids - .remove(&subscription_state.connection_id); - } - } - - let thread_state = state.threads.get(&thread_id).map(|thread_entry| { - ( - thread_entry.connection_ids.is_empty(), - thread_entry.state.clone(), - ) - }); - ( - subscription_state, - connection_still_subscribed_to_thread, - thread_state, - ) - }; - let thread_id = subscription_state.thread_id; - - if let Some((no_subscribers, thread_state)) = thread_state { - let thread_state = thread_state.lock().await; - if !connection_still_subscribed_to_thread && no_subscribers { - tracing::debug!( - thread_id = %thread_id, - subscription_id = %subscription_id, - connection_id = ?subscription_state.connection_id, - listener_generation = thread_state.listener_generation, - "retaining thread listener after last subscription removed" - ); - } - } - Some(thread_id) - } - pub(crate) async fn remove_thread_state(&self, thread_id: ThreadId) { let thread_state = { let mut state = self.state.lock().await; @@ -247,9 +175,6 @@ impl ThreadStateManager { .threads .remove(&thread_id) .map(|thread_entry| thread_entry.state); - state - .subscription_state_by_id - .retain(|_, state| state.thread_id != thread_id); state.thread_ids_by_connection.retain(|_, thread_ids| { thread_ids.remove(&thread_id); !thread_ids.is_empty() @@ -298,13 +223,6 @@ impl ThreadStateManager { if let Some(thread_entry) = state.threads.get_mut(&thread_id) { thread_entry.connection_ids.remove(&connection_id); } - - state - .subscription_state_by_id - .retain(|_, subscription_state| { - !(subscription_state.thread_id == thread_id - && subscription_state.connection_id == connection_id) - }); }; true @@ -319,38 +237,6 @@ impl ThreadStateManager { .is_some_and(|thread_entry| !thread_entry.connection_ids.is_empty()) } - pub(crate) async fn set_listener( - &self, - subscription_id: Uuid, - thread_id: ThreadId, - connection_id: ConnectionId, - experimental_raw_events: bool, - ) -> Arc> { - let thread_state = { - let mut state = self.state.lock().await; - state.subscription_state_by_id.insert( - subscription_id, - SubscriptionState { - thread_id, - connection_id, - }, - ); - state - .thread_ids_by_connection - .entry(connection_id) - .or_default() - .insert(thread_id); - let thread_entry = state.threads.entry(thread_id).or_default(); - thread_entry.connection_ids.insert(connection_id); - thread_entry.state.clone() - }; - { - let mut thread_state_guard = thread_state.lock().await; - thread_state_guard.set_experimental_raw_events(experimental_raw_events); - } - thread_state - } - pub(crate) async fn try_ensure_connection_subscribed( &self, thread_id: ThreadId, @@ -411,9 +297,6 @@ impl ThreadStateManager { .thread_ids_by_connection .remove(&connection_id) .unwrap_or_default(); - state - .subscription_state_by_id - .retain(|_, state| state.connection_id != connection_id); for thread_id in &thread_ids { if let Some(thread_entry) = state.threads.get_mut(thread_id) { thread_entry.connection_ids.remove(&connection_id); diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 9f0fb31cb4..4b525878b8 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -11,11 +11,8 @@ use tokio::process::ChildStdin; use tokio::process::ChildStdout; use anyhow::Context; -use codex_app_server_protocol::AddConversationListenerParams; use codex_app_server_protocol::AppsListParams; -use codex_app_server_protocol::ArchiveConversationParams; use codex_app_server_protocol::CancelLoginAccountParams; -use codex_app_server_protocol::CancelLoginChatGptParams; use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::ClientNotification; use codex_app_server_protocol::CollaborationModeListParams; @@ -24,32 +21,23 @@ use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ExperimentalFeatureListParams; use codex_app_server_protocol::FeedbackUploadParams; -use codex_app_server_protocol::ForkConversationParams; use codex_app_server_protocol::GetAccountParams; use codex_app_server_protocol::GetAuthStatusParams; +use codex_app_server_protocol::GetConversationSummaryParams; use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::InitializeParams; -use codex_app_server_protocol::InterruptConversationParams; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::JSONRPCMessage; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCRequest; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::ListConversationsParams; use codex_app_server_protocol::LoginAccountParams; -use codex_app_server_protocol::LoginApiKeyParams; use codex_app_server_protocol::MockExperimentalMethodParams; use codex_app_server_protocol::ModelListParams; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::RemoveConversationListenerParams; use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ResumeConversationParams; use codex_app_server_protocol::ReviewStartParams; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserTurnParams; use codex_app_server_protocol::ServerRequest; -use codex_app_server_protocol::SetDefaultModelParams; use codex_app_server_protocol::SkillsListParams; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadCompactStartParams; @@ -243,71 +231,6 @@ impl McpProcess { } } - /// Send a `newConversation` JSON-RPC request. - pub async fn send_new_conversation_request( - &mut self, - params: NewConversationParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("newConversation", params).await - } - - /// Send an `archiveConversation` JSON-RPC request. - pub async fn send_archive_conversation_request( - &mut self, - params: ArchiveConversationParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("archiveConversation", params).await - } - - /// Send an `addConversationListener` JSON-RPC request. - pub async fn send_add_conversation_listener_request( - &mut self, - params: AddConversationListenerParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("addConversationListener", params).await - } - - /// Send a `sendUserMessage` JSON-RPC request with a single text item. - pub async fn send_send_user_message_request( - &mut self, - params: SendUserMessageParams, - ) -> anyhow::Result { - // Wire format expects variants in camelCase; text item uses external tagging. - let params = Some(serde_json::to_value(params)?); - self.send_request("sendUserMessage", params).await - } - - /// Send a `removeConversationListener` JSON-RPC request. - pub async fn send_remove_thread_listener_request( - &mut self, - params: RemoveConversationListenerParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("removeConversationListener", params) - .await - } - - /// Send a `sendUserTurn` JSON-RPC request. - pub async fn send_send_user_turn_request( - &mut self, - params: SendUserTurnParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("sendUserTurn", params).await - } - - /// Send a `interruptConversation` JSON-RPC request. - pub async fn send_interrupt_conversation_request( - &mut self, - params: InterruptConversationParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("interruptConversation", params).await - } - /// Send a `getAuthStatus` JSON-RPC request. pub async fn send_get_auth_status_request( &mut self, @@ -317,14 +240,13 @@ impl McpProcess { self.send_request("getAuthStatus", params).await } - /// Send a `getUserSavedConfig` JSON-RPC request. - pub async fn send_get_user_saved_config_request(&mut self) -> anyhow::Result { - self.send_request("getUserSavedConfig", None).await - } - - /// Send a `getUserAgent` JSON-RPC request. - pub async fn send_get_user_agent_request(&mut self) -> anyhow::Result { - self.send_request("getUserAgent", None).await + /// Send a `getConversationSummary` JSON-RPC request. + pub async fn send_get_conversation_summary_request( + &mut self, + params: GetConversationSummaryParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("getConversationSummary", params).await } /// Send an `account/rateLimits/read` JSON-RPC request. @@ -366,29 +288,6 @@ impl McpProcess { self.send_request("feedback/upload", params).await } - /// Send a `userInfo` JSON-RPC request. - pub async fn send_user_info_request(&mut self) -> anyhow::Result { - self.send_request("userInfo", None).await - } - - /// Send a `setDefaultModel` JSON-RPC request. - pub async fn send_set_default_model_request( - &mut self, - params: SetDefaultModelParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("setDefaultModel", params).await - } - - /// Send a `listConversations` JSON-RPC request. - pub async fn send_list_conversations_request( - &mut self, - params: ListConversationsParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("listConversations", params).await - } - /// Send a `thread/start` JSON-RPC request. pub async fn send_thread_start_request( &mut self, @@ -548,38 +447,6 @@ impl McpProcess { self.send_request("mock/experimentalMethod", params).await } - /// Send a `resumeConversation` JSON-RPC request. - pub async fn send_resume_conversation_request( - &mut self, - params: ResumeConversationParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("resumeConversation", params).await - } - - /// Send a `forkConversation` JSON-RPC request. - pub async fn send_fork_conversation_request( - &mut self, - params: ForkConversationParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("forkConversation", params).await - } - - /// Send a `loginApiKey` JSON-RPC request. - pub async fn send_login_api_key_request( - &mut self, - params: LoginApiKeyParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("loginApiKey", params).await - } - - /// Send a `loginChatGpt` JSON-RPC request. - pub async fn send_login_chat_gpt_request(&mut self) -> anyhow::Result { - self.send_request("loginChatGpt", None).await - } - /// Send a `turn/start` JSON-RPC request (v2). pub async fn send_turn_start_request( &mut self, @@ -719,20 +586,6 @@ impl McpProcess { self.send_request("windowsSandbox/setupStart", params).await } - /// Send a `cancelLoginChatGpt` JSON-RPC request. - pub async fn send_cancel_login_chat_gpt_request( - &mut self, - params: CancelLoginChatGptParams, - ) -> anyhow::Result { - let params = Some(serde_json::to_value(params)?); - self.send_request("cancelLoginChatGpt", params).await - } - - /// Send a `logoutChatGpt` JSON-RPC request. - pub async fn send_logout_chat_gpt_request(&mut self) -> anyhow::Result { - self.send_request("logoutChatGpt", None).await - } - pub async fn send_config_read_request( &mut self, params: ConfigReadParams, diff --git a/codex-rs/app-server/tests/suite/archive_thread.rs b/codex-rs/app-server/tests/suite/archive_thread.rs deleted file mode 100644 index a47b247bfd..0000000000 --- a/codex-rs/app-server/tests/suite/archive_thread.rs +++ /dev/null @@ -1,154 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_mock_responses_server_repeating_assistant; -use app_test_support::to_response; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::ArchiveConversationParams; -use codex_app_server_protocol::ArchiveConversationResponse; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_core::ARCHIVED_SESSIONS_SUBDIR; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(20); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn archive_conversation_moves_rollout_into_archived_directory() -> Result<()> { - let server = create_mock_responses_server_repeating_assistant("Done").await; - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_request_id = mcp - .send_new_conversation_request(NewConversationParams { - model: Some("mock-model".to_string()), - ..Default::default() - }) - .await?; - let new_response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_request_id)), - ) - .await??; - - let NewConversationResponse { - conversation_id, - rollout_path, - .. - } = to_response::(new_response)?; - - assert!( - !rollout_path.exists(), - "expected rollout path {} to be deferred until first user message", - rollout_path.display() - ); - - let add_listener_request_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let add_listener_response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_request_id)), - ) - .await??; - let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::(add_listener_response)?; - - let send_request_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![InputItem::Text { - text: "materialize".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - let send_response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_request_id)), - ) - .await??; - let _: SendUserMessageResponse = to_response::(send_response)?; - let _: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - assert!( - rollout_path.exists(), - "expected rollout path {} to exist after first user message", - rollout_path.display() - ); - - let archive_request_id = mcp - .send_archive_conversation_request(ArchiveConversationParams { - conversation_id, - rollout_path: rollout_path.clone(), - }) - .await?; - let archive_response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(archive_request_id)), - ) - .await??; - - let _: ArchiveConversationResponse = - to_response::(archive_response)?; - - let archived_directory = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR); - let archived_rollout_path = - archived_directory.join(rollout_path.file_name().unwrap_or_else(|| { - panic!("rollout path {} missing file name", rollout_path.display()) - })); - - assert!( - !rollout_path.exists(), - "expected rollout path {} to be moved", - rollout_path.display() - ); - assert!( - archived_rollout_path.exists(), - "expected archived rollout path {} to exist", - archived_rollout_path.display() - ); - - Ok(()) -} - -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write(config_toml, config_contents(server_uri)) -} - -fn config_contents(server_uri: &str) -> String { - format!( - r#"model = "mock-model" -approval_policy = "never" -sandbox_mode = "read-only" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ) -} diff --git a/codex-rs/app-server/tests/suite/auth.rs b/codex-rs/app-server/tests/suite/auth.rs index 4693158611..2bbdeff400 100644 --- a/codex-rs/app-server/tests/suite/auth.rs +++ b/codex-rs/app-server/tests/suite/auth.rs @@ -6,8 +6,7 @@ use codex_app_server_protocol::GetAuthStatusParams; use codex_app_server_protocol::GetAuthStatusResponse; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginApiKeyParams; -use codex_app_server_protocol::LoginApiKeyResponse; +use codex_app_server_protocol::LoginAccountResponse; use codex_app_server_protocol::RequestId; use pretty_assertions::assert_eq; use std::path::Path; @@ -72,18 +71,15 @@ forced_login_method = "{forced_method}" } async fn login_with_api_key_via_request(mcp: &mut McpProcess, api_key: &str) -> Result<()> { - let request_id = mcp - .send_login_api_key_request(LoginApiKeyParams { - api_key: api_key.to_string(), - }) - .await?; + let request_id = mcp.send_login_account_api_key_request(api_key).await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) .await??; - let _: LoginApiKeyResponse = to_response(resp)?; + let response: LoginAccountResponse = to_response(resp)?; + assert_eq!(response, LoginAccountResponse::ApiKey {}); Ok(()) } @@ -211,9 +207,7 @@ async fn login_api_key_rejected_when_forced_chatgpt() -> Result<()> { timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let request_id = mcp - .send_login_api_key_request(LoginApiKeyParams { - api_key: "sk-test-key".to_string(), - }) + .send_login_account_api_key_request("sk-test-key") .await?; let err: JSONRPCError = timeout( diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs deleted file mode 100644 index b5176886bd..0000000000 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ /dev/null @@ -1,550 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_final_assistant_message_sse_response; -use app_test_support::create_mock_responses_server_sequence; -use app_test_support::create_shell_command_sse_response; -use app_test_support::format_with_current_shell; -use app_test_support::to_response; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::ExecCommandApprovalParams; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RemoveConversationListenerParams; -use codex_app_server_protocol::RemoveConversationSubscriptionResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_app_server_protocol::SendUserTurnParams; -use codex_app_server_protocol::SendUserTurnResponse; -use codex_app_server_protocol::ServerRequest; -use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; -use codex_protocol::config_types::ReasoningSummary; -use codex_protocol::config_types::SandboxMode; -use codex_protocol::openai_models::ReasoningEffort; -use codex_protocol::parse_command::ParsedCommand; -use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::Event; -use codex_protocol::protocol::EventMsg; -use codex_protocol::protocol::SandboxPolicy; -use pretty_assertions::assert_eq; -use std::env; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(45); - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_codex_jsonrpc_conversation_flow() -> Result<()> { - if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { - println!( - "Skipping test because it cannot execute when network is disabled in a Codex sandbox." - ); - return Ok(()); - } - - let tmp = TempDir::new()?; - // Temporary Codex home with config pointing at the mock server. - let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home)?; - let working_directory = tmp.path().join("workdir"); - std::fs::create_dir(&working_directory)?; - - // Create a mock model server that immediately ends each turn. - // Two turns are expected: initial session configure + one user message. - let responses = vec![ - create_shell_command_sse_response( - vec!["ls".to_string()], - Some(&working_directory), - Some(5000), - "call1234", - )?, - create_final_assistant_message_sse_response("Enjoy your new git repo!")?, - ]; - let server = create_mock_responses_server_sequence(responses).await; - create_config_toml(&codex_home, &server.uri())?; - - // Start MCP server and initialize. - let mut mcp = McpProcess::new(&codex_home).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // 1) newConversation - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - cwd: Some(working_directory.to_string_lossy().into_owned()), - sandbox: Some(SandboxMode::DangerFullAccess), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let new_conv_resp = to_response::(new_conv_resp)?; - let NewConversationResponse { - conversation_id, - model, - reasoning_effort: _, - rollout_path: _, - } = new_conv_resp; - assert_eq!(model, "mock-model"); - - // 2) addConversationListener - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let add_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - let AddConversationSubscriptionResponse { subscription_id } = - to_response::(add_listener_resp)?; - - // Drop any buffered events from conversation setup to avoid - // matching an earlier task_complete. - mcp.clear_message_buffer(); - - // 3) sendUserMessage (should trigger notifications; we only validate an OK response) - let send_user_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![codex_app_server_protocol::InputItem::Text { - text: "text".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - let send_user_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)), - ) - .await??; - let SendUserMessageResponse {} = to_response::(send_user_resp)?; - - let task_started_notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_started"), - ) - .await??; - let task_started_event: Event = serde_json::from_value( - task_started_notification - .params - .clone() - .expect("task_started should have params"), - ) - .expect("task_started should deserialize to Event"); - - // Verify the task_finished notification for this turn is received. - // Note this also ensures that the final request to the server was made. - let task_finished_notification: JSONRPCNotification = loop { - let notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - let event: Event = serde_json::from_value( - notification - .params - .clone() - .expect("task_complete should have params"), - ) - .expect("task_complete should deserialize to Event"); - if event.id == task_started_event.id { - break notification; - } - }; - let serde_json::Value::Object(map) = task_finished_notification - .params - .expect("notification should have params") - else { - panic!("task_finished_notification should have params"); - }; - assert_eq!( - map.get("conversationId") - .expect("should have conversationId"), - &serde_json::Value::String(conversation_id.to_string()) - ); - - // 4) removeConversationListener - let remove_listener_id = mcp - .send_remove_thread_listener_request(RemoveConversationListenerParams { subscription_id }) - .await?; - let remove_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(remove_listener_id)), - ) - .await??; - let RemoveConversationSubscriptionResponse {} = to_response(remove_listener_resp)?; - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_send_user_turn_changes_approval_policy_behavior() -> Result<()> { - if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { - println!( - "Skipping test because it cannot execute when network is disabled in a Codex sandbox." - ); - return Ok(()); - } - - let tmp = TempDir::new()?; - let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home)?; - let working_directory = tmp.path().join("workdir"); - std::fs::create_dir(&working_directory)?; - - // Mock server will request a python shell call for the first and second turn, then finish. - let responses = vec![ - create_shell_command_sse_response( - vec![ - "python3".to_string(), - "-c".to_string(), - "print(42)".to_string(), - ], - Some(&working_directory), - Some(5000), - "call1", - )?, - create_final_assistant_message_sse_response("done 1")?, - create_shell_command_sse_response( - vec![ - "python3".to_string(), - "-c".to_string(), - "print(42)".to_string(), - ], - Some(&working_directory), - Some(5000), - "call2", - )?, - create_final_assistant_message_sse_response("done 2")?, - ]; - let server = create_mock_responses_server_sequence(responses).await; - create_config_toml(&codex_home, &server.uri())?; - - // Start MCP server and initialize. - let mut mcp = McpProcess::new(&codex_home).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // 1) Start conversation with approval_policy=untrusted - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - cwd: Some(working_directory.to_string_lossy().into_owned()), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::(new_conv_resp)?; - - // 2) addConversationListener - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let _: AddConversationSubscriptionResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??, - )?; - - // 3) sendUserMessage triggers a shell call; approval policy is Untrusted so we should get an elicitation - let send_user_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![codex_app_server_protocol::InputItem::Text { - text: "run python".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - let _send_user_resp: SendUserMessageResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)), - ) - .await??, - )?; - - // Expect an ExecCommandApproval request (elicitation) - let request = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_request_message(), - ) - .await??; - let ServerRequest::ExecCommandApproval { request_id, params } = request else { - panic!("expected ExecCommandApproval request, got: {request:?}"); - }; - - assert_eq!( - ExecCommandApprovalParams { - conversation_id, - call_id: "call1".to_string(), - approval_id: None, - command: format_with_current_shell("python3 -c 'print(42)'"), - cwd: working_directory.clone(), - reason: None, - parsed_cmd: vec![ParsedCommand::Unknown { - cmd: "python3 -c 'print(42)'".to_string() - }], - }, - params - ); - - // Approve so the first turn can complete - mcp.send_response( - request_id, - serde_json::json!({ "decision": codex_protocol::protocol::ReviewDecision::Approved }), - ) - .await?; - - // Wait for first TurnComplete - let _ = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - // 4) sendUserTurn with approval_policy=never should run without elicitation - let send_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![codex_app_server_protocol::InputItem::Text { - text: "run python again".to_string(), - text_elements: Vec::new(), - }], - cwd: working_directory.clone(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - model: "mock-model".to_string(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: None, - }) - .await?; - // Acknowledge sendUserTurn - let _send_turn_resp: SendUserTurnResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id)), - ) - .await??, - )?; - - // Ensure we do NOT receive an ExecCommandApproval request before the task completes. - // If any Request is seen while waiting for task_complete, the helper will error and the test fails. - let _ = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - Ok(()) -} - -// Helper: minimal config.toml pointing at mock provider. - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() -> Result<()> { - if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { - println!( - "Skipping test because it cannot execute when network is disabled in a Codex sandbox." - ); - return Ok(()); - } - - let tmp = TempDir::new()?; - let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home)?; - let workspace_root = tmp.path().join("workspace"); - std::fs::create_dir(&workspace_root)?; - let first_cwd = workspace_root.join("turn1"); - let second_cwd = workspace_root.join("turn2"); - std::fs::create_dir(&first_cwd)?; - std::fs::create_dir(&second_cwd)?; - - let responses = vec![ - create_shell_command_sse_response( - vec!["echo".to_string(), "first".to_string(), "turn".to_string()], - None, - Some(5000), - "call-first", - )?, - create_final_assistant_message_sse_response("done first")?, - create_shell_command_sse_response( - vec!["echo".to_string(), "second".to_string(), "turn".to_string()], - None, - Some(5000), - "call-second", - )?, - create_final_assistant_message_sse_response("done second")?, - ]; - let server = create_mock_responses_server_sequence(responses).await; - create_config_toml(&codex_home, &server.uri())?; - - let mut mcp = McpProcess::new(&codex_home).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - cwd: Some(first_cwd.to_string_lossy().into_owned()), - approval_policy: Some(AskForApproval::Never), - sandbox: Some(SandboxMode::WorkspaceWrite), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, - model, - .. - } = to_response::(new_conv_resp)?; - - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - - let first_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: "first turn".to_string(), - text_elements: Vec::new(), - }], - cwd: first_cwd.clone(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::WorkspaceWrite { - writable_roots: vec![first_cwd.try_into()?], - read_only_access: Default::default(), - network_access: false, - exclude_tmpdir_env_var: false, - exclude_slash_tmp: false, - }, - model: model.clone(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: None, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(first_turn_id)), - ) - .await??; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - mcp.clear_message_buffer(); - - let second_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: "second turn".to_string(), - text_elements: Vec::new(), - }], - cwd: second_cwd.clone(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, - model: model.clone(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: None, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(second_turn_id)), - ) - .await??; - - let exec_begin_notification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/exec_command_begin"), - ) - .await??; - let params = exec_begin_notification - .params - .clone() - .expect("exec_command_begin params"); - let event: Event = serde_json::from_value(params).expect("deserialize exec begin event"); - let exec_begin = match event.msg { - EventMsg::ExecCommandBegin(exec_begin) => exec_begin, - other => panic!("expected ExecCommandBegin event, got {other:?}"), - }; - assert_eq!( - exec_begin.cwd, second_cwd, - "exec turn should run from updated cwd" - ); - let expected_command = format_with_current_shell("echo second turn"); - assert_eq!( - exec_begin.command, expected_command, - "exec turn should run expected command" - ); - - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - Ok(()) -} - -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "mock-model" -approval_policy = "untrusted" -sandbox_mode = "danger-full-access" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} diff --git a/codex-rs/app-server/tests/suite/config.rs b/codex-rs/app-server/tests/suite/config.rs deleted file mode 100644 index 27a44ef550..0000000000 --- a/codex-rs/app-server/tests/suite/config.rs +++ /dev/null @@ -1,158 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::test_tmp_path; -use app_test_support::to_response; -use codex_app_server_protocol::GetUserSavedConfigResponse; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::Profile; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SandboxSettings; -use codex_app_server_protocol::Tools; -use codex_app_server_protocol::UserSavedConfig; -use codex_protocol::config_types::ForcedLoginMethod; -use codex_protocol::config_types::ReasoningSummary; -use codex_protocol::config_types::SandboxMode; -use codex_protocol::config_types::Verbosity; -use codex_protocol::openai_models::ReasoningEffort; -use codex_protocol::protocol::AskForApproval; -use pretty_assertions::assert_eq; -use std::collections::HashMap; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { - let writable_root = test_tmp_path(); - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "gpt-5.1-codex-max" -approval_policy = "on-request" -sandbox_mode = "workspace-write" -model_reasoning_summary = "detailed" -model_reasoning_effort = "high" -model_verbosity = "medium" -profile = "test" -forced_chatgpt_workspace_id = "12345678-0000-0000-0000-000000000000" -forced_login_method = "chatgpt" - -[sandbox_workspace_write] -writable_roots = [{}] -network_access = true -exclude_tmpdir_env_var = true -exclude_slash_tmp = true - -[tools] -web_search = false -view_image = true - -[profiles.test] -model = "gpt-4o" -approval_policy = "on-request" -model_reasoning_effort = "high" -model_reasoning_summary = "detailed" -model_verbosity = "medium" -model_provider = "openai" -chatgpt_base_url = "https://api.chatgpt.com" -"#, - serde_json::json!(writable_root) - ), - ) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn get_config_toml_parses_all_fields() -> Result<()> { - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_get_user_saved_config_request().await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let config: GetUserSavedConfigResponse = to_response(resp)?; - let writable_root = test_tmp_path(); - let expected = GetUserSavedConfigResponse { - config: UserSavedConfig { - approval_policy: Some(AskForApproval::OnRequest), - sandbox_mode: Some(SandboxMode::WorkspaceWrite), - sandbox_settings: Some(SandboxSettings { - writable_roots: vec![writable_root], - network_access: Some(true), - exclude_tmpdir_env_var: Some(true), - exclude_slash_tmp: Some(true), - }), - forced_chatgpt_workspace_id: Some("12345678-0000-0000-0000-000000000000".into()), - forced_login_method: Some(ForcedLoginMethod::Chatgpt), - model: Some("gpt-5.1-codex-max".into()), - model_reasoning_effort: Some(ReasoningEffort::High), - model_reasoning_summary: Some(ReasoningSummary::Detailed), - model_verbosity: Some(Verbosity::Medium), - tools: Some(Tools { - web_search: Some(false), - view_image: Some(true), - }), - profile: Some("test".to_string()), - profiles: HashMap::from([( - "test".into(), - Profile { - model: Some("gpt-4o".into()), - approval_policy: Some(AskForApproval::OnRequest), - model_reasoning_effort: Some(ReasoningEffort::High), - model_reasoning_summary: Some(ReasoningSummary::Detailed), - model_verbosity: Some(Verbosity::Medium), - model_provider: Some("openai".into()), - chatgpt_base_url: Some("https://api.chatgpt.com".into()), - }, - )]), - }, - }; - - assert_eq!(config, expected); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_config_toml_empty() -> Result<()> { - let codex_home = TempDir::new()?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_get_user_saved_config_request().await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let config: GetUserSavedConfigResponse = to_response(resp)?; - let expected = GetUserSavedConfigResponse { - config: UserSavedConfig { - approval_policy: None, - sandbox_mode: None, - sandbox_settings: None, - forced_chatgpt_workspace_id: None, - forced_login_method: None, - model: None, - model_reasoning_effort: None, - model_reasoning_summary: None, - model_verbosity: None, - tools: None, - profile: None, - profiles: HashMap::new(), - }, - }; - - assert_eq!(config, expected); - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/conversation_summary.rs b/codex-rs/app-server/tests/suite/conversation_summary.rs new file mode 100644 index 0000000000..31108a8455 --- /dev/null +++ b/codex-rs/app-server/tests/suite/conversation_summary.rs @@ -0,0 +1,113 @@ +use anyhow::Result; +use app_test_support::McpProcess; +use app_test_support::create_fake_rollout; +use app_test_support::rollout_path; +use app_test_support::to_response; +use codex_app_server_protocol::ConversationSummary; +use codex_app_server_protocol::GetConversationSummaryParams; +use codex_app_server_protocol::GetConversationSummaryResponse; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::RequestId; +use codex_protocol::ThreadId; +use codex_protocol::protocol::SessionSource; +use pretty_assertions::assert_eq; +use std::path::PathBuf; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +const FILENAME_TS: &str = "2025-01-02T12-00-00"; +const META_RFC3339: &str = "2025-01-02T12:00:00Z"; +const PREVIEW: &str = "Summarize this conversation"; +const MODEL_PROVIDER: &str = "openai"; + +fn expected_summary(conversation_id: ThreadId, path: PathBuf) -> ConversationSummary { + ConversationSummary { + conversation_id, + path, + preview: PREVIEW.to_string(), + timestamp: Some(META_RFC3339.to_string()), + updated_at: Some(META_RFC3339.to_string()), + model_provider: MODEL_PROVIDER.to_string(), + cwd: PathBuf::from("/"), + cli_version: "0.0.0".to_string(), + source: SessionSource::Cli, + git_info: None, + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_conversation_summary_by_thread_id_reads_rollout() -> Result<()> { + let codex_home = TempDir::new()?; + let conversation_id = create_fake_rollout( + codex_home.path(), + FILENAME_TS, + META_RFC3339, + PREVIEW, + Some(MODEL_PROVIDER), + None, + )?; + let thread_id = ThreadId::from_string(&conversation_id)?; + let expected = expected_summary( + thread_id, + std::fs::canonicalize(rollout_path( + codex_home.path(), + FILENAME_TS, + &conversation_id, + ))?, + ); + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_get_conversation_summary_request(GetConversationSummaryParams::ThreadId { + conversation_id: thread_id, + }) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let received: GetConversationSummaryResponse = to_response(response)?; + + assert_eq!(received.summary, expected); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_conversation_summary_by_relative_rollout_path_resolves_from_codex_home() -> Result<()> +{ + let codex_home = TempDir::new()?; + let conversation_id = create_fake_rollout( + codex_home.path(), + FILENAME_TS, + META_RFC3339, + PREVIEW, + Some(MODEL_PROVIDER), + None, + )?; + let thread_id = ThreadId::from_string(&conversation_id)?; + let rollout_path = rollout_path(codex_home.path(), FILENAME_TS, &conversation_id); + let relative_path = rollout_path.strip_prefix(codex_home.path())?.to_path_buf(); + let expected = expected_summary(thread_id, std::fs::canonicalize(rollout_path)?); + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_get_conversation_summary_request(GetConversationSummaryParams::RolloutPath { + rollout_path: relative_path, + }) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let received: GetConversationSummaryResponse = to_response(response)?; + + assert_eq!(received.summary, expected); + Ok(()) +} diff --git a/codex-rs/app-server/tests/suite/create_thread.rs b/codex-rs/app-server/tests/suite/create_thread.rs deleted file mode 100644 index 8ad3342539..0000000000 --- a/codex-rs/app-server/tests/suite/create_thread.rs +++ /dev/null @@ -1,142 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_final_assistant_message_sse_response; -use app_test_support::to_response; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use core_test_support::responses; -use pretty_assertions::assert_eq; -use serde_json::json; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_conversation_create_and_send_message_ok() -> Result<()> { - // Mock server – we won't strictly rely on it, but provide one to satisfy any model wiring. - let response_body = create_final_assistant_message_sse_response("Done")?; - let server = responses::start_mock_server().await; - let response_mock = responses::mount_sse_sequence(&server, vec![response_body]).await; - - // Temporary Codex home with config pointing at the mock server. - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - // Start MCP server process and initialize. - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // Create a conversation via the new JSON-RPC API. - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - model: Some("o3".to_string()), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, - model, - reasoning_effort: _, - rollout_path: _, - } = to_response::(new_conv_resp)?; - assert_eq!(model, "o3"); - - // Add a listener so we receive notifications for this conversation (not strictly required for this test). - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let _sub: AddConversationSubscriptionResponse = - to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??, - )?; - - // Now send a user message via the wire API and expect an OK (empty object) result. - let send_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![InputItem::Text { - text: "Hello".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - let send_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_id)), - ) - .await??; - let _ok: SendUserMessageResponse = to_response::(send_resp)?; - - // Avoid race condition by waiting for the mock server to receive the responses request. - let deadline = std::time::Instant::now() + DEFAULT_READ_TIMEOUT; - let requests = loop { - let requests = response_mock.requests(); - if !requests.is_empty() { - break requests; - } - if std::time::Instant::now() >= deadline { - panic!("mock server did not receive the responses request in time"); - } - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - }; - - // Verify the outbound request body matches expectations for Responses. - let request = requests - .first() - .expect("mock server should have received at least one request"); - let body = request.body_json(); - assert_eq!(body["model"], json!("o3")); - let user_texts = request.message_input_texts("user"); - assert!( - user_texts.iter().any(|text| text == "Hello"), - "expected user input to include Hello, got {user_texts:?}" - ); - - drop(server); - Ok(()) -} - -// Helper to create a config.toml pointing at the mock model server. -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} diff --git a/codex-rs/app-server/tests/suite/fork_thread.rs b/codex-rs/app-server/tests/suite/fork_thread.rs deleted file mode 100644 index ec282be4a1..0000000000 --- a/codex-rs/app-server/tests/suite/fork_thread.rs +++ /dev/null @@ -1,140 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_fake_rollout; -use app_test_support::to_response; -use codex_app_server_protocol::ForkConversationParams; -use codex_app_server_protocol::ForkConversationResponse; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; // reused for overrides shape -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ServerNotification; -use codex_app_server_protocol::SessionConfiguredNotification; -use codex_protocol::protocol::EventMsg; -use pretty_assertions::assert_eq; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn fork_conversation_creates_new_rollout() -> Result<()> { - let codex_home = TempDir::new()?; - - let preview = "Hello A"; - let conversation_id = create_fake_rollout( - codex_home.path(), - "2025-01-02T12-00-00", - "2025-01-02T12:00:00Z", - preview, - Some("openai"), - None, - )?; - - let original_path = codex_home - .path() - .join("sessions") - .join("2025") - .join("01") - .join("02") - .join(format!( - "rollout-2025-01-02T12-00-00-{conversation_id}.jsonl" - )); - assert!( - original_path.exists(), - "expected original rollout to exist at {}", - original_path.display() - ); - let original_contents = std::fs::read_to_string(&original_path)?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let fork_req_id = mcp - .send_fork_conversation_request(ForkConversationParams { - path: Some(original_path.clone()), - conversation_id: None, - overrides: Some(NewConversationParams { - model: Some("o3".to_string()), - ..Default::default() - }), - }) - .await?; - - // Expect a sessionConfigured notification for the forked session. - let notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("sessionConfigured"), - ) - .await??; - let session_configured: ServerNotification = notification.try_into()?; - let ServerNotification::SessionConfigured(SessionConfiguredNotification { - model, - session_id, - rollout_path, - initial_messages: session_initial_messages, - .. - }) = session_configured - else { - unreachable!("expected sessionConfigured notification"); - }; - - assert_eq!(model, "o3"); - assert_ne!( - session_id.to_string(), - conversation_id, - "expected a new conversation id when forking" - ); - assert_ne!( - rollout_path, original_path, - "expected a new rollout path when forking" - ); - assert!( - rollout_path.exists(), - "expected forked rollout to exist at {}", - rollout_path.display() - ); - - let session_initial_messages = - session_initial_messages.expect("expected initial messages when forking from rollout"); - match session_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, preview); - } - other => panic!("unexpected initial messages from rollout fork: {other:#?}"), - } - - // Then the response for forkConversation. - let fork_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(fork_req_id)), - ) - .await??; - let ForkConversationResponse { - conversation_id: forked_id, - model: forked_model, - initial_messages: response_initial_messages, - rollout_path: response_rollout_path, - } = to_response::(fork_resp)?; - - assert_eq!(forked_model, "o3"); - assert_eq!(response_rollout_path, rollout_path); - assert_ne!(forked_id.to_string(), conversation_id); - - let response_initial_messages = - response_initial_messages.expect("expected initial messages in fork response"); - match response_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, preview); - } - other => panic!("unexpected initial messages in fork response: {other:#?}"), - } - - let after_contents = std::fs::read_to_string(&original_path)?; - assert_eq!( - after_contents, original_contents, - "fork should not mutate the original rollout file" - ); - - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/interrupt.rs b/codex-rs/app-server/tests/suite/interrupt.rs deleted file mode 100644 index dd09b832f8..0000000000 --- a/codex-rs/app-server/tests/suite/interrupt.rs +++ /dev/null @@ -1,163 +0,0 @@ -#![cfg(unix)] -// Support code lives in the `app_test_support` crate under tests/common. - -use std::path::Path; - -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::InterruptConversationParams; -use codex_app_server_protocol::InterruptConversationResponse; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_protocol::protocol::TurnAbortReason; -use core_test_support::skip_if_no_network; -use tempfile::TempDir; -use tokio::time::timeout; - -use app_test_support::McpProcess; -use app_test_support::create_mock_responses_server_sequence; -use app_test_support::create_shell_command_sse_response; -use app_test_support::to_response; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_shell_command_interruption() { - skip_if_no_network!(); - - if let Err(err) = shell_command_interruption().await { - panic!("failure: {err}"); - } -} - -async fn shell_command_interruption() -> anyhow::Result<()> { - // Use a cross-platform blocking command. On Windows plain `sleep` is not guaranteed to exist - // (MSYS/GNU coreutils may be absent) and the failure causes the tool call to finish immediately, - // which triggers a second model request before the test sends the explicit follow-up. That - // prematurely consumes the second mocked SSE response and leads to a third POST (panic: no response for 2). - // Powershell Start-Sleep is always available on Windows runners. On Unix we keep using `sleep`. - #[cfg(target_os = "windows")] - let shell_command = vec![ - "powershell".to_string(), - "-Command".to_string(), - "Start-Sleep -Seconds 10".to_string(), - ]; - #[cfg(not(target_os = "windows"))] - let shell_command = vec!["sleep".to_string(), "10".to_string()]; - - let tmp = TempDir::new()?; - // Temporary Codex home with config pointing at the mock server. - let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home)?; - let working_directory = tmp.path().join("workdir"); - std::fs::create_dir(&working_directory)?; - - // Create mock server with a single SSE response: the long sleep command - let server = create_mock_responses_server_sequence(vec![create_shell_command_sse_response( - shell_command.clone(), - Some(&working_directory), - Some(10_000), // 10 seconds timeout in ms - "call_sleep", - )?]) - .await; - create_config_toml(&codex_home, server.uri())?; - - // Start MCP server and initialize. - let mut mcp = McpProcess::new(&codex_home).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // 1) newConversation - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - cwd: Some(working_directory.to_string_lossy().into_owned()), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let new_conv_resp = to_response::(new_conv_resp)?; - let NewConversationResponse { - conversation_id, .. - } = new_conv_resp; - - // 2) addConversationListener - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let _add_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - - // 3) sendUserMessage (should trigger notifications; we only validate an OK response) - let send_user_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![codex_app_server_protocol::InputItem::Text { - text: "run first sleep command".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - let send_user_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)), - ) - .await??; - let SendUserMessageResponse {} = to_response::(send_user_resp)?; - - // Give the command a moment to start - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - - // 4) send interrupt request - let interrupt_id = mcp - .send_interrupt_conversation_request(InterruptConversationParams { conversation_id }) - .await?; - let interrupt_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(interrupt_id)), - ) - .await??; - let InterruptConversationResponse { abort_reason } = - to_response::(interrupt_resp)?; - assert_eq!(TurnAbortReason::Interrupted, abort_reason); - - Ok(()) -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -fn create_config_toml(codex_home: &Path, server_uri: String) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} diff --git a/codex-rs/app-server/tests/suite/list_resume.rs b/codex-rs/app-server/tests/suite/list_resume.rs deleted file mode 100644 index 97c8f41d11..0000000000 --- a/codex-rs/app-server/tests/suite/list_resume.rs +++ /dev/null @@ -1,440 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_fake_rollout; -use app_test_support::to_response; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::ListConversationsParams; -use codex_app_server_protocol::ListConversationsResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ResumeConversationParams; -use codex_app_server_protocol::ResumeConversationResponse; -use codex_app_server_protocol::ServerNotification; -use codex_app_server_protocol::SessionConfiguredNotification; -use codex_protocol::models::ContentItem; -use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::EventMsg; -use pretty_assertions::assert_eq; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_list_and_resume_conversations() -> Result<()> { - // Prepare a temporary CODEX_HOME with a few fake rollout files. - let codex_home = TempDir::new()?; - create_fake_rollout( - codex_home.path(), - "2025-01-02T12-00-00", - "2025-01-02T12:00:00Z", - "Hello A", - Some("openai"), - None, - )?; - create_fake_rollout( - codex_home.path(), - "2025-01-01T13-00-00", - "2025-01-01T13:00:00Z", - "Hello B", - Some("openai"), - None, - )?; - create_fake_rollout( - codex_home.path(), - "2025-01-01T12-00-00", - "2025-01-01T12:00:00Z", - "Hello C", - None, - None, - )?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // Request first page with size 2 - let req_id = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(2), - cursor: None, - model_providers: None, - }) - .await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(req_id)), - ) - .await??; - let ListConversationsResponse { items, next_cursor } = - to_response::(resp)?; - - assert_eq!(items.len(), 2); - // Newest first; preview text should match - assert_eq!(items[0].preview, "Hello A"); - assert_eq!(items[1].preview, "Hello B"); - assert_eq!(items[0].model_provider, "openai"); - assert_eq!(items[1].model_provider, "openai"); - assert!(items[0].path.is_absolute()); - assert!(next_cursor.is_some()); - - // Request the next page using the cursor - let req_id2 = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(2), - cursor: next_cursor, - model_providers: None, - }) - .await?; - let resp2: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(req_id2)), - ) - .await??; - let ListConversationsResponse { - items: items2, - next_cursor: next2, - .. - } = to_response::(resp2)?; - assert_eq!(items2.len(), 1); - assert_eq!(items2[0].preview, "Hello C"); - assert_eq!(items2[0].model_provider, "openai"); - assert_eq!(next2, None); - - // Add a conversation with an explicit non-OpenAI provider for filter tests. - create_fake_rollout( - codex_home.path(), - "2025-01-01T11-30-00", - "2025-01-01T11:30:00Z", - "Hello TP", - Some("test-provider"), - None, - )?; - - // Filtering by model provider should return only matching sessions. - let filter_req_id = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(10), - cursor: None, - model_providers: Some(vec!["test-provider".to_string()]), - }) - .await?; - let filter_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(filter_req_id)), - ) - .await??; - let ListConversationsResponse { - items: filtered_items, - next_cursor: filtered_next, - } = to_response::(filter_resp)?; - assert_eq!(filtered_items.len(), 1); - assert_eq!(filtered_next, None); - assert_eq!(filtered_items[0].preview, "Hello TP"); - assert_eq!(filtered_items[0].model_provider, "test-provider"); - - // Empty filter should include every session regardless of provider metadata. - let unfiltered_req_id = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(10), - cursor: None, - model_providers: Some(Vec::new()), - }) - .await?; - let unfiltered_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(unfiltered_req_id)), - ) - .await??; - let ListConversationsResponse { - items: unfiltered_items, - next_cursor: unfiltered_next, - } = to_response::(unfiltered_resp)?; - assert_eq!(unfiltered_items.len(), 4); - assert!(unfiltered_next.is_none()); - - let empty_req_id = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(10), - cursor: None, - model_providers: Some(vec!["other".to_string()]), - }) - .await?; - let empty_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(empty_req_id)), - ) - .await??; - let ListConversationsResponse { - items: empty_items, - next_cursor: empty_next, - } = to_response::(empty_resp)?; - assert!(empty_items.is_empty()); - assert!(empty_next.is_none()); - - let first_item = &items[0]; - - // Now resume one of the sessions from an explicit rollout path. - let resume_req_id = mcp - .send_resume_conversation_request(ResumeConversationParams { - path: Some(first_item.path.clone()), - conversation_id: None, - history: None, - overrides: Some(NewConversationParams { - model: Some("o3".to_string()), - ..Default::default() - }), - }) - .await?; - - // Expect a codex/event notification with msg.type == sessionConfigured - let notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("sessionConfigured"), - ) - .await??; - let session_configured: ServerNotification = notification.try_into()?; - let ServerNotification::SessionConfigured(SessionConfiguredNotification { - model, - rollout_path, - initial_messages: session_initial_messages, - .. - }) = session_configured - else { - unreachable!("expected sessionConfigured notification"); - }; - assert_eq!(model, "o3"); - assert_eq!(rollout_path, first_item.path.clone()); - let session_initial_messages = session_initial_messages - .expect("expected initial messages when resuming from rollout path"); - match session_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, first_item.preview.clone()); - } - other => panic!("unexpected initial messages from rollout resume: {other:#?}"), - } - - // Then the response for resumeConversation - let resume_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(resume_req_id)), - ) - .await??; - let ResumeConversationResponse { - conversation_id, - model: resume_model, - initial_messages: response_initial_messages, - .. - } = to_response::(resume_resp)?; - // conversation id should be a valid UUID - assert!(!conversation_id.to_string().is_empty()); - assert_eq!(resume_model, "o3"); - let response_initial_messages = - response_initial_messages.expect("expected initial messages in resume response"); - match response_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, first_item.preview.clone()); - } - other => panic!("unexpected initial messages in resume response: {other:#?}"), - } - - // Resuming with only a conversation id should locate the rollout automatically. - let resume_by_id_req_id = mcp - .send_resume_conversation_request(ResumeConversationParams { - path: None, - conversation_id: Some(first_item.conversation_id), - history: None, - overrides: Some(NewConversationParams { - model: Some("o3".to_string()), - ..Default::default() - }), - }) - .await?; - let notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("sessionConfigured"), - ) - .await??; - let session_configured: ServerNotification = notification.try_into()?; - let ServerNotification::SessionConfigured(SessionConfiguredNotification { - model, - rollout_path, - initial_messages: session_initial_messages, - .. - }) = session_configured - else { - unreachable!("expected sessionConfigured notification"); - }; - assert_eq!(model, "o3"); - assert_eq!(rollout_path, first_item.path.clone()); - let session_initial_messages = session_initial_messages - .expect("expected initial messages when resuming from conversation id"); - match session_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, first_item.preview.clone()); - } - other => panic!("unexpected initial messages from conversation id resume: {other:#?}"), - } - let resume_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(resume_by_id_req_id)), - ) - .await??; - let ResumeConversationResponse { - conversation_id: by_id_conversation_id, - model: by_id_model, - initial_messages: by_id_initial_messages, - .. - } = to_response::(resume_resp)?; - assert!(!by_id_conversation_id.to_string().is_empty()); - assert_eq!(by_id_model, "o3"); - let by_id_initial_messages = by_id_initial_messages - .expect("expected initial messages when resuming from conversation id response"); - match by_id_initial_messages.as_slice() { - [EventMsg::UserMessage(message)] => { - assert_eq!(message.message, first_item.preview.clone()); - } - other => { - panic!("unexpected initial messages in conversation id resume response: {other:#?}") - } - } - - // Resuming with explicit history should succeed even without a stored rollout. - let fork_history_text = "Hello from history"; - let history = vec![ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: fork_history_text.to_string(), - }], - end_turn: None, - phase: None, - }]; - let resume_with_history_req_id = mcp - .send_resume_conversation_request(ResumeConversationParams { - path: None, - conversation_id: None, - history: Some(history), - overrides: Some(NewConversationParams { - model: Some("o3".to_string()), - ..Default::default() - }), - }) - .await?; - let notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("sessionConfigured"), - ) - .await??; - let session_configured: ServerNotification = notification.try_into()?; - let ServerNotification::SessionConfigured(SessionConfiguredNotification { - model, - initial_messages: session_initial_messages, - .. - }) = session_configured - else { - unreachable!("expected sessionConfigured notification"); - }; - assert_eq!(model, "o3"); - assert!( - session_initial_messages.as_ref().is_none_or(Vec::is_empty), - "expected no initial messages when resuming from explicit history but got {session_initial_messages:#?}" - ); - let resume_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(resume_with_history_req_id)), - ) - .await??; - let ResumeConversationResponse { - conversation_id: history_conversation_id, - model: history_model, - initial_messages: history_initial_messages, - .. - } = to_response::(resume_resp)?; - assert!(!history_conversation_id.to_string().is_empty()); - assert_eq!(history_model, "o3"); - assert!( - history_initial_messages.as_ref().is_none_or(Vec::is_empty), - "expected no initial messages in resume response when history is provided but got {history_initial_messages:#?}" - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn list_conversations_fetches_through_filtered_pages() -> Result<()> { - let codex_home = TempDir::new()?; - - // Only the last 3 conversations match the provider filter; request 3 and - // ensure pagination keeps fetching past non-matching pages. - let cases = [ - ( - "2025-03-04T12-00-00", - "2025-03-04T12:00:00Z", - "skip_provider", - ), - ( - "2025-03-03T12-00-00", - "2025-03-03T12:00:00Z", - "skip_provider", - ), - ( - "2025-03-02T12-00-00", - "2025-03-02T12:00:00Z", - "target_provider", - ), - ( - "2025-03-01T12-00-00", - "2025-03-01T12:00:00Z", - "target_provider", - ), - ( - "2025-02-28T12-00-00", - "2025-02-28T12:00:00Z", - "target_provider", - ), - ]; - - for (ts_file, ts_rfc, provider) in cases { - create_fake_rollout( - codex_home.path(), - ts_file, - ts_rfc, - "Hello", - Some(provider), - None, - )?; - } - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let req_id = mcp - .send_list_conversations_request(ListConversationsParams { - page_size: Some(3), - cursor: None, - model_providers: Some(vec!["target_provider".to_string()]), - }) - .await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(req_id)), - ) - .await??; - let ListConversationsResponse { items, next_cursor } = - to_response::(resp)?; - - assert_eq!( - items.len(), - 3, - "should fetch across pages to satisfy the limit" - ); - assert!( - items - .iter() - .all(|item| item.model_provider == "target_provider") - ); - assert_eq!(next_cursor, None); - - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/login.rs b/codex-rs/app-server/tests/suite/login.rs deleted file mode 100644 index afc69787eb..0000000000 --- a/codex-rs/app-server/tests/suite/login.rs +++ /dev/null @@ -1,161 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::to_response; -use codex_app_server_protocol::GetAuthStatusParams; -use codex_app_server_protocol::GetAuthStatusResponse; -use codex_app_server_protocol::JSONRPCError; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginChatGptResponse; -use codex_app_server_protocol::LogoutChatGptResponse; -use codex_app_server_protocol::RequestId; -use codex_core::auth::AuthCredentialsStoreMode; -use codex_login::login_with_api_key; -use serial_test::serial; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -// Helper to create a config.toml; mirrors create_conversation.rs -fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "http://127.0.0.1:0/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"#, - ) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn logout_chatgpt_removes_auth() -> Result<()> { - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path())?; - login_with_api_key( - codex_home.path(), - "sk-test-key", - AuthCredentialsStoreMode::File, - )?; - assert!(codex_home.path().join("auth.json").exists()); - - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let id = mcp.send_logout_chat_gpt_request().await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(id)), - ) - .await??; - let _ok: LogoutChatGptResponse = to_response(resp)?; - - assert!( - !codex_home.path().join("auth.json").exists(), - "auth.json should be deleted" - ); - - // Verify status reflects signed-out state. - let status_id = mcp - .send_get_auth_status_request(GetAuthStatusParams { - include_token: Some(true), - refresh_token: Some(false), - }) - .await?; - let status_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(status_id)), - ) - .await??; - let status: GetAuthStatusResponse = to_response(status_resp)?; - assert_eq!(status.auth_method, None); - assert_eq!(status.auth_token, None); - Ok(()) -} - -fn create_config_toml_forced_login(codex_home: &Path, forced_method: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - let contents = format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" -forced_login_method = "{forced_method}" -"# - ); - std::fs::write(config_toml, contents) -} - -fn create_config_toml_forced_workspace( - codex_home: &Path, - workspace_id: &str, -) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - let contents = format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" -forced_chatgpt_workspace_id = "{workspace_id}" -"# - ); - std::fs::write(config_toml, contents) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn login_chatgpt_rejected_when_forced_api() -> Result<()> { - let codex_home = TempDir::new()?; - create_config_toml_forced_login(codex_home.path(), "api")?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_login_chat_gpt_request().await?; - let err: JSONRPCError = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_error_message(RequestId::Integer(request_id)), - ) - .await??; - - assert_eq!( - err.error.message, - "ChatGPT login is disabled. Use API key login instead." - ); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -// Serialize tests that launch the login server since it binds to a fixed port. -#[serial(login_port)] -async fn login_chatgpt_includes_forced_workspace_query_param() -> Result<()> { - let codex_home = TempDir::new()?; - create_config_toml_forced_workspace(codex_home.path(), "ws-forced")?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_login_chat_gpt_request().await?; - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let login: LoginChatGptResponse = to_response(resp)?; - assert!( - login.auth_url.contains("allowed_workspace_id=ws-forced"), - "auth URL should include forced workspace" - ); - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/mod.rs b/codex-rs/app-server/tests/suite/mod.rs index ae7e0cb438..c60f4a32a1 100644 --- a/codex-rs/app-server/tests/suite/mod.rs +++ b/codex-rs/app-server/tests/suite/mod.rs @@ -1,16 +1,4 @@ -mod archive_thread; mod auth; -mod codex_message_processor_flow; -mod config; -mod create_thread; -mod fork_thread; +mod conversation_summary; mod fuzzy_file_search; -mod interrupt; -mod list_resume; -mod login; -mod output_schema; -mod send_message; -mod set_default_model; -mod user_agent; -mod user_info; mod v2; diff --git a/codex-rs/app-server/tests/suite/output_schema.rs b/codex-rs/app-server/tests/suite/output_schema.rs deleted file mode 100644 index b182e4262e..0000000000 --- a/codex-rs/app-server/tests/suite/output_schema.rs +++ /dev/null @@ -1,372 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::to_response; -use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE; -use codex_app_server::INVALID_PARAMS_ERROR_CODE; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCError; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SendUserTurnParams; -use codex_app_server_protocol::SendUserTurnResponse; -use codex_protocol::config_types::ReasoningSummary; -use codex_protocol::openai_models::ReasoningEffort; -use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; -use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS; -use core_test_support::responses; -use core_test_support::skip_if_no_network; -use pretty_assertions::assert_eq; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test] -async fn send_user_turn_accepts_output_schema_v1() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let response_mock = responses::mount_sse_once(&server, body).await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::(new_conv_resp)?; - - let listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(listener_id)), - ) - .await??; - - let output_schema = serde_json::json!({ - "type": "object", - "properties": { - "answer": { "type": "string" } - }, - "required": ["answer"], - "additionalProperties": false - }); - - let send_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: "Hello".to_string(), - text_elements: Vec::new(), - }], - cwd: codex_home.path().to_path_buf(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - model: "mock-model".to_string(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: Some(output_schema.clone()), - }) - .await?; - let _send_turn_resp: SendUserTurnResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id)), - ) - .await??, - )?; - - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - let request = response_mock.single_request(); - let payload = request.body_json(); - let text = payload.get("text").expect("request missing text field"); - let format = text - .get("format") - .expect("request missing text.format field"); - assert_eq!( - format, - &serde_json::json!({ - "name": "codex_output_schema", - "type": "json_schema", - "strict": true, - "schema": output_schema, - }) - ); - - Ok(()) -} - -#[tokio::test] -async fn send_user_turn_rejects_oversized_input_v1() -> Result<()> { - let server = responses::start_mock_server().await; - let body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let _response_mock = responses::mount_sse_once(&server, body).await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::(new_conv_resp)?; - - let listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(listener_id)), - ) - .await??; - - let oversized_input = "x".repeat(MAX_USER_INPUT_TEXT_CHARS + 1); - let send_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: oversized_input.clone(), - text_elements: Vec::new(), - }], - cwd: codex_home.path().to_path_buf(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, - model: "mock-model".to_string(), - effort: Some(ReasoningEffort::Low), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: None, - }) - .await?; - - let err: JSONRPCError = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_error_message(RequestId::Integer(send_turn_id)), - ) - .await??; - - assert_eq!(err.error.code, INVALID_PARAMS_ERROR_CODE); - assert_eq!( - err.error.message, - format!("Input exceeds the maximum length of {MAX_USER_INPUT_TEXT_CHARS} characters.") - ); - let data = err.error.data.expect("expected structured error data"); - assert_eq!(data["input_error_code"], INPUT_TOO_LARGE_ERROR_CODE); - assert_eq!(data["max_chars"], MAX_USER_INPUT_TEXT_CHARS); - assert_eq!(data["actual_chars"], oversized_input.chars().count()); - - Ok(()) -} - -#[tokio::test] -async fn send_user_turn_output_schema_is_per_turn_v1() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let body1 = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let response_mock1 = responses::mount_sse_once(&server, body1).await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::(new_conv_resp)?; - - let listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(listener_id)), - ) - .await??; - - let output_schema = serde_json::json!({ - "type": "object", - "properties": { - "answer": { "type": "string" } - }, - "required": ["answer"], - "additionalProperties": false - }); - - let send_turn_id = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: "Hello".to_string(), - text_elements: Vec::new(), - }], - cwd: codex_home.path().to_path_buf(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - model: "mock-model".to_string(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: Some(output_schema.clone()), - }) - .await?; - let _send_turn_resp: SendUserTurnResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id)), - ) - .await??, - )?; - - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - let payload1 = response_mock1.single_request().body_json(); - assert_eq!( - payload1.pointer("/text/format"), - Some(&serde_json::json!({ - "name": "codex_output_schema", - "type": "json_schema", - "strict": true, - "schema": output_schema, - })) - ); - - let body2 = responses::sse(vec![ - responses::ev_response_created("resp-2"), - responses::ev_assistant_message("msg-2", "Done"), - responses::ev_completed("resp-2"), - ]); - let response_mock2 = responses::mount_sse_once(&server, body2).await; - - let send_turn_id_2 = mcp - .send_send_user_turn_request(SendUserTurnParams { - conversation_id, - items: vec![InputItem::Text { - text: "Hello again".to_string(), - text_elements: Vec::new(), - }], - cwd: codex_home.path().to_path_buf(), - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::new_read_only_policy(), - model: "mock-model".to_string(), - effort: Some(ReasoningEffort::Medium), - summary: ReasoningSummary::Auto, - service_tier: None, - output_schema: None, - }) - .await?; - let _send_turn_resp_2: SendUserTurnResponse = to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id_2)), - ) - .await??, - )?; - - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - - let payload2 = response_mock2.single_request().body_json(); - assert_eq!(payload2.pointer("/text/format"), None); - - Ok(()) -} - -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "read-only" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} diff --git a/codex-rs/app-server/tests/suite/send_message.rs b/codex-rs/app-server/tests/suite/send_message.rs deleted file mode 100644 index 725f327b4c..0000000000 --- a/codex-rs/app-server/tests/suite/send_message.rs +++ /dev/null @@ -1,645 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::create_fake_rollout; -use app_test_support::rollout_path; -use app_test_support::to_response; -use codex_app_server::INPUT_TOO_LARGE_ERROR_CODE; -use codex_app_server::INVALID_PARAMS_ERROR_CODE; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCError; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::ResumeConversationParams; -use codex_app_server_protocol::ResumeConversationResponse; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_execpolicy::Policy; -use codex_protocol::ThreadId; -use codex_protocol::config_types::ReasoningSummary; -use codex_protocol::models::ContentItem; -use codex_protocol::models::DeveloperInstructions; -use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::RawResponseItemEvent; -use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::RolloutLine; -use codex_protocol::protocol::SandboxPolicy; -use codex_protocol::protocol::TurnContextItem; -use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS; -use core_test_support::responses; -use pretty_assertions::assert_eq; -use std::io::Write; -use std::path::Path; -use std::path::PathBuf; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test] -async fn test_send_message_success() -> Result<()> { - // Spin up a mock responses server that immediately ends the Codex turn. - // Two Codex turns hit the mock model (session start + send-user-message). Provide two SSE responses. - let server = responses::start_mock_server().await; - let body1 = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let body2 = responses::sse(vec![ - responses::ev_response_created("resp-2"), - responses::ev_assistant_message("msg-2", "Done"), - responses::ev_completed("resp-2"), - ]); - let _response_mock1 = responses::mount_sse_once(&server, body1).await; - let _response_mock2 = responses::mount_sse_once(&server, body2).await; - - // Create a temporary Codex home with config pointing at the mock server. - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - // Start MCP server process and initialize. - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - // Start a conversation using the new wire API. - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::<_>(new_conv_resp)?; - - // 2) addConversationListener - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let add_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::<_>(add_listener_resp)?; - - // Now exercise sendUserMessage twice. - send_message("Hello", conversation_id, &mut mcp).await?; - send_message("Hello again", conversation_id, &mut mcp).await?; - Ok(()) -} - -#[expect(clippy::expect_used)] -async fn send_message( - message: &str, - conversation_id: ThreadId, - mcp: &mut McpProcess, -) -> Result<()> { - // Now exercise sendUserMessage. - let send_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![InputItem::Text { - text: message.to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - - let response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_id)), - ) - .await??; - - let _ok: SendUserMessageResponse = to_response::(response)?; - - // Verify the task_finished notification is received. - // Note this also ensures that the final request to the server was made. - let task_finished_notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await??; - let serde_json::Value::Object(map) = task_finished_notification - .params - .expect("notification should have params") - else { - panic!("task_finished_notification should have params"); - }; - assert_eq!( - map.get("conversationId") - .expect("should have conversationId"), - &serde_json::Value::String(conversation_id.to_string()) - ); - - let raw_attempt = tokio::time::timeout( - std::time::Duration::from_millis(200), - mcp.read_stream_until_notification_message("codex/event/raw_response_item"), - ) - .await; - assert!( - raw_attempt.is_err(), - "unexpected raw item notification when not opted in" - ); - Ok(()) -} - -#[tokio::test] -async fn test_send_message_raw_notifications_opt_in() -> Result<()> { - let server = responses::start_mock_server().await; - let body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let _response_mock = responses::mount_sse_once(&server, body).await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - developer_instructions: Some("Use the test harness tools.".to_string()), - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::<_>(new_conv_resp)?; - - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: true, - }) - .await?; - let add_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::<_>(add_listener_resp)?; - - let send_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![InputItem::Text { - text: "Hello".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - - let developer = read_raw_response_item(&mut mcp, conversation_id).await; - assert_permissions_message(&developer); - assert_developer_message(&developer, "Use the test harness tools."); - - let contextual_user = read_raw_response_item(&mut mcp, conversation_id).await; - assert_instructions_message(&contextual_user); - assert_environment_message(&contextual_user); - - let response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(send_id)), - ) - .await??; - let _ok: SendUserMessageResponse = to_response::(response)?; - - let user_message = read_raw_response_item(&mut mcp, conversation_id).await; - assert_user_message(&user_message, "Hello"); - - let assistant_message = read_raw_response_item(&mut mcp, conversation_id).await; - assert_assistant_message(&assistant_message, "Done"); - - let _ = tokio::time::timeout( - std::time::Duration::from_millis(250), - mcp.read_stream_until_notification_message("codex/event/task_complete"), - ) - .await; - - Ok(()) -} - -#[tokio::test] -async fn test_send_message_session_not_found() -> Result<()> { - // Start MCP without creating a Codex session - let codex_home = TempDir::new()?; - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let unknown = ThreadId::new(); - let req_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id: unknown, - items: vec![InputItem::Text { - text: "ping".to_string(), - text_elements: Vec::new(), - }], - }) - .await?; - - // Expect an error response for unknown conversation. - let err = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_error_message(RequestId::Integer(req_id)), - ) - .await??; - assert_eq!(err.id, RequestId::Integer(req_id)); - Ok(()) -} - -#[tokio::test] -async fn test_send_message_rejects_oversized_input() -> Result<()> { - let server = responses::start_mock_server().await; - let body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]); - let _response_mock = responses::mount_sse_once(&server, body).await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let new_conv_id = mcp - .send_new_conversation_request(NewConversationParams { - ..Default::default() - }) - .await?; - let new_conv_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), - ) - .await??; - let NewConversationResponse { - conversation_id, .. - } = to_response::<_>(new_conv_resp)?; - - let oversized_input = "x".repeat(MAX_USER_INPUT_TEXT_CHARS + 1); - let req_id = mcp - .send_send_user_message_request(SendUserMessageParams { - conversation_id, - items: vec![InputItem::Text { - text: oversized_input.clone(), - text_elements: Vec::new(), - }], - }) - .await?; - - let err: JSONRPCError = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_error_message(RequestId::Integer(req_id)), - ) - .await??; - - assert_eq!(err.error.code, INVALID_PARAMS_ERROR_CODE); - assert_eq!( - err.error.message, - format!("Input exceeds the maximum length of {MAX_USER_INPUT_TEXT_CHARS} characters.") - ); - let data = err.error.data.expect("expected structured error data"); - assert_eq!(data["input_error_code"], INPUT_TOO_LARGE_ERROR_CODE); - assert_eq!(data["max_chars"], MAX_USER_INPUT_TEXT_CHARS); - assert_eq!(data["actual_chars"], oversized_input.chars().count()); - - Ok(()) -} - -#[tokio::test] -async fn resume_with_model_mismatch_appends_model_switch_once() -> Result<()> { - let server = responses::start_mock_server().await; - let response_mock = responses::mount_sse_sequence( - &server, - vec![ - responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_assistant_message("msg-1", "Done"), - responses::ev_completed("resp-1"), - ]), - responses::sse(vec![ - responses::ev_response_created("resp-2"), - responses::ev_assistant_message("msg-2", "Done again"), - responses::ev_completed("resp-2"), - ]), - ], - ) - .await; - - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path(), &server.uri())?; - - let filename_ts = "2025-01-02T12-00-00"; - let meta_rfc3339 = "2025-01-02T12:00:00Z"; - let preview = "Resume me"; - let conversation_id = create_fake_rollout( - codex_home.path(), - filename_ts, - meta_rfc3339, - preview, - Some("mock_provider"), - None, - )?; - let rollout_path = rollout_path(codex_home.path(), filename_ts, &conversation_id); - append_rollout_turn_context(&rollout_path, meta_rfc3339, "previous-model")?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let resume_id = mcp - .send_resume_conversation_request(ResumeConversationParams { - path: Some(rollout_path.clone()), - conversation_id: None, - history: None, - overrides: Some(NewConversationParams { - model: Some("gpt-5.2-codex".to_string()), - ..Default::default() - }), - }) - .await?; - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("sessionConfigured"), - ) - .await??; - let resume_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), - ) - .await??; - let ResumeConversationResponse { - conversation_id, .. - } = to_response::(resume_resp)?; - - let add_listener_id = mcp - .send_add_conversation_listener_request(AddConversationListenerParams { - conversation_id, - experimental_raw_events: false, - }) - .await?; - let add_listener_resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await??; - let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::<_>(add_listener_resp)?; - - send_message("hello after resume", conversation_id, &mut mcp).await?; - send_message("second turn", conversation_id, &mut mcp).await?; - - let requests = response_mock.requests(); - assert_eq!(requests.len(), 2, "expected two model requests"); - - let first_developer_texts = requests[0].message_input_texts("developer"); - let first_model_switch_count = first_developer_texts - .iter() - .filter(|text| text.contains("")) - .count(); - assert!( - first_model_switch_count >= 1, - "expected model switch message on first post-resume turn, got {first_developer_texts:?}" - ); - - let second_developer_texts = requests[1].message_input_texts("developer"); - let second_model_switch_count = second_developer_texts - .iter() - .filter(|text| text.contains("")) - .count(); - assert_eq!( - second_model_switch_count, 1, - "did not expect duplicate model switch message on second post-resume turn, got {second_developer_texts:?}" - ); - - Ok(()) -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - format!( - r#" -model = "mock-model" -approval_policy = "never" -sandbox_mode = "danger-full-access" - -model_provider = "mock_provider" - -[model_providers.mock_provider] -name = "Mock provider for test" -base_url = "{server_uri}/v1" -wire_api = "responses" -request_max_retries = 0 -stream_max_retries = 0 -"# - ), - ) -} - -#[expect(clippy::expect_used)] -async fn read_raw_response_item(mcp: &mut McpProcess, conversation_id: ThreadId) -> ResponseItem { - // TODO: Switch to rawResponseItem/completed once we migrate to app server v2 in codex web. - loop { - let raw_notification: JSONRPCNotification = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_notification_message("codex/event/raw_response_item"), - ) - .await - .expect("codex/event/raw_response_item notification timeout") - .expect("codex/event/raw_response_item notification resp"); - - let serde_json::Value::Object(params) = raw_notification - .params - .expect("codex/event/raw_response_item should have params") - else { - panic!("codex/event/raw_response_item should have params"); - }; - - let conversation_id_value = params - .get("conversationId") - .and_then(|value| value.as_str()) - .expect("raw response item should include conversationId"); - - assert_eq!( - conversation_id_value, - conversation_id.to_string(), - "raw response item conversation mismatch" - ); - - let msg_value = params - .get("msg") - .cloned() - .expect("raw response item should include msg payload"); - - // Ghost snapshots are produced concurrently and may arrive before the model reply. - let event: RawResponseItemEvent = - serde_json::from_value(msg_value).expect("deserialize raw response item"); - if !matches!(event.item, ResponseItem::GhostSnapshot { .. }) { - return event.item; - } - } -} - -fn assert_instructions_message(item: &ResponseItem) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "user"); - let texts = content_texts(content); - let is_instructions = texts - .iter() - .any(|text| text.starts_with("# AGENTS.md instructions for ")); - assert!( - is_instructions, - "expected instructions message, got {texts:?}" - ); - } - other => panic!("expected instructions message, got {other:?}"), - } -} - -fn assert_permissions_message(item: &ResponseItem) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "developer"); - let texts = content_texts(content); - let expected = DeveloperInstructions::from_policy( - &SandboxPolicy::DangerFullAccess, - AskForApproval::Never, - &Policy::empty(), - &PathBuf::from("/tmp"), - false, - ) - .into_text(); - assert!( - texts.iter().any(|text| *text == expected), - "expected permissions developer message, got {texts:?}" - ); - } - other => panic!("expected permissions message, got {other:?}"), - } -} - -fn assert_developer_message(item: &ResponseItem, expected_text: &str) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "developer"); - let texts = content_texts(content); - assert!( - texts.contains(&expected_text), - "expected developer instructions message, got {texts:?}" - ); - } - other => panic!("expected developer instructions message, got {other:?}"), - } -} - -fn assert_environment_message(item: &ResponseItem) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "user"); - let texts = content_texts(content); - assert!( - texts - .iter() - .any(|text| text.contains("")), - "expected environment context message, got {texts:?}" - ); - } - other => panic!("expected environment message, got {other:?}"), - } -} - -fn assert_user_message(item: &ResponseItem, expected_text: &str) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "user"); - let texts = content_texts(content); - assert_eq!(texts, vec![expected_text]); - } - other => panic!("expected user message, got {other:?}"), - } -} - -fn assert_assistant_message(item: &ResponseItem, expected_text: &str) { - match item { - ResponseItem::Message { role, content, .. } => { - assert_eq!(role, "assistant"); - let texts = content_texts(content); - assert_eq!(texts, vec![expected_text]); - } - other => panic!("expected assistant message, got {other:?}"), - } -} - -fn content_texts(content: &[ContentItem]) -> Vec<&str> { - content - .iter() - .filter_map(|item| match item { - ContentItem::InputText { text, .. } | ContentItem::OutputText { text } => { - Some(text.as_str()) - } - _ => None, - }) - .collect() -} - -fn append_rollout_turn_context(path: &Path, timestamp: &str, model: &str) -> std::io::Result<()> { - let line = RolloutLine { - timestamp: timestamp.to_string(), - item: RolloutItem::TurnContext(TurnContextItem { - turn_id: None, - cwd: PathBuf::from("/"), - current_date: None, - timezone: None, - approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, - network: None, - model: model.to_string(), - personality: None, - collaboration_mode: None, - realtime_active: Some(false), - effort: None, - summary: ReasoningSummary::Auto, - user_instructions: None, - developer_instructions: None, - final_output_json_schema: None, - truncation_policy: None, - }), - }; - let serialized = serde_json::to_string(&line).map_err(std::io::Error::other)?; - std::fs::OpenOptions::new() - .append(true) - .open(path)? - .write_all(format!("{serialized}\n").as_bytes()) -} diff --git a/codex-rs/app-server/tests/suite/set_default_model.rs b/codex-rs/app-server/tests/suite/set_default_model.rs deleted file mode 100644 index b56c54dbd9..0000000000 --- a/codex-rs/app-server/tests/suite/set_default_model.rs +++ /dev/null @@ -1,64 +0,0 @@ -use anyhow::Result; -use app_test_support::McpProcess; -use app_test_support::to_response; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SetDefaultModelParams; -use codex_app_server_protocol::SetDefaultModelResponse; -use codex_core::config::ConfigToml; -use pretty_assertions::assert_eq; -use std::path::Path; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn set_default_model_persists_overrides() -> Result<()> { - let codex_home = TempDir::new()?; - create_config_toml(codex_home.path())?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let params = SetDefaultModelParams { - model: Some("gpt-4.1".to_string()), - reasoning_effort: None, - }; - - let request_id = mcp.send_set_default_model_request(params).await?; - - let resp: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let _: SetDefaultModelResponse = to_response(resp)?; - - let config_path = codex_home.path().join("config.toml"); - let config_contents = tokio::fs::read_to_string(&config_path).await?; - let config_toml: ConfigToml = toml::from_str(&config_contents)?; - - assert_eq!( - ConfigToml { - model: Some("gpt-4.1".to_string()), - model_reasoning_effort: None, - ..Default::default() - }, - config_toml, - ); - Ok(()) -} - -// Helper to create a config.toml; mirrors create_conversation.rs -fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { - let config_toml = codex_home.join("config.toml"); - std::fs::write( - config_toml, - r#" -model = "gpt-5.1-codex-max" -model_reasoning_effort = "medium" -"#, - ) -} diff --git a/codex-rs/app-server/tests/suite/user_agent.rs b/codex-rs/app-server/tests/suite/user_agent.rs deleted file mode 100644 index 9178a3ef58..0000000000 --- a/codex-rs/app-server/tests/suite/user_agent.rs +++ /dev/null @@ -1,43 +0,0 @@ -use anyhow::Result; -use app_test_support::DEFAULT_CLIENT_NAME; -use app_test_support::McpProcess; -use app_test_support::to_response; -use codex_app_server_protocol::GetUserAgentResponse; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; -use pretty_assertions::assert_eq; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_user_agent_returns_current_codex_user_agent() -> Result<()> { - let codex_home = TempDir::new()?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_get_user_agent_request().await?; - let response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let os_info = os_info::get(); - let originator = DEFAULT_CLIENT_NAME; - let os_type = os_info.os_type(); - let os_version = os_info.version(); - let architecture = os_info.architecture().unwrap_or("unknown"); - let terminal_ua = codex_core::terminal::user_agent(); - let user_agent = format!( - "{originator}/0.0.0 ({os_type} {os_version}; {architecture}) {terminal_ua} ({DEFAULT_CLIENT_NAME}; 0.1.0)" - ); - - let received: GetUserAgentResponse = to_response(response)?; - let expected = GetUserAgentResponse { user_agent }; - - assert_eq!(received, expected); - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/user_info.rs b/codex-rs/app-server/tests/suite/user_info.rs deleted file mode 100644 index 6a44f1a3ab..0000000000 --- a/codex-rs/app-server/tests/suite/user_info.rs +++ /dev/null @@ -1,46 +0,0 @@ -use anyhow::Result; -use app_test_support::ChatGptAuthFixture; -use app_test_support::McpProcess; -use app_test_support::to_response; -use app_test_support::write_chatgpt_auth; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::UserInfoResponse; -use codex_core::auth::AuthCredentialsStoreMode; -use pretty_assertions::assert_eq; -use std::time::Duration; -use tempfile::TempDir; -use tokio::time::timeout; - -const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10); - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn user_info_returns_email_from_auth_json() -> Result<()> { - let codex_home = TempDir::new()?; - - write_chatgpt_auth( - codex_home.path(), - ChatGptAuthFixture::new("access") - .refresh_token("refresh") - .email("user@example.com"), - AuthCredentialsStoreMode::File, - )?; - - let mut mcp = McpProcess::new(codex_home.path()).await?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - - let request_id = mcp.send_user_info_request().await?; - let response: JSONRPCResponse = timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(request_id)), - ) - .await??; - - let received: UserInfoResponse = to_response(response)?; - let expected = UserInfoResponse { - alleged_user_email: Some("user@example.com".to_string()), - }; - - assert_eq!(received, expected); - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/v2/rate_limits.rs b/codex-rs/app-server/tests/suite/v2/rate_limits.rs index 6f66d7bff7..51048202f6 100644 --- a/codex-rs/app-server/tests/suite/v2/rate_limits.rs +++ b/codex-rs/app-server/tests/suite/v2/rate_limits.rs @@ -6,7 +6,7 @@ use app_test_support::write_chatgpt_auth; use codex_app_server_protocol::GetAccountRateLimitsResponse; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginApiKeyParams; +use codex_app_server_protocol::LoginAccountResponse; use codex_app_server_protocol::RateLimitSnapshot; use codex_app_server_protocol::RateLimitWindow; use codex_app_server_protocol::RequestId; @@ -221,17 +221,14 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { } async fn login_with_api_key(mcp: &mut McpProcess, api_key: &str) -> Result<()> { - let request_id = mcp - .send_login_api_key_request(LoginApiKeyParams { - api_key: api_key.to_string(), - }) - .await?; - - timeout( + let request_id = mcp.send_login_account_api_key_request(api_key).await?; + let response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) .await??; + let login: LoginAccountResponse = to_response(response)?; + assert_eq!(login, LoginAccountResponse::ApiKey {}); Ok(()) } diff --git a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs b/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs index 139e8b378a..71150d7126 100644 --- a/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs +++ b/codex-rs/app-server/tests/suite/v2/realtime_conversation.rs @@ -5,7 +5,7 @@ use app_test_support::create_mock_responses_server_sequence_unchecked; use app_test_support::to_response; use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginApiKeyParams; +use codex_app_server_protocol::LoginAccountResponse; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ThreadRealtimeAppendAudioParams; use codex_app_server_protocol::ThreadRealtimeAppendAudioResponse; @@ -350,17 +350,14 @@ async fn read_notification(mcp: &mut McpProcess, method: &s } async fn login_with_api_key(mcp: &mut McpProcess, api_key: &str) -> Result<()> { - let request_id = mcp - .send_login_api_key_request(LoginApiKeyParams { - api_key: api_key.to_string(), - }) - .await?; - - timeout( + let request_id = mcp.send_login_account_api_key_request(api_key).await?; + let response: JSONRPCResponse = timeout( DEFAULT_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) .await??; + let login: LoginAccountResponse = to_response(response)?; + assert_eq!(login, LoginAccountResponse::ApiKey {}); Ok(()) } diff --git a/codex-rs/docs/codex_mcp_interface.md b/codex-rs/docs/codex_mcp_interface.md index 763f2602ad..b4e3adfe53 100644 --- a/codex-rs/docs/codex_mcp_interface.md +++ b/codex-rs/docs/codex_mcp_interface.md @@ -1,38 +1,36 @@ # Codex MCP Server Interface [experimental] -This document describes Codex’s experimental MCP server interface: a JSON‑RPC API that runs over the Model Context Protocol (MCP) transport to control a local Codex engine. +This document describes Codex's experimental MCP server interface: a JSON-RPC API that runs over the Model Context Protocol (MCP) transport to control a local Codex engine. - Status: experimental and subject to change without notice - Server binary: `codex mcp-server` (or `codex-mcp-server`) -- Transport: standard MCP over stdio (JSON‑RPC 2.0, line‑delimited) +- Transport: standard MCP over stdio (JSON-RPC 2.0, line-delimited) ## Overview -Codex exposes a small set of MCP‑compatible methods to create and manage conversations, send user input, receive live events, and handle approval prompts. The types are defined in `protocol/src/mcp_protocol.rs` and re‑used by the MCP server implementation in `mcp-server/`. +Codex exposes MCP-compatible methods to manage threads, turns, accounts, config, and approvals. The types live in `app-server-protocol/src/protocol/{common,v1,v2}.rs` and are consumed by the app server implementation in `app-server/`. At a glance: -- Conversations - - `newConversation` → start a Codex session - - `sendUserMessage` / `sendUserTurn` → send user input into a conversation - - `interruptConversation` → stop the current turn - - `listConversations`, `resumeConversation`, `archiveConversation` -- Configuration and info - - `getUserSavedConfig`, `setDefaultModel`, `getUserAgent`, `userInfo` - - `model/list` → enumerate available models and reasoning options - - `collaborationMode/list` → enumerate collaboration mode presets (experimental) -- Auth +- Primary v2 RPCs + - `thread/start`, `thread/resume`, `thread/fork`, `thread/read`, `thread/list` + - `turn/start`, `turn/steer`, `turn/interrupt` - `account/read`, `account/login/start`, `account/login/cancel`, `account/logout`, `account/rateLimits/read` - - notifications: `account/login/completed`, `account/updated`, `account/rateLimits/updated` -- Utilities - - `gitDiffToRemote`, `execOneOffCommand` -- Approvals (server → client requests) + - `config/read`, `config/value/write`, `config/batchWrite` + - `model/list`, `app/list`, `collaborationMode/list` +- Remaining v1 compatibility RPCs + - `getConversationSummary` + - `getAuthStatus` + - `gitDiffToRemote` + - `fuzzyFileSearch`, `fuzzyFileSearch/sessionStart`, `fuzzyFileSearch/sessionUpdate`, `fuzzyFileSearch/sessionStop` +- Notifications + - v2 typed notifications such as `thread/started`, `turn/completed`, `account/login/completed` + - `codex/event/*` stream notifications for live agent events + - `fuzzyFileSearch/sessionUpdated`, `fuzzyFileSearch/sessionCompleted` +- Approvals (server -> client requests) - `applyPatchApproval`, `execCommandApproval` -- Notifications (server → client) - - `loginChatGptComplete`, `authStatusChange` - - `codex/event` stream with agent events -See code for full type definitions and exact shapes: `protocol/src/mcp_protocol.rs`. +See code for full type definitions and exact shapes: `app-server-protocol/src/protocol/{common,v1,v2}.rs`. ## Starting the server @@ -50,73 +48,45 @@ npx @modelcontextprotocol/inspector codex mcp-server Use the separate `codex mcp` subcommand to manage configured MCP server launchers in `config.toml`. -## Conversations +## Threads and turns -Start a new session with optional overrides: +Use the v2 thread and turn APIs for all new integrations. `thread/start` creates a thread, `turn/start` submits user input, `turn/interrupt` stops an in-flight turn, and `thread/list` / `thread/read` expose persisted history. -Request `newConversation` params (subset): +`getConversationSummary` remains as a compatibility helper for clients that still need a summary lookup by `conversationId` or `rolloutPath`. -- `model`: string model id (e.g. "o3", "gpt-5.1", "gpt-5.1-codex") -- `profile`: optional named profile -- `cwd`: optional working directory -- `approvalPolicy`: `untrusted` | `on-request` | `on-failure` (deprecated) | `never` -- `sandbox`: `read-only` | `workspace-write` | `external-sandbox` (honors `networkAccess` restricted/enabled) | `danger-full-access` -- `config`: map of additional config overrides -- `baseInstructions`: optional instruction override -- `compactPrompt`: optional replacement for the default compaction prompt -- `includePlanTool` / `includeApplyPatchTool`: booleans - -Response: `{ conversationId, model, reasoningEffort?, rolloutPath }` - -Send input to the active turn: - -- `sendUserMessage` → enqueue items to the conversation -- `sendUserTurn` → structured turn with explicit `cwd`, `approvalPolicy`, `sandboxPolicy`, `model`, optional `effort`, `summary`, optional `personality`, and optional `outputSchema` (JSON Schema for the final assistant message) - -Valid `personality` values are `friendly`, `pragmatic`, and `none`. When `none` is selected, the personality placeholder is replaced with an empty string. - -For v2 threads, `turn/start` also accepts `outputSchema` to constrain the final assistant message for that turn. - -Interrupt a running turn: `interruptConversation`. - -List/resume/archive: `listConversations`, `resumeConversation`, `archiveConversation`. - -For v2 threads, use `thread/list` with filters such as `archived: true`, `cwd: "/path"`, or -`searchTerm: "needle"` to -narrow results, and `thread/unarchive` to restore archived rollouts to the active sessions -directory (it returns the restored thread summary). +For complete request and response shapes, see the app-server README and the protocol definitions in `app-server-protocol/src/protocol/v2.rs`. ## Models Fetch the catalog of models available in the current Codex build with `model/list`. The request accepts optional pagination inputs: -- `limit` – number of models to return (defaults to a server-selected value) -- `cursor` – opaque string from the previous response’s `nextCursor` +- `limit` - number of models to return (defaults to a server-selected value) +- `cursor` - opaque string from the previous response's `nextCursor` Each response yields: -- `data` – ordered list of models. A model includes: +- `data` - ordered list of models. A model includes: - `id`, `model`, `displayName`, `description` - - `supportedReasoningEfforts` – array of objects with: - - `reasoningEffort` – one of `none|minimal|low|medium|high|xhigh` - - `description` – human-friendly label for the effort - - `defaultReasoningEffort` – suggested effort for the UI - - `inputModalities` – accepted input types for the model - - `supportsPersonality` – whether the model supports personality-specific instructions - - `isDefault` – whether the model is recommended for most users - - `upgrade` – optional recommended upgrade model id - - `upgradeInfo` – optional upgrade metadata object with: - - `model` – recommended upgrade model id - - `upgradeCopy` – optional display copy for the upgrade recommendation - - `modelLink` – optional link for the upgrade recommendation - - `migrationMarkdown` – optional markdown shown when presenting the upgrade -- `nextCursor` – pass into the next request to continue paging (optional) + - `supportedReasoningEfforts` - array of objects with: + - `reasoningEffort` - one of `none|minimal|low|medium|high|xhigh` + - `description` - human-friendly label for the effort + - `defaultReasoningEffort` - suggested effort for the UI + - `inputModalities` - accepted input types for the model + - `supportsPersonality` - whether the model supports personality-specific instructions + - `isDefault` - whether the model is recommended for most users + - `upgrade` - optional recommended upgrade model id + - `upgradeInfo` - optional upgrade metadata object with: + - `model` - recommended upgrade model id + - `upgradeCopy` - optional display copy for the upgrade recommendation + - `modelLink` - optional link for the upgrade recommendation + - `migrationMarkdown` - optional markdown shown when presenting the upgrade +- `nextCursor` - pass into the next request to continue paging (optional) ## Collaboration modes (experimental) Fetch the built-in collaboration mode presets with `collaborationMode/list`. This endpoint does not accept pagination and returns the full list in one response: -- `data` – ordered list of collaboration mode masks (partial settings to apply on top of the base mode) +- `data` - ordered list of collaboration mode masks (partial settings to apply on top of the base mode) - For tri-state fields like `reasoning_effort` and `developer_instructions`, omit the field to keep the current value, set it to `null` to clear it, or set a concrete value to update it. When sending `turn/start` with `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". @@ -125,16 +95,14 @@ When sending `turn/start` with `collaborationMode`, `settings.developer_instruct While a conversation runs, the server sends notifications: -- `codex/event` with the serialized Codex event payload. The shape matches `core/src/protocol.rs`’s `Event` and `EventMsg` types. Some notifications include a `_meta.requestId` to correlate with the originating request. -- Auth notifications via method names `loginChatGptComplete` and `authStatusChange`. +- `codex/event` with the serialized Codex event payload. The shape matches `core/src/protocol.rs`'s `Event` and `EventMsg` types. Some notifications include a `_meta.requestId` to correlate with the originating request. +- `fuzzyFileSearch/sessionUpdated` and `fuzzyFileSearch/sessionCompleted` for the legacy fuzzy search flow. Clients should render events and, when present, surface approval requests (see next section). ## Tool responses -The `codex` and `codex-reply` tools return standard MCP `CallToolResult` payloads. For -compatibility with MCP clients that prefer `structuredContent`, Codex mirrors the -content blocks inside `structuredContent` alongside the `threadId`. +The `codex` and `codex-reply` tools return standard MCP `CallToolResult` payloads. For compatibility with MCP clients that prefer `structuredContent`, Codex mirrors the content blocks inside `structuredContent` alongside the `threadId`. Example: @@ -148,9 +116,9 @@ Example: } ``` -## Approvals (server → client) +## Approvals (server -> client) -When Codex needs approval to apply changes or run commands, the server issues JSON‑RPC requests to the client: +When Codex needs approval to apply changes or run commands, the server issues JSON-RPC requests to the client: - `applyPatchApproval { conversationId, callId, fileChanges, reason?, grantRoot? }` - `execCommandApproval { conversationId, callId, approvalId?, command, cwd, reason? }` @@ -159,28 +127,17 @@ The client must reply with `{ decision: "allow" | "deny" }` for each request. ## Auth helpers -For the complete request/response shapes and flow examples, see the [“Auth endpoints (v2)” section in the app‑server README](../app-server/README.md#auth-endpoints-v2). +For the complete request/response shapes and flow examples, see the [Auth endpoints (v2) section in the app-server README](../app-server/README.md#auth-endpoints-v2). -## Example: start and send a message +## Legacy compatibility methods -```json -{ "jsonrpc": "2.0", "id": 1, "method": "newConversation", "params": { "model": "gpt-5.1", "approvalPolicy": "on-request" } } -``` +The server still accepts a narrow v1 compatibility surface for existing app clients: -Server responds: - -```json -{ "jsonrpc": "2.0", "id": 1, "result": { "conversationId": "c7b0…", "model": "gpt-5.1", "rolloutPath": "/path/to/rollout.jsonl" } } -``` - -Then send input: - -```json -{ "jsonrpc": "2.0", "id": 2, "method": "sendUserMessage", "params": { "conversationId": "c7b0…", "items": [{ "type": "text", "text": "Hello Codex" }] } } -``` - -While processing, the server emits `codex/event` notifications containing agent output, approvals, and status updates. +- `getConversationSummary` +- `getAuthStatus` +- `gitDiffToRemote` +- `fuzzyFileSearch`, `fuzzyFileSearch/sessionStart`, `fuzzyFileSearch/sessionUpdate`, `fuzzyFileSearch/sessionStop` ## Compatibility and stability -This interface is experimental. Method names, fields, and event shapes may evolve. For the authoritative schema, consult `protocol/src/mcp_protocol.rs` and the corresponding server wiring in `mcp-server/`. +This interface is experimental. Method names, fields, and event shapes may evolve. For the authoritative schema, consult `app-server-protocol/src/protocol/{common,v1,v2}.rs` and the corresponding server wiring in `app-server/`. From d7eb195b62a54d63ddd28e884d0b4aed0276cdd3 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Tue, 3 Mar 2026 13:37:40 -0800 Subject: [PATCH 17/17] chore(app-server): restore EventMsg TS types (#13397) Realized EventMsg generated types were unintentionally removed as part of this PR: https://github.com/openai/codex/pull/13375 Turns out our TypeScript export pipeline relied on transitively reaching `EventMsg`. We should still export `EventMsg` explicitly since we're still emitting `codex/event/*` events (for now, but getting dropped soon as well). --- .../schema/typescript/AgentMessageContent.ts | 5 + .../AgentMessageContentDeltaEvent.ts | 5 + .../typescript/AgentMessageDeltaEvent.ts | 5 + .../schema/typescript/AgentMessageEvent.ts | 6 + .../schema/typescript/AgentMessageItem.ts | 21 +++ .../typescript/AgentReasoningDeltaEvent.ts | 5 + .../schema/typescript/AgentReasoningEvent.ts | 5 + .../AgentReasoningRawContentDeltaEvent.ts | 5 + .../AgentReasoningRawContentEvent.ts | 5 + .../AgentReasoningSectionBreakEvent.ts | 5 + .../schema/typescript/AgentStatus.ts | 8 + .../ApplyPatchApprovalRequestEvent.ts | 23 +++ .../schema/typescript/AskForApproval.ts | 10 ++ .../schema/typescript/BackgroundEventEvent.ts | 5 + .../schema/typescript/ByteRange.ts | 13 ++ .../schema/typescript/CallToolResult.ts | 9 ++ .../schema/typescript/CodexErrorInfo.ts | 8 + .../CollabAgentInteractionBeginEvent.ts | 23 +++ .../CollabAgentInteractionEndEvent.ts | 36 +++++ .../schema/typescript/CollabAgentRef.ts | 18 +++ .../typescript/CollabAgentSpawnBeginEvent.ts | 19 +++ .../typescript/CollabAgentSpawnEndEvent.ts | 36 +++++ .../typescript/CollabAgentStatusEntry.ts | 23 +++ .../typescript/CollabCloseBeginEvent.ts | 18 +++ .../schema/typescript/CollabCloseEndEvent.ts | 32 ++++ .../typescript/CollabResumeBeginEvent.ts | 26 ++++ .../schema/typescript/CollabResumeEndEvent.ts | 32 ++++ .../typescript/CollabWaitingBeginEvent.ts | 23 +++ .../typescript/CollabWaitingEndEvent.ts | 24 +++ .../typescript/ContextCompactedEvent.ts | 5 + .../typescript/ContextCompactionItem.ts | 5 + .../schema/typescript/CreditsSnapshot.ts | 5 + .../schema/typescript/CustomPrompt.ts | 5 + .../typescript/DeprecationNoticeEvent.ts | 13 ++ .../DynamicToolCallOutputContentItem.ts | 5 + .../typescript/DynamicToolCallRequest.ts | 6 + .../DynamicToolCallResponseEvent.ts | 39 +++++ .../typescript/ElicitationRequestEvent.ts | 5 + .../schema/typescript/ErrorEvent.ts | 6 + .../schema/typescript/EventMsg.ts | 82 ++++++++++ .../typescript/ExecApprovalRequestEvent.ts | 62 ++++++++ .../typescript/ExecCommandBeginEvent.ts | 35 +++++ .../schema/typescript/ExecCommandEndEvent.ts | 64 ++++++++ .../typescript/ExecCommandOutputDeltaEvent.ts | 18 +++ .../schema/typescript/ExecCommandSource.ts | 5 + .../schema/typescript/ExecCommandStatus.ts | 5 + .../schema/typescript/ExecOutputStream.ts | 5 + .../typescript/ExitedReviewModeEvent.ts | 6 + .../typescript/FileSystemPermissions.ts | 6 + .../GetHistoryEntryResponseEvent.ts | 10 ++ .../schema/typescript/HistoryEntry.ts | 5 + .../schema/typescript/ItemCompletedEvent.ts | 7 + .../schema/typescript/ItemStartedEvent.ts | 7 + .../ListCustomPromptsResponseEvent.ts | 9 ++ .../ListRemoteSkillsResponseEvent.ts | 9 ++ .../typescript/ListSkillsResponseEvent.ts | 9 ++ .../schema/typescript/MacOsPermissions.ts | 7 + .../schema/typescript/McpAuthStatus.ts | 5 + .../schema/typescript/McpInvocation.ts | 18 +++ .../typescript/McpListToolsResponseEvent.ts | 25 ++++ .../typescript/McpStartupCompleteEvent.ts | 6 + .../schema/typescript/McpStartupFailure.ts | 5 + .../schema/typescript/McpStartupStatus.ts | 5 + .../typescript/McpStartupUpdateEvent.ts | 14 ++ .../typescript/McpToolCallBeginEvent.ts | 10 ++ .../schema/typescript/McpToolCallEndEvent.ts | 15 ++ .../schema/typescript/ModelRerouteEvent.ts | 6 + .../schema/typescript/ModelRerouteReason.ts | 5 + .../schema/typescript/NetworkAccess.ts | 8 + .../typescript/NetworkApprovalContext.ts | 6 + .../typescript/NetworkApprovalProtocol.ts | 5 + .../schema/typescript/PatchApplyBeginEvent.ts | 23 +++ .../schema/typescript/PatchApplyEndEvent.ts | 36 +++++ .../schema/typescript/PatchApplyStatus.ts | 5 + .../schema/typescript/PermissionProfile.ts | 7 + .../schema/typescript/PlanDeltaEvent.ts | 5 + .../schema/typescript/PlanItem.ts | 5 + .../schema/typescript/PlanItemArg.ts | 6 + .../schema/typescript/RateLimitSnapshot.ts | 8 + .../schema/typescript/RateLimitWindow.ts | 17 +++ .../schema/typescript/RawResponseItemEvent.ts | 6 + .../schema/typescript/ReadOnlyAccess.ts | 19 +++ .../schema/typescript/RealtimeAudioFrame.ts | 5 + .../RealtimeConversationClosedEvent.ts | 5 + .../RealtimeConversationRealtimeEvent.ts | 6 + .../RealtimeConversationStartedEvent.ts | 5 + .../schema/typescript/RealtimeEvent.ts | 8 + .../typescript/RealtimeHandoffMessage.ts | 5 + .../typescript/RealtimeHandoffRequested.ts | 6 + .../typescript/ReasoningContentDeltaEvent.ts | 5 + .../schema/typescript/ReasoningItem.ts | 5 + .../ReasoningRawContentDeltaEvent.ts | 5 + .../schema/typescript/RejectConfig.ts | 17 +++ .../typescript/RemoteSkillDownloadedEvent.ts | 8 + .../schema/typescript/RemoteSkillSummary.ts | 5 + .../typescript/RequestUserInputEvent.ts | 15 ++ .../typescript/RequestUserInputQuestion.ts | 6 + .../RequestUserInputQuestionOption.ts | 5 + .../schema/typescript/ReviewCodeLocation.ts | 9 ++ .../schema/typescript/ReviewFinding.ts | 9 ++ .../schema/typescript/ReviewLineRange.ts | 8 + .../schema/typescript/ReviewOutputEvent.ts | 9 ++ .../schema/typescript/ReviewRequest.ts | 9 ++ .../schema/typescript/ReviewTarget.ts | 9 ++ .../schema/typescript/SandboxPolicy.ts | 44 ++++++ .../typescript/SessionConfiguredEvent.ts | 58 ++++++++ .../typescript/SessionNetworkProxyRuntime.ts | 5 + .../schema/typescript/SkillDependencies.ts | 6 + .../schema/typescript/SkillErrorInfo.ts | 5 + .../schema/typescript/SkillInterface.ts | 5 + .../schema/typescript/SkillMetadata.ts | 12 ++ .../schema/typescript/SkillScope.ts | 5 + .../schema/typescript/SkillToolDependency.ts | 5 + .../schema/typescript/SkillsListEntry.ts | 7 + .../schema/typescript/StepStatus.ts | 5 + .../schema/typescript/StreamErrorEvent.ts | 12 ++ .../typescript/TerminalInteractionEvent.ts | 17 +++ .../schema/typescript/TextElement.ts | 14 ++ .../typescript/ThreadNameUpdatedEvent.ts | 6 + .../typescript/ThreadRolledBackEvent.ts | 9 ++ .../schema/typescript/TokenCountEvent.ts | 7 + .../schema/typescript/TokenUsage.ts | 5 + .../schema/typescript/TokenUsageInfo.ts | 6 + .../schema/typescript/TurnAbortReason.ts | 5 + .../schema/typescript/TurnAbortedEvent.ts | 6 + .../schema/typescript/TurnCompleteEvent.ts | 5 + .../schema/typescript/TurnDiffEvent.ts | 5 + .../schema/typescript/TurnItem.ts | 11 ++ .../schema/typescript/TurnStartedEvent.ts | 6 + .../schema/typescript/UndoCompletedEvent.ts | 5 + .../schema/typescript/UndoStartedEvent.ts | 5 + .../schema/typescript/UpdatePlanArgs.ts | 10 ++ .../schema/typescript/UserInput.ts | 16 ++ .../schema/typescript/UserMessageEvent.ts | 22 +++ .../schema/typescript/UserMessageItem.ts | 6 + .../typescript/ViewImageToolCallEvent.ts | 13 ++ .../schema/typescript/WarningEvent.ts | 5 + .../schema/typescript/WebSearchBeginEvent.ts | 5 + .../schema/typescript/WebSearchEndEvent.ts | 6 + .../schema/typescript/WebSearchItem.ts | 6 + .../schema/typescript/index.ts | 140 ++++++++++++++++++ codex-rs/app-server-protocol/src/export.rs | 2 + 142 files changed, 1844 insertions(+) create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ByteRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/EventMsg.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillScope.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/StepStatus.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TextElement.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserInput.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts new file mode 100644 index 0000000000..dc2cfb77e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageContent = { "type": "Text", text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts new file mode 100644 index 0000000000..1473a4f2bc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts new file mode 100644 index 0000000000..1e12d85fbb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentMessageDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts new file mode 100644 index 0000000000..b32680055f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { MessagePhase } from "./MessagePhase"; + +export type AgentMessageEvent = { message: string, phase: MessagePhase | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts new file mode 100644 index 0000000000..ee67a3e23b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentMessageItem.ts @@ -0,0 +1,21 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageContent } from "./AgentMessageContent"; +import type { MessagePhase } from "./MessagePhase"; + +/** + * Assistant-authored message payload used in turn-item streams. + * + * `phase` is optional because not all providers/models emit it. Consumers + * should use it when present, but retain legacy completion semantics when it + * is `None`. + */ +export type AgentMessageItem = { id: string, content: Array, +/** + * Optional phase metadata carried through from `ResponseItem::Message`. + * + * This is currently used by TUI rendering to distinguish mid-turn + * commentary from a final answer and avoid status-indicator jitter. + */ +phase?: MessagePhase, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts new file mode 100644 index 0000000000..fc2c221937 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts new file mode 100644 index 0000000000..bf0062cd43 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts new file mode 100644 index 0000000000..fcfa816f5d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningRawContentDeltaEvent = { delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts new file mode 100644 index 0000000000..364c278229 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningRawContentEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningRawContentEvent = { text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts new file mode 100644 index 0000000000..604aceed93 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentReasoningSectionBreakEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AgentReasoningSectionBreakEvent = { item_id: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts new file mode 100644 index 0000000000..ddf6789c78 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Agent lifecycle status, derived from emitted events. + */ +export type AgentStatus = "pending_init" | "running" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts new file mode 100644 index 0000000000..0c53cf50b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ApplyPatchApprovalRequestEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; + +export type ApplyPatchApprovalRequestEvent = { +/** + * Responses API call id for the associated patch apply call, if available. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility with older senders. + */ +turn_id: string, changes: { [key in string]?: FileChange }, +/** + * Optional explanatory reason (e.g. request for extra write access). + */ +reason: string | null, +/** + * When set, the agent is asking the user to allow writes under this root for the remainder of the session. + */ +grant_root: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts b/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts new file mode 100644 index 0000000000..227eb44e77 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AskForApproval.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RejectConfig } from "./RejectConfig"; + +/** + * Determines the conditions under which the user is consulted to approve + * running the command proposed by Codex. + */ +export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "reject": RejectConfig } | "never"; diff --git a/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts b/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts new file mode 100644 index 0000000000..236b1dd888 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/BackgroundEventEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type BackgroundEventEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts b/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts new file mode 100644 index 0000000000..ab36a79acd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ByteRange.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ByteRange = { +/** + * Start byte offset (inclusive) within the UTF-8 text buffer. + */ +start: number, +/** + * End byte offset (exclusive) within the UTF-8 text buffer. + */ +end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts new file mode 100644 index 0000000000..e7a471d465 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CallToolResult.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +/** + * The server's response to a tool call. + */ +export type CallToolResult = { content: Array, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts new file mode 100644 index 0000000000..522b91ce20 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CodexErrorInfo.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Codex errors that we expose to clients. + */ +export type CodexErrorInfo = "context_window_exceeded" | "usage_limit_exceeded" | "server_overloaded" | { "http_connection_failed": { http_status_code: number | null, } } | { "response_stream_connection_failed": { http_status_code: number | null, } } | "internal_server_error" | "unauthorized" | "bad_request" | "sandbox_error" | { "response_stream_disconnected": { http_status_code: number | null, } } | { "response_too_many_failed_attempts": { http_status_code: number | null, } } | "thread_rollback_failed" | "other"; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts new file mode 100644 index 0000000000..7109741999 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionBeginEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentInteractionBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Prompt sent from the sender to the receiver. Can be empty to prevent CoT + * leaking at the beginning. + */ +prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts new file mode 100644 index 0000000000..5458e06dce --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentInteractionEndEvent.ts @@ -0,0 +1,36 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentInteractionEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Optional nickname assigned to the receiver agent. + */ +receiver_agent_nickname?: string | null, +/** + * Optional role assigned to the receiver agent. + */ +receiver_agent_role?: string | null, +/** + * Prompt sent from the sender to the receiver. Can be empty to prevent CoT + * leaking at the beginning. + */ +prompt: string, +/** + * Last known status of the receiver agent reported to the sender agent. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts new file mode 100644 index 0000000000..cae7bf88b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentRef.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentRef = { +/** + * Thread ID of the receiver/new agent. + */ +thread_id: ThreadId, +/** + * Optional nickname assigned to an AgentControl-spawned sub-agent. + */ +agent_nickname?: string | null, +/** + * Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. + */ +agent_role?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts new file mode 100644 index 0000000000..a86598e20c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnBeginEvent.ts @@ -0,0 +1,19 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentSpawnBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the + * beginning. + */ +prompt: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts new file mode 100644 index 0000000000..34753c8e08 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentSpawnEndEvent.ts @@ -0,0 +1,36 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentSpawnEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the newly spawned agent, if it was created. + */ +new_thread_id: ThreadId | null, +/** + * Optional nickname assigned to the new agent. + */ +new_agent_nickname?: string | null, +/** + * Optional role assigned to the new agent. + */ +new_agent_role?: string | null, +/** + * Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the + * beginning. + */ +prompt: string, +/** + * Last known status of the new agent reported to the sender agent. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts b/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts new file mode 100644 index 0000000000..286d19423d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabAgentStatusEntry.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabAgentStatusEntry = { +/** + * Thread ID of the receiver/new agent. + */ +thread_id: ThreadId, +/** + * Optional nickname assigned to an AgentControl-spawned sub-agent. + */ +agent_nickname?: string | null, +/** + * Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. + */ +agent_role?: string | null, +/** + * Last known status of the agent. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts new file mode 100644 index 0000000000..355d59523a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabCloseBeginEvent.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabCloseBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts new file mode 100644 index 0000000000..171886f1ef --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabCloseEndEvent.ts @@ -0,0 +1,32 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabCloseEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Optional nickname assigned to the receiver agent. + */ +receiver_agent_nickname?: string | null, +/** + * Optional role assigned to the receiver agent. + */ +receiver_agent_role?: string | null, +/** + * Last known status of the receiver agent reported to the sender agent before + * the close. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts new file mode 100644 index 0000000000..e6c1c3d5cd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabResumeBeginEvent.ts @@ -0,0 +1,26 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type CollabResumeBeginEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Optional nickname assigned to the receiver agent. + */ +receiver_agent_nickname?: string | null, +/** + * Optional role assigned to the receiver agent. + */ +receiver_agent_role?: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts new file mode 100644 index 0000000000..caf970ec28 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabResumeEndEvent.ts @@ -0,0 +1,32 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { ThreadId } from "./ThreadId"; + +export type CollabResumeEndEvent = { +/** + * Identifier for the collab tool call. + */ +call_id: string, +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receiver. + */ +receiver_thread_id: ThreadId, +/** + * Optional nickname assigned to the receiver agent. + */ +receiver_agent_nickname?: string | null, +/** + * Optional role assigned to the receiver agent. + */ +receiver_agent_role?: string | null, +/** + * Last known status of the receiver agent reported to the sender agent after + * resume. + */ +status: AgentStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts new file mode 100644 index 0000000000..f2f07f87ea --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingBeginEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CollabAgentRef } from "./CollabAgentRef"; +import type { ThreadId } from "./ThreadId"; + +export type CollabWaitingBeginEvent = { +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * Thread ID of the receivers. + */ +receiver_thread_ids: Array, +/** + * Optional nicknames/roles for receivers. + */ +receiver_agents?: Array, +/** + * ID of the waiting call. + */ +call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts new file mode 100644 index 0000000000..929d59c611 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CollabWaitingEndEvent.ts @@ -0,0 +1,24 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentStatus } from "./AgentStatus"; +import type { CollabAgentStatusEntry } from "./CollabAgentStatusEntry"; +import type { ThreadId } from "./ThreadId"; + +export type CollabWaitingEndEvent = { +/** + * Thread ID of the sender. + */ +sender_thread_id: ThreadId, +/** + * ID of the waiting call. + */ +call_id: string, +/** + * Optional receiver metadata paired with final statuses. + */ +agent_statuses?: Array, +/** + * Last known status of the receiver agents reported to the sender agent. + */ +statuses: { [key in ThreadId]?: AgentStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts new file mode 100644 index 0000000000..538ca7a1bc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContextCompactedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ContextCompactedEvent = null; diff --git a/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts b/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts new file mode 100644 index 0000000000..dc3ab6388e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ContextCompactionItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ContextCompactionItem = { id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts new file mode 100644 index 0000000000..737bf99bef --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CreditsSnapshot.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CreditsSnapshot = { has_credits: boolean, unlimited: boolean, balance: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts b/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts new file mode 100644 index 0000000000..96fe75e969 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/CustomPrompt.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CustomPrompt = { name: string, path: string, content: string, description: string | null, argument_hint: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts new file mode 100644 index 0000000000..c1a7d81314 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DeprecationNoticeEvent.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DeprecationNoticeEvent = { +/** + * Concise summary of what is deprecated. + */ +summary: string, +/** + * Optional extra guidance, such as migration steps or rationale. + */ +details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts new file mode 100644 index 0000000000..8f432109d1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallOutputContentItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DynamicToolCallOutputContentItem = { "type": "inputText", text: string, } | { "type": "inputImage", imageUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts new file mode 100644 index 0000000000..94b0c65c66 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallRequest.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +export type DynamicToolCallRequest = { callId: string, turnId: string, tool: string, arguments: JsonValue, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts new file mode 100644 index 0000000000..442c0ce6f3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/DynamicToolCallResponseEvent.ts @@ -0,0 +1,39 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; +import type { JsonValue } from "./serde_json/JsonValue"; + +export type DynamicToolCallResponseEvent = { +/** + * Identifier for the corresponding DynamicToolCallRequest. + */ +call_id: string, +/** + * Turn ID that this dynamic tool call belongs to. + */ +turn_id: string, +/** + * Dynamic tool name. + */ +tool: string, +/** + * Dynamic tool call arguments. + */ +arguments: JsonValue, +/** + * Dynamic tool response content items. + */ +content_items: Array, +/** + * Whether the tool call succeeded. + */ +success: boolean, +/** + * Optional error text when the tool call failed before producing a response. + */ +error: string | null, +/** + * The duration of the dynamic tool call. + */ +duration: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts new file mode 100644 index 0000000000..045e304bdc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ElicitationRequestEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts new file mode 100644 index 0000000000..fafde767e0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ErrorEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodexErrorInfo } from "./CodexErrorInfo"; + +export type ErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts new file mode 100644 index 0000000000..5ccbe1e697 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/EventMsg.ts @@ -0,0 +1,82 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; +import type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; +import type { AgentMessageEvent } from "./AgentMessageEvent"; +import type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; +import type { AgentReasoningEvent } from "./AgentReasoningEvent"; +import type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; +import type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; +import type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; +import type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; +import type { BackgroundEventEvent } from "./BackgroundEventEvent"; +import type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; +import type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; +import type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; +import type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; +import type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; +import type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; +import type { CollabResumeBeginEvent } from "./CollabResumeBeginEvent"; +import type { CollabResumeEndEvent } from "./CollabResumeEndEvent"; +import type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; +import type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; +import type { ContextCompactedEvent } from "./ContextCompactedEvent"; +import type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; +import type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; +import type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent"; +import type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; +import type { ErrorEvent } from "./ErrorEvent"; +import type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; +import type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; +import type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; +import type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; +import type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; +import type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; +import type { ItemCompletedEvent } from "./ItemCompletedEvent"; +import type { ItemStartedEvent } from "./ItemStartedEvent"; +import type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +import type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; +import type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; +import type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; +import type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; +import type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; +import type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; +import type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; +import type { ModelRerouteEvent } from "./ModelRerouteEvent"; +import type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; +import type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; +import type { PlanDeltaEvent } from "./PlanDeltaEvent"; +import type { RawResponseItemEvent } from "./RawResponseItemEvent"; +import type { RealtimeConversationClosedEvent } from "./RealtimeConversationClosedEvent"; +import type { RealtimeConversationRealtimeEvent } from "./RealtimeConversationRealtimeEvent"; +import type { RealtimeConversationStartedEvent } from "./RealtimeConversationStartedEvent"; +import type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; +import type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; +import type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; +import type { RequestUserInputEvent } from "./RequestUserInputEvent"; +import type { ReviewRequest } from "./ReviewRequest"; +import type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; +import type { StreamErrorEvent } from "./StreamErrorEvent"; +import type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; +import type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; +import type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; +import type { TokenCountEvent } from "./TokenCountEvent"; +import type { TurnAbortedEvent } from "./TurnAbortedEvent"; +import type { TurnCompleteEvent } from "./TurnCompleteEvent"; +import type { TurnDiffEvent } from "./TurnDiffEvent"; +import type { TurnStartedEvent } from "./TurnStartedEvent"; +import type { UndoCompletedEvent } from "./UndoCompletedEvent"; +import type { UndoStartedEvent } from "./UndoStartedEvent"; +import type { UpdatePlanArgs } from "./UpdatePlanArgs"; +import type { UserMessageEvent } from "./UserMessageEvent"; +import type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; +import type { WarningEvent } from "./WarningEvent"; +import type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; +import type { WebSearchEndEvent } from "./WebSearchEndEvent"; + +/** + * Response event from the agent + * NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen. + */ +export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "dynamic_tool_call_response" } & DynamicToolCallResponseEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts new file mode 100644 index 0000000000..cc13400900 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecApprovalRequestEvent.ts @@ -0,0 +1,62 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +import type { NetworkApprovalContext } from "./NetworkApprovalContext"; +import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; +import type { ParsedCommand } from "./ParsedCommand"; +import type { PermissionProfile } from "./PermissionProfile"; +import type { ReviewDecision } from "./ReviewDecision"; + +export type ExecApprovalRequestEvent = { +/** + * Identifier for the associated command execution item. + */ +call_id: string, +/** + * Identifier for this specific approval callback. + * + * When absent, the approval is for the command item itself (`call_id`). + * This is present for subcommand approvals (via execve intercept). + */ +approval_id?: string, +/** + * Turn ID that this command belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * The command to be executed. + */ +command: Array, +/** + * The command's working directory. + */ +cwd: string, +/** + * Optional human-readable reason for the approval (e.g. retry without sandbox). + */ +reason: string | null, +/** + * Optional network context for a blocked request that can be approved. + */ +network_approval_context?: NetworkApprovalContext, +/** + * Proposed execpolicy amendment that can be applied to allow future runs. + */ +proposed_execpolicy_amendment?: ExecPolicyAmendment, +/** + * Proposed network policy amendments (for example allow/deny this host in future). + */ +proposed_network_policy_amendments?: Array, +/** + * Optional additional filesystem permissions requested for this command. + */ +additional_permissions?: PermissionProfile, +/** + * Ordered list of decisions the client may present for this prompt. + * + * When absent, clients should derive the legacy default set from the + * other fields on this request. + */ +available_decisions?: Array, parsed_cmd: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts new file mode 100644 index 0000000000..a9b4bc9393 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandBeginEvent.ts @@ -0,0 +1,35 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecCommandSource } from "./ExecCommandSource"; +import type { ParsedCommand } from "./ParsedCommand"; + +export type ExecCommandBeginEvent = { +/** + * Identifier so this can be paired with the ExecCommandEnd event. + */ +call_id: string, +/** + * Identifier for the underlying PTY process (when available). + */ +process_id?: string, +/** + * Turn ID that this command belongs to. + */ +turn_id: string, +/** + * The command to be executed. + */ +command: Array, +/** + * The command's working directory if not the default cwd for the agent. + */ +cwd: string, parsed_cmd: Array, +/** + * Where the command originated. Defaults to Agent for backward compatibility. + */ +source: ExecCommandSource, +/** + * Raw input sent to a unified exec session (if this is an interaction event). + */ +interaction_input?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts new file mode 100644 index 0000000000..0bfc41ea81 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandEndEvent.ts @@ -0,0 +1,64 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecCommandSource } from "./ExecCommandSource"; +import type { ExecCommandStatus } from "./ExecCommandStatus"; +import type { ParsedCommand } from "./ParsedCommand"; + +export type ExecCommandEndEvent = { +/** + * Identifier for the ExecCommandBegin that finished. + */ +call_id: string, +/** + * Identifier for the underlying PTY process (when available). + */ +process_id?: string, +/** + * Turn ID that this command belongs to. + */ +turn_id: string, +/** + * The command that was executed. + */ +command: Array, +/** + * The command's working directory if not the default cwd for the agent. + */ +cwd: string, parsed_cmd: Array, +/** + * Where the command originated. Defaults to Agent for backward compatibility. + */ +source: ExecCommandSource, +/** + * Raw input sent to a unified exec session (if this is an interaction event). + */ +interaction_input?: string, +/** + * Captured stdout + */ +stdout: string, +/** + * Captured stderr + */ +stderr: string, +/** + * Captured aggregated output + */ +aggregated_output: string, +/** + * The command's exit code. + */ +exit_code: number, +/** + * The duration of the command execution. + */ +duration: string, +/** + * Formatted output from the command, as seen by the model. + */ +formatted_output: string, +/** + * Completion status for this command execution. + */ +status: ExecCommandStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts new file mode 100644 index 0000000000..0930bdd827 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandOutputDeltaEvent.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ExecOutputStream } from "./ExecOutputStream"; + +export type ExecCommandOutputDeltaEvent = { +/** + * Identifier for the ExecCommandBegin that produced this chunk. + */ +call_id: string, +/** + * Which stream produced this chunk. + */ +stream: ExecOutputStream, +/** + * Raw bytes from the stream (may not be valid UTF-8). + */ +chunk: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts new file mode 100644 index 0000000000..b665441bc2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandSource.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecCommandSource = "agent" | "user_shell" | "unified_exec_startup" | "unified_exec_interaction"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts b/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts new file mode 100644 index 0000000000..d8d91fb19f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecCommandStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecCommandStatus = "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts b/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts new file mode 100644 index 0000000000..96aa74483d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExecOutputStream.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ExecOutputStream = "stdout" | "stderr"; diff --git a/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts new file mode 100644 index 0000000000..7271f07a3f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ExitedReviewModeEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewOutputEvent } from "./ReviewOutputEvent"; + +export type ExitedReviewModeEvent = { review_output: ReviewOutputEvent | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts new file mode 100644 index 0000000000..aedf84de80 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FileSystemPermissions.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; + +export type FileSystemPermissions = { read: Array | null, write: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts new file mode 100644 index 0000000000..d46019c1dc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/GetHistoryEntryResponseEvent.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HistoryEntry } from "./HistoryEntry"; + +export type GetHistoryEntryResponseEvent = { offset: number, log_id: bigint, +/** + * The entry at the requested offset, if available and parseable. + */ +entry: HistoryEntry | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts b/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts new file mode 100644 index 0000000000..da5bc37c21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/HistoryEntry.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type HistoryEntry = { conversation_id: string, ts: bigint, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts new file mode 100644 index 0000000000..97de348dff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ItemCompletedEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; +import type { TurnItem } from "./TurnItem"; + +export type ItemCompletedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts new file mode 100644 index 0000000000..e82f78f965 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ItemStartedEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; +import type { TurnItem } from "./TurnItem"; + +export type ItemStartedEvent = { thread_id: ThreadId, turn_id: string, item: TurnItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts new file mode 100644 index 0000000000..9ebb43afb7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListCustomPromptsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CustomPrompt } from "./CustomPrompt"; + +/** + * Response payload for `Op::ListCustomPrompts`. + */ +export type ListCustomPromptsResponseEvent = { custom_prompts: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts new file mode 100644 index 0000000000..e3b277f4d6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListRemoteSkillsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RemoteSkillSummary } from "./RemoteSkillSummary"; + +/** + * Response payload for `Op::ListRemoteSkills`. + */ +export type ListRemoteSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts new file mode 100644 index 0000000000..efdd547596 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ListSkillsResponseEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillsListEntry } from "./SkillsListEntry"; + +/** + * Response payload for `Op::ListSkills`. + */ +export type ListSkillsResponseEvent = { skills: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts new file mode 100644 index 0000000000..5c0792412c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/MacOsPermissions.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { MacOsAutomationValue } from "./MacOsAutomationValue"; +import type { MacOsPreferencesValue } from "./MacOsPreferencesValue"; + +export type MacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts new file mode 100644 index 0000000000..919ae85fd0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpAuthStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpAuthStatus = "unsupported" | "not_logged_in" | "bearer_token" | "o_auth"; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts b/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts new file mode 100644 index 0000000000..5b7103a60c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpInvocation.ts @@ -0,0 +1,18 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { JsonValue } from "./serde_json/JsonValue"; + +export type McpInvocation = { +/** + * Name of the MCP server as defined in the config. + */ +server: string, +/** + * Name of the tool as given by the MCP server. + */ +tool: string, +/** + * Arguments to the tool call. + */ +arguments: JsonValue | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts new file mode 100644 index 0000000000..945959431a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpListToolsResponseEvent.ts @@ -0,0 +1,25 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpAuthStatus } from "./McpAuthStatus"; +import type { Resource } from "./Resource"; +import type { ResourceTemplate } from "./ResourceTemplate"; +import type { Tool } from "./Tool"; + +export type McpListToolsResponseEvent = { +/** + * Fully qualified tool name -> tool definition. + */ +tools: { [key in string]?: Tool }, +/** + * Known resources grouped by server name. + */ +resources: { [key in string]?: Array }, +/** + * Known resource templates grouped by server name. + */ +resource_templates: { [key in string]?: Array }, +/** + * Authentication status for each configured MCP server. + */ +auth_statuses: { [key in string]?: McpAuthStatus }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts new file mode 100644 index 0000000000..67354adfbe --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupCompleteEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpStartupFailure } from "./McpStartupFailure"; + +export type McpStartupCompleteEvent = { ready: Array, failed: Array, cancelled: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts new file mode 100644 index 0000000000..b12009b15b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupFailure.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpStartupFailure = { server: string, error: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts new file mode 100644 index 0000000000..48c08226f4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpStartupStatus = { "state": "starting" } | { "state": "ready" } | { "state": "failed", error: string, } | { "state": "cancelled" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts new file mode 100644 index 0000000000..ecfe7d551e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpStartupUpdateEvent.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpStartupStatus } from "./McpStartupStatus"; + +export type McpStartupUpdateEvent = { +/** + * Server name being started. + */ +server: string, +/** + * Current startup status. + */ +status: McpStartupStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts new file mode 100644 index 0000000000..feb7ca7c21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpToolCallBeginEvent.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpInvocation } from "./McpInvocation"; + +export type McpToolCallBeginEvent = { +/** + * Identifier so this can be paired with the McpToolCallEnd event. + */ +call_id: string, invocation: McpInvocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts new file mode 100644 index 0000000000..0ca82b2bc6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/McpToolCallEndEvent.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CallToolResult } from "./CallToolResult"; +import type { McpInvocation } from "./McpInvocation"; + +export type McpToolCallEndEvent = { +/** + * Identifier for the corresponding McpToolCallBegin that finished. + */ +call_id: string, invocation: McpInvocation, duration: string, +/** + * Result of the tool call. Note this could be an error. + */ +result: { Ok : CallToolResult } | { Err : string }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts new file mode 100644 index 0000000000..23a4e1efb6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ModelRerouteReason } from "./ModelRerouteReason"; + +export type ModelRerouteEvent = { from_model: string, to_model: string, reason: ModelRerouteReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts new file mode 100644 index 0000000000..f5e1abf1e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ModelRerouteReason.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ModelRerouteReason = "high_risk_cyber_activity"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts new file mode 100644 index 0000000000..f259e67b99 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NetworkAccess.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Represents whether outbound network access is available to the agent. + */ +export type NetworkAccess = "restricted" | "enabled"; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts new file mode 100644 index 0000000000..b4b78e473c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalContext.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol"; + +export type NetworkApprovalContext = { host: string, protocol: NetworkApprovalProtocol, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts new file mode 100644 index 0000000000..a33eab566f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/NetworkApprovalProtocol.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type NetworkApprovalProtocol = "http" | "https" | "socks5_tcp" | "socks5_udp"; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts new file mode 100644 index 0000000000..19ff0d5754 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PatchApplyBeginEvent.ts @@ -0,0 +1,23 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; + +export type PatchApplyBeginEvent = { +/** + * Identifier so this can be paired with the PatchApplyEnd event. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * If true, there was no ApplyPatchApprovalRequest for this patch. + */ +auto_approved: boolean, +/** + * The changes to be applied. + */ +changes: { [key in string]?: FileChange }, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts new file mode 100644 index 0000000000..9dacb00e44 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PatchApplyEndEvent.ts @@ -0,0 +1,36 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileChange } from "./FileChange"; +import type { PatchApplyStatus } from "./PatchApplyStatus"; + +export type PatchApplyEndEvent = { +/** + * Identifier for the PatchApplyBegin that finished. + */ +call_id: string, +/** + * Turn ID that this patch belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, +/** + * Captured stdout (summary printed by apply_patch). + */ +stdout: string, +/** + * Captured stderr (parser errors, IO failures, etc.). + */ +stderr: string, +/** + * Whether the patch was applied successfully. + */ +success: boolean, +/** + * The changes that were applied (mirrors PatchApplyBeginEvent::changes). + */ +changes: { [key in string]?: FileChange }, +/** + * Completion status for this patch application. + */ +status: PatchApplyStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts b/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts new file mode 100644 index 0000000000..721fcd9b1f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PatchApplyStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PatchApplyStatus = "completed" | "failed" | "declined"; diff --git a/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts new file mode 100644 index 0000000000..bfc0de27b7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PermissionProfile.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { FileSystemPermissions } from "./FileSystemPermissions"; +import type { MacOsPermissions } from "./MacOsPermissions"; + +export type PermissionProfile = { network: boolean | null, file_system: FileSystemPermissions | null, macos: MacOsPermissions | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts new file mode 100644 index 0000000000..f2ff588442 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlanDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts new file mode 100644 index 0000000000..909ab40e64 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type PlanItem = { id: string, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts b/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts new file mode 100644 index 0000000000..a9c8acfa75 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/PlanItemArg.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { StepStatus } from "./StepStatus"; + +export type PlanItemArg = { step: string, status: StepStatus, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts new file mode 100644 index 0000000000..8604128b4e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RateLimitSnapshot.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CreditsSnapshot } from "./CreditsSnapshot"; +import type { PlanType } from "./PlanType"; +import type { RateLimitWindow } from "./RateLimitWindow"; + +export type RateLimitSnapshot = { limit_id: string | null, limit_name: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, plan_type: PlanType | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts b/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts new file mode 100644 index 0000000000..4a85062bf7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RateLimitWindow.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RateLimitWindow = { +/** + * Percentage (0-100) of the window that has been consumed. + */ +used_percent: number, +/** + * Rolling window duration, in minutes. + */ +window_minutes: number | null, +/** + * Unix timestamp (seconds since epoch) when the window resets. + */ +resets_at: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts new file mode 100644 index 0000000000..62dd4f0018 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RawResponseItemEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ResponseItem } from "./ResponseItem"; + +export type RawResponseItemEvent = { item: ResponseItem, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts b/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts new file mode 100644 index 0000000000..c01bdd37c6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReadOnlyAccess.ts @@ -0,0 +1,19 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; + +/** + * Determines how read-only file access is granted inside a restricted + * sandbox. + */ +export type ReadOnlyAccess = { "type": "restricted", +/** + * Include built-in platform read roots required for basic process + * execution. + */ +include_platform_defaults: boolean, +/** + * Additional absolute roots that should be readable. + */ +readable_roots?: Array, } | { "type": "full-access" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts new file mode 100644 index 0000000000..99c0c1063c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeAudioFrame.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RealtimeAudioFrame = { data: string, sample_rate: number, num_channels: number, samples_per_channel: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts new file mode 100644 index 0000000000..c73e6833ae --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationClosedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RealtimeConversationClosedEvent = { reason: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts new file mode 100644 index 0000000000..4ff24a8281 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationRealtimeEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RealtimeEvent } from "./RealtimeEvent"; + +export type RealtimeConversationRealtimeEvent = { payload: RealtimeEvent, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts new file mode 100644 index 0000000000..f2894fcb1e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeConversationStartedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RealtimeConversationStartedEvent = { session_id: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts new file mode 100644 index 0000000000..8286f34cad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeEvent.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RealtimeAudioFrame } from "./RealtimeAudioFrame"; +import type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested"; +import type { JsonValue } from "./serde_json/JsonValue"; + +export type RealtimeEvent = { "SessionUpdated": { session_id: string, instructions: string | null, } } | { "AudioOut": RealtimeAudioFrame } | { "ConversationItemAdded": JsonValue } | { "ConversationItemDone": { item_id: string, } } | { "HandoffRequested": RealtimeHandoffRequested } | { "Error": string }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts new file mode 100644 index 0000000000..39b77cfdf1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffMessage.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RealtimeHandoffMessage = { role: string, text: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts new file mode 100644 index 0000000000..f3426706aa --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RealtimeHandoffRequested.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage"; + +export type RealtimeHandoffRequested = { handoff_id: string, item_id: string, input_transcript: string, messages: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts new file mode 100644 index 0000000000..70dfc01d24 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, summary_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts new file mode 100644 index 0000000000..80bcb65fd1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningItem.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningItem = { id: string, summary_text: Array, raw_content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts new file mode 100644 index 0000000000..ef3a792caf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReasoningRawContentDeltaEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReasoningRawContentDeltaEvent = { thread_id: string, turn_id: string, item_id: string, delta: string, content_index: bigint, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts b/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts new file mode 100644 index 0000000000..320f1096b0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RejectConfig.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RejectConfig = { +/** + * Reject approval prompts related to sandbox escalation. + */ +sandbox_approval: boolean, +/** + * Reject prompts triggered by execpolicy `prompt` rules. + */ +rules: boolean, +/** + * Reject MCP elicitation prompts. + */ +mcp_elicitations: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts new file mode 100644 index 0000000000..83082f2a57 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillDownloadedEvent.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Response payload for `Op::DownloadRemoteSkill`. + */ +export type RemoteSkillDownloadedEvent = { id: string, name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts new file mode 100644 index 0000000000..7bf57b3b09 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RemoteSkillSummary.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoteSkillSummary = { id: string, name: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts new file mode 100644 index 0000000000..8ea6453de9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputEvent.ts @@ -0,0 +1,15 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; + +export type RequestUserInputEvent = { +/** + * Responses API call id for the associated tool call, if available. + */ +call_id: string, +/** + * Turn ID that this request belongs to. + * Uses `#[serde(default)]` for backwards compatibility. + */ +turn_id: string, questions: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts new file mode 100644 index 0000000000..2a68f7b4c8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestion.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; + +export type RequestUserInputQuestion = { id: string, header: string, question: string, isOther: boolean, isSecret: boolean, options: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts new file mode 100644 index 0000000000..b2d2a0db48 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/RequestUserInputQuestionOption.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RequestUserInputQuestionOption = { label: string, description: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts new file mode 100644 index 0000000000..752589fe55 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewCodeLocation.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewLineRange } from "./ReviewLineRange"; + +/** + * Location of the code related to a review finding. + */ +export type ReviewCodeLocation = { absolute_file_path: string, line_range: ReviewLineRange, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts new file mode 100644 index 0000000000..e7c96bd170 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewFinding.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewCodeLocation } from "./ReviewCodeLocation"; + +/** + * A single review finding describing an observed issue or recommendation. + */ +export type ReviewFinding = { title: string, body: string, confidence_score: number, priority: number, code_location: ReviewCodeLocation, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts new file mode 100644 index 0000000000..c57ec6ed60 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewLineRange.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Inclusive line range in a file associated with the finding. + */ +export type ReviewLineRange = { start: number, end: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts new file mode 100644 index 0000000000..c45747424b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewOutputEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewFinding } from "./ReviewFinding"; + +/** + * Structured review result produced by a child review session. + */ +export type ReviewOutputEvent = { findings: Array, overall_correctness: string, overall_explanation: string, overall_confidence_score: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts new file mode 100644 index 0000000000..1e9b8ad2ee --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewRequest.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewTarget } from "./ReviewTarget"; + +/** + * Review request sent to the review session. + */ +export type ReviewRequest = { target: ReviewTarget, user_facing_hint?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts b/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts new file mode 100644 index 0000000000..a79f1e993c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ReviewTarget.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReviewTarget = { "type": "uncommittedChanges" } | { "type": "baseBranch", branch: string, } | { "type": "commit", sha: string, +/** + * Optional human-readable label (e.g., commit subject) for UIs. + */ +title: string | null, } | { "type": "custom", instructions: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts new file mode 100644 index 0000000000..743ad22229 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicy.ts @@ -0,0 +1,44 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; +import type { NetworkAccess } from "./NetworkAccess"; +import type { ReadOnlyAccess } from "./ReadOnlyAccess"; + +/** + * Determines execution restrictions for model shell commands. + */ +export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only", +/** + * Read access granted while running under this policy. + */ +access?: ReadOnlyAccess, } | { "type": "external-sandbox", +/** + * Whether the external sandbox permits outbound network traffic. + */ +network_access: NetworkAccess, } | { "type": "workspace-write", +/** + * Additional folders (beyond cwd and possibly TMPDIR) that should be + * writable from within the sandbox. + */ +writable_roots?: Array, +/** + * Read access granted while running under this policy. + */ +read_only_access?: ReadOnlyAccess, +/** + * When set to `true`, outbound network access is allowed. `false` by + * default. + */ +network_access: boolean, +/** + * When set to `true`, will NOT include the per-user `TMPDIR` + * environment variable among the default writable roots. Defaults to + * `false`. + */ +exclude_tmpdir_env_var: boolean, +/** + * When set to `true`, will NOT include the `/tmp` among the default + * writable roots on UNIX. Defaults to `false`. + */ +exclude_slash_tmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts new file mode 100644 index 0000000000..b4696a0e4c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SessionConfiguredEvent.ts @@ -0,0 +1,58 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AskForApproval } from "./AskForApproval"; +import type { EventMsg } from "./EventMsg"; +import type { ReasoningEffort } from "./ReasoningEffort"; +import type { SandboxPolicy } from "./SandboxPolicy"; +import type { ServiceTier } from "./ServiceTier"; +import type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime"; +import type { ThreadId } from "./ThreadId"; + +export type SessionConfiguredEvent = { session_id: ThreadId, forked_from_id: ThreadId | null, +/** + * Optional user-facing thread name (may be unset). + */ +thread_name?: string, +/** + * Tell the client what model is being queried. + */ +model: string, model_provider_id: string, service_tier: ServiceTier | null, +/** + * When to escalate for approval for execution + */ +approval_policy: AskForApproval, +/** + * How to sandbox commands executed in the system + */ +sandbox_policy: SandboxPolicy, +/** + * Working directory that should be treated as the *root* of the + * session. + */ +cwd: string, +/** + * The effort the model is putting into reasoning about the user's request. + */ +reasoning_effort: ReasoningEffort | null, +/** + * Identifier of the history log file (inode on Unix, 0 otherwise). + */ +history_log_id: bigint, +/** + * Current number of entries in the history log. + */ +history_entry_count: number, +/** + * Optional initial messages (as events) for resumed sessions. + * When present, UIs can use these to seed the history. + */ +initial_messages: Array | null, +/** + * Runtime proxy bind addresses, when the managed proxy was started for this session. + */ +network_proxy?: SessionNetworkProxyRuntime, +/** + * Path in which the rollout is stored. Can be `None` for ephemeral threads + */ +rollout_path: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts b/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts new file mode 100644 index 0000000000..3f0c6d857e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SessionNetworkProxyRuntime.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, admin_addr: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts b/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts new file mode 100644 index 0000000000..e2dd4f4241 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillDependencies.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillToolDependency } from "./SkillToolDependency"; + +export type SkillDependencies = { tools: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts b/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts new file mode 100644 index 0000000000..6eaf035d8c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillErrorInfo.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillErrorInfo = { path: string, message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts b/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts new file mode 100644 index 0000000000..30250b9383 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillInterface.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillInterface = { display_name?: string, short_description?: string, icon_small?: string, icon_large?: string, brand_color?: string, default_prompt?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts new file mode 100644 index 0000000000..088abc406a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillMetadata.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillDependencies } from "./SkillDependencies"; +import type { SkillInterface } from "./SkillInterface"; +import type { SkillScope } from "./SkillScope"; + +export type SkillMetadata = { name: string, description: string, +/** + * Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description. + */ +short_description?: string, interface?: SkillInterface, dependencies?: SkillDependencies, path: string, scope: SkillScope, enabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts b/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts new file mode 100644 index 0000000000..997006f5b8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillScope.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillScope = "user" | "repo" | "system" | "admin"; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts b/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts new file mode 100644 index 0000000000..a5da45e178 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillToolDependency.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SkillToolDependency = { type: string, value: string, description?: string, transport?: string, command?: string, url?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts b/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts new file mode 100644 index 0000000000..3f46c98a4a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SkillsListEntry.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SkillErrorInfo } from "./SkillErrorInfo"; +import type { SkillMetadata } from "./SkillMetadata"; + +export type SkillsListEntry = { cwd: string, skills: Array, errors: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts b/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts new file mode 100644 index 0000000000..8494a76e0b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/StepStatus.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type StepStatus = "pending" | "in_progress" | "completed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts b/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts new file mode 100644 index 0000000000..b88993a344 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/StreamErrorEvent.ts @@ -0,0 +1,12 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CodexErrorInfo } from "./CodexErrorInfo"; + +export type StreamErrorEvent = { message: string, codex_error_info: CodexErrorInfo | null, +/** + * Optional details about the underlying stream failure (often the same + * human-readable message that is surfaced as the terminal error if retries + * are exhausted). + */ +additional_details: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts new file mode 100644 index 0000000000..5f300e6ca5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TerminalInteractionEvent.ts @@ -0,0 +1,17 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TerminalInteractionEvent = { +/** + * Identifier for the ExecCommandBegin that produced this chunk. + */ +call_id: string, +/** + * Process id associated with the running command. + */ +process_id: string, +/** + * Stdin sent to the running session. + */ +stdin: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TextElement.ts b/codex-rs/app-server-protocol/schema/typescript/TextElement.ts new file mode 100644 index 0000000000..3dcd369d82 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TextElement.ts @@ -0,0 +1,14 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ByteRange } from "./ByteRange"; + +export type TextElement = { +/** + * Byte range in the parent `text` buffer that this element occupies. + */ +byte_range: ByteRange, +/** + * Optional human-readable placeholder for the element, displayed in the UI. + */ +placeholder: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts new file mode 100644 index 0000000000..639e29f9d7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ThreadNameUpdatedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadId } from "./ThreadId"; + +export type ThreadNameUpdatedEvent = { thread_id: ThreadId, thread_name?: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts new file mode 100644 index 0000000000..30bc64c9c1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ThreadRolledBackEvent.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadRolledBackEvent = { +/** + * Number of user turns that were removed from context. + */ +num_turns: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts new file mode 100644 index 0000000000..f58b574641 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenCountEvent.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RateLimitSnapshot } from "./RateLimitSnapshot"; +import type { TokenUsageInfo } from "./TokenUsageInfo"; + +export type TokenCountEvent = { info: TokenUsageInfo | null, rate_limits: RateLimitSnapshot | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts new file mode 100644 index 0000000000..41186b25b9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenUsage.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TokenUsage = { input_tokens: number, cached_input_tokens: number, output_tokens: number, reasoning_output_tokens: number, total_tokens: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts b/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts new file mode 100644 index 0000000000..cb15de42e7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TokenUsageInfo.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TokenUsage } from "./TokenUsage"; + +export type TokenUsageInfo = { total_token_usage: TokenUsage, last_token_usage: TokenUsage, model_context_window: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts new file mode 100644 index 0000000000..f07cde6292 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnAbortReason.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnAbortReason = "interrupted" | "replaced" | "review_ended"; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts new file mode 100644 index 0000000000..0b4e9075b3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnAbortedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnAbortReason } from "./TurnAbortReason"; + +export type TurnAbortedEvent = { turn_id: string | null, reason: TurnAbortReason, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts new file mode 100644 index 0000000000..6987d59f98 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnCompleteEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnCompleteEvent = { turn_id: string, last_agent_message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts new file mode 100644 index 0000000000..52e3df09b0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnDiffEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnDiffEvent = { unified_diff: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts b/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts new file mode 100644 index 0000000000..0f2ea12a21 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnItem.ts @@ -0,0 +1,11 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AgentMessageItem } from "./AgentMessageItem"; +import type { ContextCompactionItem } from "./ContextCompactionItem"; +import type { PlanItem } from "./PlanItem"; +import type { ReasoningItem } from "./ReasoningItem"; +import type { UserMessageItem } from "./UserMessageItem"; +import type { WebSearchItem } from "./WebSearchItem"; + +export type TurnItem = { "type": "UserMessage" } & UserMessageItem | { "type": "AgentMessage" } & AgentMessageItem | { "type": "Plan" } & PlanItem | { "type": "Reasoning" } & ReasoningItem | { "type": "WebSearch" } & WebSearchItem | { "type": "ContextCompaction" } & ContextCompactionItem; diff --git a/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts new file mode 100644 index 0000000000..14c0d76707 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/TurnStartedEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ModeKind } from "./ModeKind"; + +export type TurnStartedEvent = { turn_id: string, model_context_window: bigint | null, collaboration_mode_kind: ModeKind, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts new file mode 100644 index 0000000000..2d94e2e18d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UndoCompletedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UndoCompletedEvent = { success: boolean, message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts new file mode 100644 index 0000000000..712082adff --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UndoStartedEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UndoStartedEvent = { message: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts b/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts new file mode 100644 index 0000000000..61613fcb5f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UpdatePlanArgs.ts @@ -0,0 +1,10 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PlanItemArg } from "./PlanItemArg"; + +export type UpdatePlanArgs = { +/** + * Arguments for the `update_plan` todo/checklist tool (not plan mode). + */ +explanation: string | null, plan: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserInput.ts b/codex-rs/app-server-protocol/schema/typescript/UserInput.ts new file mode 100644 index 0000000000..e6a9c3a580 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserInput.ts @@ -0,0 +1,16 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +/** + * User input + */ +export type UserInput = { "type": "text", text: string, +/** + * UI-defined spans within `text` that should be treated as special elements. + * These are byte ranges into the UTF-8 `text` buffer and are used to render + * or persist rich input markers (e.g., image placeholders) across history + * and resume without mutating the literal text. + */ +text_elements: Array, } | { "type": "image", image_url: string, } | { "type": "local_image", path: string, } | { "type": "skill", name: string, path: string, } | { "type": "mention", name: string, path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts new file mode 100644 index 0000000000..2fde364d67 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserMessageEvent.ts @@ -0,0 +1,22 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TextElement } from "./TextElement"; + +export type UserMessageEvent = { message: string, +/** + * Image URLs sourced from `UserInput::Image`. These are safe + * to replay in legacy UI history events and correspond to images sent to + * the model. + */ +images: Array | null, +/** + * Local file paths sourced from `UserInput::LocalImage`. These are kept so + * the UI can reattach images when editing history, and should not be sent + * to the model or treated as API-ready URLs. + */ +local_images: Array, +/** + * UI-defined spans within `message` used to render or persist special elements. + */ +text_elements: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts b/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts new file mode 100644 index 0000000000..df856287a5 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/UserMessageItem.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { UserInput } from "./UserInput"; + +export type UserMessageItem = { id: string, content: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts b/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts new file mode 100644 index 0000000000..76541a773a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ViewImageToolCallEvent.ts @@ -0,0 +1,13 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ViewImageToolCallEvent = { +/** + * Identifier for the originating tool call. + */ +call_id: string, +/** + * Local filesystem path provided to the tool. + */ +path: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts new file mode 100644 index 0000000000..35ec40f7cd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WarningEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WarningEvent = { message: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts new file mode 100644 index 0000000000..4a8d881914 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchBeginEvent.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WebSearchBeginEvent = { call_id: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts new file mode 100644 index 0000000000..5b8b67c28b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchEndEvent.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WebSearchAction } from "./WebSearchAction"; + +export type WebSearchEndEvent = { call_id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts b/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts new file mode 100644 index 0000000000..46b1406519 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/WebSearchItem.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WebSearchAction } from "./WebSearchAction"; + +export type WebSearchItem = { id: string, query: string, action: WebSearchAction, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index fa1284843d..adaeb4c26c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -1,20 +1,69 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! export type { AbsolutePathBuf } from "./AbsolutePathBuf"; +export type { AgentMessageContent } from "./AgentMessageContent"; +export type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent"; +export type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent"; +export type { AgentMessageEvent } from "./AgentMessageEvent"; +export type { AgentMessageItem } from "./AgentMessageItem"; +export type { AgentReasoningDeltaEvent } from "./AgentReasoningDeltaEvent"; +export type { AgentReasoningEvent } from "./AgentReasoningEvent"; +export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent"; +export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent"; +export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent"; +export type { AgentStatus } from "./AgentStatus"; export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; +export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent"; export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; +export type { AskForApproval } from "./AskForApproval"; export type { AuthMode } from "./AuthMode"; +export type { BackgroundEventEvent } from "./BackgroundEventEvent"; +export type { ByteRange } from "./ByteRange"; +export type { CallToolResult } from "./CallToolResult"; export type { ClientInfo } from "./ClientInfo"; export type { ClientNotification } from "./ClientNotification"; export type { ClientRequest } from "./ClientRequest"; +export type { CodexErrorInfo } from "./CodexErrorInfo"; +export type { CollabAgentInteractionBeginEvent } from "./CollabAgentInteractionBeginEvent"; +export type { CollabAgentInteractionEndEvent } from "./CollabAgentInteractionEndEvent"; +export type { CollabAgentRef } from "./CollabAgentRef"; +export type { CollabAgentSpawnBeginEvent } from "./CollabAgentSpawnBeginEvent"; +export type { CollabAgentSpawnEndEvent } from "./CollabAgentSpawnEndEvent"; +export type { CollabAgentStatusEntry } from "./CollabAgentStatusEntry"; +export type { CollabCloseBeginEvent } from "./CollabCloseBeginEvent"; +export type { CollabCloseEndEvent } from "./CollabCloseEndEvent"; +export type { CollabResumeBeginEvent } from "./CollabResumeBeginEvent"; +export type { CollabResumeEndEvent } from "./CollabResumeEndEvent"; +export type { CollabWaitingBeginEvent } from "./CollabWaitingBeginEvent"; +export type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent"; export type { CollaborationMode } from "./CollaborationMode"; export type { ContentItem } from "./ContentItem"; +export type { ContextCompactedEvent } from "./ContextCompactedEvent"; +export type { ContextCompactionItem } from "./ContextCompactionItem"; export type { ConversationGitInfo } from "./ConversationGitInfo"; export type { ConversationSummary } from "./ConversationSummary"; +export type { CreditsSnapshot } from "./CreditsSnapshot"; +export type { CustomPrompt } from "./CustomPrompt"; +export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent"; +export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; +export type { DynamicToolCallRequest } from "./DynamicToolCallRequest"; +export type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent"; +export type { ElicitationRequestEvent } from "./ElicitationRequestEvent"; +export type { ErrorEvent } from "./ErrorEvent"; +export type { EventMsg } from "./EventMsg"; +export type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent"; export type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams"; export type { ExecCommandApprovalResponse } from "./ExecCommandApprovalResponse"; +export type { ExecCommandBeginEvent } from "./ExecCommandBeginEvent"; +export type { ExecCommandEndEvent } from "./ExecCommandEndEvent"; +export type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent"; +export type { ExecCommandSource } from "./ExecCommandSource"; +export type { ExecCommandStatus } from "./ExecCommandStatus"; +export type { ExecOutputStream } from "./ExecOutputStream"; export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +export type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent"; export type { FileChange } from "./FileChange"; +export type { FileSystemPermissions } from "./FileSystemPermissions"; export type { ForcedLoginMethod } from "./ForcedLoginMethod"; export type { FunctionCallOutputBody } from "./FunctionCallOutputBody"; export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; @@ -28,44 +77,135 @@ export type { GetAuthStatusParams } from "./GetAuthStatusParams"; export type { GetAuthStatusResponse } from "./GetAuthStatusResponse"; export type { GetConversationSummaryParams } from "./GetConversationSummaryParams"; export type { GetConversationSummaryResponse } from "./GetConversationSummaryResponse"; +export type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent"; export type { GhostCommit } from "./GhostCommit"; export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams"; export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse"; export type { GitSha } from "./GitSha"; +export type { HistoryEntry } from "./HistoryEntry"; export type { InitializeCapabilities } from "./InitializeCapabilities"; export type { InitializeParams } from "./InitializeParams"; export type { InitializeResponse } from "./InitializeResponse"; export type { InputModality } from "./InputModality"; +export type { ItemCompletedEvent } from "./ItemCompletedEvent"; +export type { ItemStartedEvent } from "./ItemStartedEvent"; +export type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent"; +export type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent"; +export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent"; export type { LocalShellAction } from "./LocalShellAction"; export type { LocalShellExecAction } from "./LocalShellExecAction"; export type { LocalShellStatus } from "./LocalShellStatus"; export type { MacOsAutomationValue } from "./MacOsAutomationValue"; +export type { MacOsPermissions } from "./MacOsPermissions"; export type { MacOsPreferencesValue } from "./MacOsPreferencesValue"; +export type { McpAuthStatus } from "./McpAuthStatus"; +export type { McpInvocation } from "./McpInvocation"; +export type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent"; +export type { McpStartupCompleteEvent } from "./McpStartupCompleteEvent"; +export type { McpStartupFailure } from "./McpStartupFailure"; +export type { McpStartupStatus } from "./McpStartupStatus"; +export type { McpStartupUpdateEvent } from "./McpStartupUpdateEvent"; +export type { McpToolCallBeginEvent } from "./McpToolCallBeginEvent"; +export type { McpToolCallEndEvent } from "./McpToolCallEndEvent"; export type { MessagePhase } from "./MessagePhase"; export type { ModeKind } from "./ModeKind"; +export type { ModelRerouteEvent } from "./ModelRerouteEvent"; +export type { ModelRerouteReason } from "./ModelRerouteReason"; +export type { NetworkAccess } from "./NetworkAccess"; +export type { NetworkApprovalContext } from "./NetworkApprovalContext"; +export type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol"; export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment"; export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction"; export type { ParsedCommand } from "./ParsedCommand"; +export type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent"; +export type { PatchApplyEndEvent } from "./PatchApplyEndEvent"; +export type { PatchApplyStatus } from "./PatchApplyStatus"; +export type { PermissionProfile } from "./PermissionProfile"; export type { Personality } from "./Personality"; +export type { PlanDeltaEvent } from "./PlanDeltaEvent"; +export type { PlanItem } from "./PlanItem"; +export type { PlanItemArg } from "./PlanItemArg"; export type { PlanType } from "./PlanType"; +export type { RateLimitSnapshot } from "./RateLimitSnapshot"; +export type { RateLimitWindow } from "./RateLimitWindow"; +export type { RawResponseItemEvent } from "./RawResponseItemEvent"; +export type { ReadOnlyAccess } from "./ReadOnlyAccess"; +export type { RealtimeAudioFrame } from "./RealtimeAudioFrame"; +export type { RealtimeConversationClosedEvent } from "./RealtimeConversationClosedEvent"; +export type { RealtimeConversationRealtimeEvent } from "./RealtimeConversationRealtimeEvent"; +export type { RealtimeConversationStartedEvent } from "./RealtimeConversationStartedEvent"; +export type { RealtimeEvent } from "./RealtimeEvent"; +export type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage"; +export type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested"; +export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent"; export type { ReasoningEffort } from "./ReasoningEffort"; +export type { ReasoningItem } from "./ReasoningItem"; export type { ReasoningItemContent } from "./ReasoningItemContent"; export type { ReasoningItemReasoningSummary } from "./ReasoningItemReasoningSummary"; +export type { ReasoningRawContentDeltaEvent } from "./ReasoningRawContentDeltaEvent"; export type { ReasoningSummary } from "./ReasoningSummary"; +export type { RejectConfig } from "./RejectConfig"; +export type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent"; +export type { RemoteSkillSummary } from "./RemoteSkillSummary"; export type { RequestId } from "./RequestId"; +export type { RequestUserInputEvent } from "./RequestUserInputEvent"; +export type { RequestUserInputQuestion } from "./RequestUserInputQuestion"; +export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionOption"; export type { Resource } from "./Resource"; export type { ResourceTemplate } from "./ResourceTemplate"; export type { ResponseItem } from "./ResponseItem"; +export type { ReviewCodeLocation } from "./ReviewCodeLocation"; export type { ReviewDecision } from "./ReviewDecision"; +export type { ReviewFinding } from "./ReviewFinding"; +export type { ReviewLineRange } from "./ReviewLineRange"; +export type { ReviewOutputEvent } from "./ReviewOutputEvent"; +export type { ReviewRequest } from "./ReviewRequest"; +export type { ReviewTarget } from "./ReviewTarget"; +export type { SandboxPolicy } from "./SandboxPolicy"; export type { ServerNotification } from "./ServerNotification"; export type { ServerRequest } from "./ServerRequest"; export type { ServiceTier } from "./ServiceTier"; +export type { SessionConfiguredEvent } from "./SessionConfiguredEvent"; +export type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime"; export type { SessionSource } from "./SessionSource"; export type { Settings } from "./Settings"; +export type { SkillDependencies } from "./SkillDependencies"; +export type { SkillErrorInfo } from "./SkillErrorInfo"; +export type { SkillInterface } from "./SkillInterface"; +export type { SkillMetadata } from "./SkillMetadata"; +export type { SkillScope } from "./SkillScope"; +export type { SkillToolDependency } from "./SkillToolDependency"; +export type { SkillsListEntry } from "./SkillsListEntry"; +export type { StepStatus } from "./StepStatus"; +export type { StreamErrorEvent } from "./StreamErrorEvent"; export type { SubAgentSource } from "./SubAgentSource"; +export type { TerminalInteractionEvent } from "./TerminalInteractionEvent"; +export type { TextElement } from "./TextElement"; export type { ThreadId } from "./ThreadId"; +export type { ThreadNameUpdatedEvent } from "./ThreadNameUpdatedEvent"; +export type { ThreadRolledBackEvent } from "./ThreadRolledBackEvent"; +export type { TokenCountEvent } from "./TokenCountEvent"; +export type { TokenUsage } from "./TokenUsage"; +export type { TokenUsageInfo } from "./TokenUsageInfo"; export type { Tool } from "./Tool"; +export type { TurnAbortReason } from "./TurnAbortReason"; +export type { TurnAbortedEvent } from "./TurnAbortedEvent"; +export type { TurnCompleteEvent } from "./TurnCompleteEvent"; +export type { TurnDiffEvent } from "./TurnDiffEvent"; +export type { TurnItem } from "./TurnItem"; +export type { TurnStartedEvent } from "./TurnStartedEvent"; +export type { UndoCompletedEvent } from "./UndoCompletedEvent"; +export type { UndoStartedEvent } from "./UndoStartedEvent"; +export type { UpdatePlanArgs } from "./UpdatePlanArgs"; +export type { UserInput } from "./UserInput"; +export type { UserMessageEvent } from "./UserMessageEvent"; +export type { UserMessageItem } from "./UserMessageItem"; export type { Verbosity } from "./Verbosity"; +export type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent"; +export type { WarningEvent } from "./WarningEvent"; export type { WebSearchAction } from "./WebSearchAction"; +export type { WebSearchBeginEvent } from "./WebSearchBeginEvent"; +export type { WebSearchEndEvent } from "./WebSearchEndEvent"; +export type { WebSearchItem } from "./WebSearchItem"; export type { WebSearchMode } from "./WebSearchMode"; export * as v2 from "./v2"; diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 2802a87480..aad5ab7233 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -117,6 +117,7 @@ pub fn generate_ts_with_options( ServerRequest::export_all_to(out_dir)?; export_server_responses(out_dir)?; ServerNotification::export_all_to(out_dir)?; + EventMsg::export_all_to(out_dir)?; if !options.experimental_api { filter_experimental_ts(out_dir)?; @@ -1910,6 +1911,7 @@ mod tests { client_request_ts.contains("MockExperimentalMethodParams"), false ); + assert_eq!(output_dir.join("EventMsg.ts").exists(), true); let thread_start_ts = fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?; assert_eq!(thread_start_ts.contains("mockExperimentalField"), false);