[CmdletBinding()] param( [string]$CatalogUrl = 'https://api.tianjinlinghang.com/docs/assets/auto-review-20261002/linghang-self-review-model-catalog.json', [string]$CodexDirectory = '', [string]$CatalogFile = '' ) $ErrorActionPreference = 'Stop' $expectedSha256 = '344464b4bfdeff232850712d2ddd87417daa3f662df419f011c68bea8b50a028' $requiredModels = @( 'gpt-6.1-sol', 'gpt-6-astra', 'gpt-6-astra-fast', 'gpt-6-sol', 'gpt-6', 'gpt-5.6-sol', 'gpt-5.6-sol-fast', 'gpt-5.6-sol-openai-compact', 'gpt-5.6-terra', 'gpt-5.6-terra-fast', 'gpt-5.6-terra-openai-compact', 'gpt-5.6-luna', 'gpt-5.6', 'gpt-5.5', 'gpt-5.5-fast', 'gpt-5.5-openai-compact', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex-spark' ) $codexDir = if ($CodexDirectory) { [System.IO.Path]::GetFullPath($CodexDirectory) } elseif ($env:CODEX_HOME) { [System.IO.Path]::GetFullPath($env:CODEX_HOME) } else { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.codex' } $catalogDir = Join-Path $codexDir 'model-catalogs' $catalogPath = Join-Path $catalogDir 'linghang-self-review-model-catalog.json' $configPath = Join-Path $codexDir 'config.toml' $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss-fff' $tempPath = Join-Path ([System.IO.Path]::GetTempPath()) "linghang-model-catalog-$([Guid]::NewGuid().ToString('N')).json" try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 if ($CatalogFile) { Copy-Item -LiteralPath $CatalogFile -Destination $tempPath } else { Invoke-WebRequest -Uri $CatalogUrl -OutFile $tempPath -UseBasicParsing } $actualSha256 = (Get-FileHash -LiteralPath $tempPath -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualSha256 -ne $expectedSha256) { throw "Model catalog checksum failed. Actual SHA-256: $actualSha256" } $catalog = Get-Content -LiteralPath $tempPath -Raw -Encoding UTF8 | ConvertFrom-Json $models = @($catalog.models) foreach ($slug in $requiredModels) { $model = $models | Where-Object { $_.slug -eq $slug } | Select-Object -First 1 if (-not $model) { throw "Model catalog is missing: $slug" } if ($model.auto_review_model_override -ne $slug) { throw "Auto-review model does not match: $slug" } } $utf8NoBom = New-Object System.Text.UTF8Encoding($false) $tomlCatalogPath = ConvertTo-Json -InputObject $catalogPath.Replace('\', '/') -Compress $catalogLine = "model_catalog_json = $tomlCatalogPath" $originalConfig = '' if (Test-Path -LiteralPath $configPath) { $originalConfig = [System.IO.File]::ReadAllText($configPath) $section = [regex]::Match($originalConfig, '(?m)^[\t ]*\[') $split = if ($section.Success) { $section.Index } else { $originalConfig.Length } $rootConfig = $originalConfig.Substring(0, $split) $restConfig = $originalConfig.Substring($split) $linePattern = '(?m)^[\t ]*model_catalog_json[\t ]*=[^\r\n]*' $matches = [regex]::Matches($rootConfig, $linePattern) if ($matches.Count -gt 1) { throw 'config.toml has duplicate top-level model_catalog_json entries.' } if ($matches.Count -eq 1) { $match = $matches[0] $configText = $rootConfig.Substring(0, $match.Index) + $catalogLine + $rootConfig.Substring($match.Index + $match.Length) + $restConfig } else { $configText = "$catalogLine$([Environment]::NewLine)$([Environment]::NewLine)$originalConfig" } } else { New-Item -ItemType Directory -Path $codexDir -Force | Out-Null $configText = "$catalogLine$([Environment]::NewLine)" } New-Item -ItemType Directory -Path $catalogDir -Force | Out-Null $catalogChanged = -not (Test-Path -LiteralPath $catalogPath) if (-not $catalogChanged) { $catalogChanged = (Get-FileHash -LiteralPath $catalogPath -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expectedSha256 } if ($catalogChanged) { if (Test-Path -LiteralPath $catalogPath) { Copy-Item -LiteralPath $catalogPath -Destination "$catalogPath.backup-$timestamp" } Copy-Item -LiteralPath $tempPath -Destination $catalogPath -Force } if ($originalConfig -ne $configText) { if (Test-Path -LiteralPath $configPath) { if ([System.IO.File]::ReadAllText($configPath) -ne $originalConfig) { throw 'config.toml changed during installation. Please retry.' } Copy-Item -LiteralPath $configPath -Destination "$configPath.backup-$timestamp" } $newConfigPath = "$configPath.tmp-$timestamp" [System.IO.File]::WriteAllText($newConfigPath, $configText, $utf8NoBom) Move-Item -LiteralPath $newConfigPath -Destination $configPath -Force } Write-Host 'Configured: Auto-review now follows the current main model.' -ForegroundColor Green Write-Host "Model catalog: $catalogPath" Write-Host "Config file: $configPath" Write-Host 'Fully quit Codex, including its tray process, and then reopen it.' -ForegroundColor Yellow } finally { if (Test-Path -LiteralPath $tempPath) { Remove-Item -LiteralPath $tempPath -Force } }