mirror of
https://github.com/openai/codex.git
synced 2026-05-03 10:56:37 +00:00
`codex-core` had accumulated command parsing and command safety logic (`bash`, `powershell`, `parse_command`, and `command_safety`) that is logically cohesive but orthogonal to most core session/runtime logic. Keeping this code in `codex-core` made the crate increasingly monolithic and raised iteration cost for unrelated core changes. This change extracts that surface into a dedicated crate, `codex-command`, while preserving existing `codex_core::...` call sites via re-exports. ## Why this refactor During analysis, command parsing/safety stood out as a good first split because it has: - a clear domain boundary (shell parsing + safety classification) - relatively self-contained dependencies (notably `tree-sitter` / `tree-sitter-bash`) - a meaningful standalone test surface (`134` tests moved with the crate) - many downstream uses that benefit from independent compilation and caching The practical problem was build latency from a large `codex-core` compile/test graph. Clean-build timings before and after this split showed measurable wins: - `cargo check -p codex-core`: `57.08s` -> `53.54s` (~`6.2%` faster) - `cargo test -p codex-core --no-run`: `2m39.9s` -> `2m20s` (~`12.4%` faster) - `codex-core lib` compile unit: `57.18s` -> `49.67s` (~`13.1%` faster) - `codex-core lib(test)` compile unit: `60.87s` -> `53.21s` (~`12.6%` faster) This gives a concrete reduction in core build overhead without changing behavior. ## What changed ### New crate - Added `codex-rs/command` as workspace crate `codex-command`. - Added: - `command/src/lib.rs` - `command/src/bash.rs` - `command/src/powershell.rs` - `command/src/parse_command.rs` - `command/src/command_safety/*` - `command/src/shell_detect.rs` - `command/BUILD.bazel` ### Code moved out of `codex-core` - Moved modules from `core/src` into `command/src`: - `bash.rs` - `powershell.rs` - `parse_command.rs` - `command_safety/*` ### Dependency graph updates - Added workspace member/dependency entries for `codex-command` in `codex-rs/Cargo.toml`. - Added `codex-command` dependency to `codex-rs/core/Cargo.toml`. - Removed `tree-sitter` and `tree-sitter-bash` from `codex-core` direct deps (now owned by `codex-command`). ### API compatibility for callers To avoid immediate downstream churn, `codex-core` now re-exports the moved modules/functions: - `codex_command::bash` - `codex_command::powershell` - `codex_command::parse_command` - `codex_command::is_safe_command` - `codex_command::is_dangerous_command` This keeps existing `codex_core::...` paths working while enabling gradual migration to direct `codex-command` usage. ### Internal decoupling detail - Added `command::shell_detect` so moved `bash`/`powershell` logic no longer depends on core shell internals. - Adjusted PowerShell helper visibility in `codex-command` for existing core test usage (`UTF8` prefix helper + executable discovery functions). ## Validation - `just fmt` - `just fix -p codex-command -p codex-core` - `cargo test -p codex-command` (`134` passed) - `cargo test -p codex-core --no-run` - `cargo test -p codex-core shell_command_handler` ## Notes / follow-up This commit intentionally prioritizes boundary extraction and compatibility. A follow-up can migrate downstream crates to depend directly on `codex-command` (instead of through `codex-core` re-exports) to realize additional incremental build wins.
202 lines
5.0 KiB
PowerShell
202 lines
5.0 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
|
|
$payload = $env:CODEX_POWERSHELL_PAYLOAD
|
|
if ([string]::IsNullOrEmpty($payload)) {
|
|
Write-Output '{"status":"parse_failed"}'
|
|
exit 0
|
|
}
|
|
|
|
try {
|
|
$source =
|
|
[System.Text.Encoding]::Unicode.GetString(
|
|
[System.Convert]::FromBase64String($payload)
|
|
)
|
|
} catch {
|
|
Write-Output '{"status":"parse_failed"}'
|
|
exit 0
|
|
}
|
|
|
|
$tokens = $null
|
|
$errors = $null
|
|
|
|
$ast = $null
|
|
try {
|
|
$ast = [System.Management.Automation.Language.Parser]::ParseInput(
|
|
$source,
|
|
[ref]$tokens,
|
|
[ref]$errors
|
|
)
|
|
} catch {
|
|
Write-Output '{"status":"parse_failed"}'
|
|
exit 0
|
|
}
|
|
|
|
if ($errors.Count -gt 0) {
|
|
Write-Output '{"status":"parse_errors"}'
|
|
exit 0
|
|
}
|
|
|
|
function Convert-CommandElement {
|
|
param($element)
|
|
|
|
if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
|
|
return @($element.Value)
|
|
}
|
|
|
|
if ($element -is [System.Management.Automation.Language.ExpandableStringExpressionAst]) {
|
|
if ($element.NestedExpressions.Count -gt 0) {
|
|
return $null
|
|
}
|
|
return @($element.Value)
|
|
}
|
|
|
|
if ($element -is [System.Management.Automation.Language.ConstantExpressionAst]) {
|
|
return @($element.Value.ToString())
|
|
}
|
|
|
|
if ($element -is [System.Management.Automation.Language.CommandParameterAst]) {
|
|
if ($element.Argument -eq $null) {
|
|
return @('-' + $element.ParameterName)
|
|
}
|
|
|
|
if ($element.Argument -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
|
|
return @('-' + $element.ParameterName, $element.Argument.Value)
|
|
}
|
|
|
|
if ($element.Argument -is [System.Management.Automation.Language.ConstantExpressionAst]) {
|
|
return @('-' + $element.ParameterName, $element.Argument.Value.ToString())
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Convert-PipelineElement {
|
|
param($element)
|
|
|
|
if ($element -is [System.Management.Automation.Language.CommandAst]) {
|
|
if ($element.Redirections.Count -gt 0) {
|
|
return $null
|
|
}
|
|
|
|
if (
|
|
$element.InvocationOperator -ne $null -and
|
|
$element.InvocationOperator -ne [System.Management.Automation.Language.TokenKind]::Unknown
|
|
) {
|
|
return $null
|
|
}
|
|
|
|
$parts = @()
|
|
foreach ($commandElement in $element.CommandElements) {
|
|
$converted = Convert-CommandElement $commandElement
|
|
if ($converted -eq $null) {
|
|
return $null
|
|
}
|
|
$parts += $converted
|
|
}
|
|
return $parts
|
|
}
|
|
|
|
if ($element -is [System.Management.Automation.Language.CommandExpressionAst]) {
|
|
if ($element.Redirections.Count -gt 0) {
|
|
return $null
|
|
}
|
|
|
|
if ($element.Expression -is [System.Management.Automation.Language.ParenExpressionAst]) {
|
|
$innerPipeline = $element.Expression.Pipeline
|
|
if ($innerPipeline -and $innerPipeline.PipelineElements.Count -eq 1) {
|
|
return Convert-PipelineElement $innerPipeline.PipelineElements[0]
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Add-CommandsFromPipelineAst {
|
|
param($pipeline, $commands)
|
|
|
|
if ($pipeline.PipelineElements.Count -eq 0) {
|
|
return $false
|
|
}
|
|
|
|
foreach ($element in $pipeline.PipelineElements) {
|
|
$words = Convert-PipelineElement $element
|
|
if ($words -eq $null -or $words.Count -eq 0) {
|
|
return $false
|
|
}
|
|
$null = $commands.Add($words)
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Add-CommandsFromPipelineChain {
|
|
param($chain, $commands)
|
|
|
|
if (-not (Add-CommandsFromPipelineBase $chain.LhsPipelineChain $commands)) {
|
|
return $false
|
|
}
|
|
|
|
if (-not (Add-CommandsFromPipelineAst $chain.RhsPipeline $commands)) {
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Add-CommandsFromPipelineBase {
|
|
param($pipeline, $commands)
|
|
|
|
if ($pipeline -is [System.Management.Automation.Language.PipelineAst]) {
|
|
return Add-CommandsFromPipelineAst $pipeline $commands
|
|
}
|
|
|
|
if ($pipeline -is [System.Management.Automation.Language.PipelineChainAst]) {
|
|
return Add-CommandsFromPipelineChain $pipeline $commands
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
$commands = [System.Collections.ArrayList]::new()
|
|
|
|
foreach ($statement in $ast.EndBlock.Statements) {
|
|
if (-not (Add-CommandsFromPipelineBase $statement $commands)) {
|
|
$commands = $null
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($commands -ne $null) {
|
|
$normalized = [System.Collections.ArrayList]::new()
|
|
foreach ($cmd in $commands) {
|
|
if ($cmd -is [string]) {
|
|
$null = $normalized.Add(@($cmd))
|
|
continue
|
|
}
|
|
|
|
if ($cmd -is [System.Array] -or $cmd -is [System.Collections.IEnumerable]) {
|
|
$null = $normalized.Add(@($cmd))
|
|
continue
|
|
}
|
|
|
|
$normalized = $null
|
|
break
|
|
}
|
|
|
|
$commands = $normalized
|
|
}
|
|
|
|
$result = if ($commands -eq $null) {
|
|
@{ status = 'unsupported' }
|
|
} else {
|
|
@{ status = 'ok'; commands = $commands }
|
|
}
|
|
|
|
,$result | ConvertTo-Json -Depth 3
|