Merge 4de61af3fb into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-02-12 01:01:31 -08:00
committed by GitHub
5 changed files with 161 additions and 107 deletions
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$#" -lt 1 ]]; then
echo "usage: $0 <target> [<target> ...]"
exit 1
fi
process_target() {
local target="$1"
local release_dir="target/${target}/release"
local dest="dist/${target}"
local repo_root
repo_root="$(pwd)"
ls -lh "${release_dir}/codex.exe"
ls -lh "${release_dir}/codex-responses-api-proxy.exe"
ls -lh "${release_dir}/codex-windows-sandbox-setup.exe"
ls -lh "${release_dir}/codex-command-runner.exe"
mkdir -p "$dest"
cp "${release_dir}/codex.exe" "$dest/codex-${target}.exe"
cp "${release_dir}/codex-responses-api-proxy.exe" "$dest/codex-responses-api-proxy-${target}.exe"
cp "${release_dir}/codex-windows-sandbox-setup.exe" "$dest/codex-windows-sandbox-setup-${target}.exe"
cp "${release_dir}/codex-command-runner.exe" "$dest/codex-command-runner-${target}.exe"
for f in "$dest"/*; do
local base
base="$(basename "$f")"
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
if [[ "$base" == *.sigstore ]]; then
continue
fi
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
if [[ "$base" == "codex-${target}.exe" ]]; then
local bundle_dir
local runner_src
local setup_src
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${target}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${target}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
done
}
for target in "$@"; do
process_target "$target"
done
+9 -93
View File
@@ -121,8 +121,8 @@ jobs:
build-windows:
needs:
- build-windows-binaries
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runs_on }}
name: Build - windows-x64 - ${{ matrix.target }}
runs-on: windows-2022
timeout-minutes: 60
permissions:
contents: read
@@ -135,16 +135,8 @@ jobs:
fail-fast: false
matrix:
include:
- runner: windows-x64
target: x86_64-pc-windows-msvc
runs_on:
group: codex-runners
labels: codex-windows-x64
- runner: windows-arm64
target: aarch64-pc-windows-msvc
runs_on:
group: codex-runners
labels: codex-windows-arm64
- target: x86_64-pc-windows-msvc
- target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v6
@@ -161,14 +153,8 @@ jobs:
name: windows-binaries-${{ matrix.target }}-helpers
path: codex-rs/target/${{ matrix.target }}/release
- name: Verify binaries
shell: bash
run: |
set -euo pipefail
ls -lh target/${{ matrix.target }}/release/codex.exe
ls -lh target/${{ matrix.target }}/release/codex-responses-api-proxy.exe
ls -lh target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe
ls -lh target/${{ matrix.target }}/release/codex-command-runner.exe
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Sign Windows binaries with Azure Trusted Signing
uses: ./.github/actions/windows-code-sign
@@ -181,81 +167,11 @@ jobs:
account-name: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }}
- name: Stage artifacts
- name: Stage and compress Windows release artifacts
shell: bash
run: |
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-responses-api-proxy.exe "$dest/codex-responses-api-proxy-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-windows-sandbox-setup.exe "$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
cp target/${{ matrix.target }}/release/codex-command-runner.exe "$dest/codex-command-runner-${{ matrix.target }}.exe"
- name: Install DotSlash
uses: facebook/install-dotslash@v2
- name: Compress artifacts
shell: bash
run: |
# Path that contains the uncompressed binaries for the current
# ${{ matrix.target }}
dest="dist/${{ matrix.target }}"
repo_root=$PWD
# For compatibility with environments that lack the `zstd` tool we
# additionally create a `.tar.gz` and `.zip` for every Windows binary.
# The end result is:
# codex-<target>.zst
# codex-<target>.tar.gz
# codex-<target>.zip
for f in "$dest"/*; do
base="$(basename "$f")"
# Skip files that are already archives (shouldn't happen, but be
# safe).
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
# Don't try to compress signature bundles.
if [[ "$base" == *.sigstore ]]; then
continue
fi
# Create per-binary tar.gz
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
# Create zip archive for Windows binaries.
# Must run from inside the dest dir so 7z won't embed the
# directory path inside the zip.
if [[ "$base" == "codex-${{ matrix.target }}.exe" ]]; then
# Bundle the sandbox helper binaries into the main codex zip so
# WinGet installs include the required helpers next to codex.exe.
# Fall back to the single-binary zip if the helpers are missing
# to avoid breaking releases.
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${{ matrix.target }}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${{ matrix.target }}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
# Use an absolute path so bundle zips land in the real dist
# dir even when 7z runs from a temp directory.
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
# Keep raw executables and produce .zst alongside them.
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
done
bash "$GITHUB_WORKSPACE/.github/scripts/stage-and-compress-windows-release-artifacts.sh" \
${{ matrix.target }}
- uses: actions/upload-artifact@v6
with:
+11
View File
@@ -254,6 +254,17 @@ pub fn process_responses_event(
"response.failed event received".into(),
)));
}
"response.incomplete" => {
let reason = event.response.as_ref().and_then(|response| {
response
.get("incomplete_details")
.and_then(|details| details.get("reason"))
.and_then(Value::as_str)
});
let reason = reason.unwrap_or("unknown");
let message = format!("Incomplete response returned, reason: {reason}");
return Err(ResponsesEventError::Api(ApiError::Stream(message)));
}
"response.completed" => {
if let Some(resp_val) = event.response {
match serde_json::from_value::<ResponseCompleted>(resp_val) {
+9 -14
View File
@@ -177,20 +177,15 @@ impl SessionState {
}
}
// Merge partial rate-limit updates: new fields overwrite existing values;
// missing fields retain prior values. If `limit_id` is absent everywhere,
// default it to `"codex"`.
// Sometimes new snapshots don't include credits or plan information.
// Preserve those from the previous snapshot when missing. For `limit_id`, treat
// missing values as the default `"codex"` bucket.
fn merge_rate_limit_fields(
previous: Option<&RateLimitSnapshot>,
mut snapshot: RateLimitSnapshot,
) -> RateLimitSnapshot {
if snapshot.limit_id.is_none() {
snapshot.limit_id = previous
.and_then(|prior| prior.limit_id.clone())
.or_else(|| Some("codex".to_string()));
}
if snapshot.limit_name.is_none() {
snapshot.limit_name = previous.and_then(|prior| prior.limit_name.clone());
snapshot.limit_id = Some("codex".to_string());
}
if snapshot.credits.is_none() {
snapshot.credits = previous.and_then(|prior| prior.credits.clone());
@@ -305,7 +300,7 @@ mod tests {
}
#[tokio::test]
async fn set_rate_limits_preserves_previous_limit_id_when_missing() {
async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_bucket() {
let session_configuration = make_session_configuration_for_tests().await;
let mut state = SessionState::new(session_configuration);
@@ -339,12 +334,12 @@ mod tests {
.latest_rate_limits
.as_ref()
.and_then(|v| v.limit_id.clone()),
Some("codex_other".to_string())
Some("codex".to_string())
);
}
#[tokio::test]
async fn set_rate_limits_accepts_new_limit_id_bucket() {
async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other() {
let session_configuration = make_session_configuration_for_tests().await;
let mut state = SessionState::new(session_configuration);
@@ -367,7 +362,7 @@ mod tests {
state.set_rate_limits(RateLimitSnapshot {
limit_id: Some("codex_other".to_string()),
limit_name: Some("codex_other".to_string()),
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 30.0,
window_minutes: Some(120),
@@ -382,7 +377,7 @@ mod tests {
state.latest_rate_limits,
Some(RateLimitSnapshot {
limit_id: Some("codex_other".to_string()),
limit_name: Some("codex_other".to_string()),
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 30.0,
window_minutes: Some(120),
+60
View File
@@ -37,6 +37,8 @@ use codex_protocol::user_input::UserInput;
use core_test_support::load_default_config_for_test;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::ev_message_item_added;
use core_test_support::responses::ev_output_text_delta;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
use core_test_support::responses::mount_sse_once_match;
@@ -1787,6 +1789,64 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
let incomplete_response = sse(vec![
ev_response_created("resp_incomplete"),
ev_message_item_added("msg_incomplete", "partial content"),
ev_output_text_delta("continued chunk"),
json!({
"type": "response.incomplete",
"response": {
"id": "resp_incomplete",
"object": "response",
"status": "incomplete",
"error": null,
"incomplete_details": {
"reason": "content_filter"
}
}
}),
]);
let responses_mock = mount_sse_once(&server, incomplete_response).await;
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.model_provider.stream_max_retries = Some(0);
})
.build(&server)
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "trigger incomplete".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
})
.await?;
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
assert!(
matches!(
error_event,
EventMsg::Error(ref err)
if err.message
== "stream disconnected before completion: Incomplete response returned, reason: content_filter"
),
"expected incomplete content filter error; got {error_event:?}"
);
assert_eq!(responses_mock.requests().len(), 1);
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn azure_overrides_assign_properties_used_for_responses_url() {
skip_if_no_network!();