mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
warning
This commit is contained in:
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -1144,6 +1144,7 @@ dependencies = [
|
||||
"codex-apply-patch",
|
||||
"codex-arg0",
|
||||
"codex-async-utils",
|
||||
"codex-client",
|
||||
"codex-execpolicy",
|
||||
"codex-file-search",
|
||||
"codex-git",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "codex-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-core"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@@ -18,12 +18,13 @@ askama = { workspace = true }
|
||||
async-channel = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
chardetng = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-api = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-apply-patch = { workspace = true }
|
||||
codex-async-utils = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-file-search = { workspace = true }
|
||||
codex-git = { workspace = true }
|
||||
@@ -37,8 +38,8 @@ codex-utils-string = { workspace = true }
|
||||
codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" }
|
||||
dirs = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
env-flags = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
env-flags = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
@@ -46,8 +47,10 @@ indexmap = { workspace = true }
|
||||
keyring = { workspace = true, features = ["crypto-rust"] }
|
||||
libc = { workspace = true }
|
||||
mcp-types = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
os_info = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
regex-lite = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "stream"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -57,9 +60,6 @@ sha2 = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
test-case = "3.3.1"
|
||||
test-log = { workspace = true }
|
||||
@@ -83,15 +83,16 @@ toml_edit = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
tree-sitter = { workspace = true }
|
||||
tree-sitter-bash = { workspace = true }
|
||||
url = { workspace = true }
|
||||
uuid = { workspace = true, features = ["serde", "v4", "v5"] }
|
||||
which = { workspace = true }
|
||||
wildmatch = { workspace = true }
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
keyring = { workspace = true, features = ["linux-native-async-persistent"] }
|
||||
landlock = { workspace = true }
|
||||
seccompiler = { workspace = true }
|
||||
keyring = { workspace = true, features = ["linux-native-async-persistent"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
|
||||
@@ -52,6 +52,7 @@ use serde_json::Value;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time::sleep_until;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::debug;
|
||||
use tracing::error;
|
||||
@@ -106,6 +107,7 @@ use crate::shell;
|
||||
use crate::state::ActiveTurn;
|
||||
use crate::state::SessionServices;
|
||||
use crate::state::SessionState;
|
||||
use crate::status::IdleWarning;
|
||||
use crate::tasks::GhostSnapshotTask;
|
||||
use crate::tasks::ReviewTask;
|
||||
use crate::tasks::SessionTask;
|
||||
@@ -2187,6 +2189,8 @@ async fn try_run_turn(
|
||||
.or_cancel(&cancellation_token)
|
||||
.await??;
|
||||
|
||||
let mut idle_warning = IdleWarning::default();
|
||||
|
||||
let tool_runtime = ToolCallRuntime::new(
|
||||
Arc::clone(&router),
|
||||
Arc::clone(&sess),
|
||||
@@ -2202,7 +2206,24 @@ async fn try_run_turn(
|
||||
// Poll the next item from the model stream. We must inspect *both* Ok and Err
|
||||
// cases so that transient stream failures (e.g., dropped SSE connection before
|
||||
// `response.completed`) bubble up and trigger the caller's retry logic.
|
||||
let event = match stream.next().or_cancel(&cancellation_token).await {
|
||||
let event = tokio::select! {
|
||||
biased;
|
||||
result = stream.next().or_cancel(&cancellation_token) => result,
|
||||
_ = sleep_until(idle_warning.deadline()) => {
|
||||
if let Some(message) = idle_warning.maybe_warning_message().await {
|
||||
sess.send_event(
|
||||
&turn_context,
|
||||
EventMsg::Warning(WarningEvent { message }),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
idle_warning.mark_event();
|
||||
|
||||
let event = match event {
|
||||
Ok(event) => event,
|
||||
Err(codex_async_utils::CancelErr::Cancelled) => {
|
||||
let processed_items = output.try_collect().await?;
|
||||
|
||||
@@ -42,6 +42,7 @@ pub mod parse_command;
|
||||
pub mod powershell;
|
||||
mod response_processing;
|
||||
pub mod sandboxing;
|
||||
pub mod status;
|
||||
mod text_encoding;
|
||||
pub mod token_data;
|
||||
mod truncate;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "codex-status"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-client = { path = "../codex-client" }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
@@ -1,217 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use codex_client::Request;
|
||||
use codex_client::ReqwestTransport;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use strum::Display;
|
||||
|
||||
const STATUS_WIDGET_URL: &str = "https://status.openai.com/proxy/status.openai.com";
|
||||
const CODEX_COMPONENT_NAME: &str = "Codex";
|
||||
|
||||
#[derive(Debug, Clone, Display, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ComponentHealth {
|
||||
Operational,
|
||||
DegradedPerformance,
|
||||
PartialOutage,
|
||||
MajorOutage,
|
||||
UnderMaintenance,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ComponentHealth {
|
||||
fn operational() -> Self {
|
||||
Self::Operational
|
||||
}
|
||||
|
||||
pub fn is_operational(self) -> bool {
|
||||
self == Self::Operational
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_codex_health() -> Result<ComponentHealth> {
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.context("building HTTP client")?;
|
||||
|
||||
let response = ReqwestTransport::new(client)
|
||||
.execute(Request::new(
|
||||
http::Method::GET,
|
||||
STATUS_WIDGET_URL.to_string(),
|
||||
))
|
||||
.await
|
||||
.context("requesting status widget")?;
|
||||
|
||||
let content_type = response
|
||||
.headers
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
|
||||
if !content_type.contains("json") {
|
||||
let snippet = String::from_utf8_lossy(&response.body)
|
||||
.chars()
|
||||
.take(200)
|
||||
.collect::<String>();
|
||||
|
||||
bail!(
|
||||
"Expected JSON from {STATUS_WIDGET_URL}: Content-Type={content_type}. Body starts with: {snippet:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let payload: StatusPayload =
|
||||
serde_json::from_slice(&response.body).context("parsing status widget JSON")?;
|
||||
|
||||
derive_component_health(&payload, CODEX_COMPONENT_NAME)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct StatusPayload {
|
||||
#[serde(default)]
|
||||
summary: Summary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct Summary {
|
||||
#[serde(default)]
|
||||
components: Vec<Component>,
|
||||
#[serde(default)]
|
||||
affected_components: Vec<AffectedComponent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct Component {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct AffectedComponent {
|
||||
component_id: String,
|
||||
#[serde(default = "ComponentHealth::operational")]
|
||||
status: ComponentHealth,
|
||||
}
|
||||
|
||||
fn derive_component_health(
|
||||
payload: &StatusPayload,
|
||||
component_name: &str,
|
||||
) -> Result<ComponentHealth> {
|
||||
let component = payload
|
||||
.summary
|
||||
.components
|
||||
.iter()
|
||||
.find(|component| component.name == component_name)
|
||||
.ok_or_else(|| anyhow!("Component {component_name:?} not found in status summary"))?;
|
||||
|
||||
let status = payload
|
||||
.summary
|
||||
.affected_components
|
||||
.iter()
|
||||
.find(|affected| affected.component_id == component.id)
|
||||
.map(|affected| affected.status)
|
||||
.unwrap_or(ComponentHealth::Operational);
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn defaults_to_operational_when_not_affected() {
|
||||
let payload = serde_json::from_value::<StatusPayload>(json!({
|
||||
"summary": {
|
||||
"id": "sum-1",
|
||||
"name": "OpenAI",
|
||||
"components": [
|
||||
{"id": "cmp-1", "name": "Codex", "status_page_id": "page-1"},
|
||||
{"id": "cmp-2", "name": "Chat", "status_page_id": "page-1"}
|
||||
]
|
||||
}
|
||||
}))
|
||||
.expect("valid payload");
|
||||
|
||||
let status = derive_component_health(&payload, "Codex").expect("codex component exists");
|
||||
|
||||
assert_eq!(status, ComponentHealth::Operational);
|
||||
assert!(status.is_operational());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_affected_component_status() {
|
||||
let payload = serde_json::from_value::<StatusPayload>(json!({
|
||||
"summary": {
|
||||
"id": "sum-1",
|
||||
"name": "OpenAI",
|
||||
"components": [
|
||||
{"id": "cmp-1", "name": "Codex", "status_page_id": "page-1"}
|
||||
],
|
||||
"affected_components": [
|
||||
{"component_id": "cmp-1", "status": "major_outage"}
|
||||
]
|
||||
}
|
||||
}))
|
||||
.expect("valid payload");
|
||||
|
||||
let status = derive_component_health(&payload, "Codex").expect("codex component exists");
|
||||
|
||||
assert_eq!(status, ComponentHealth::MajorOutage);
|
||||
assert!(!status.is_operational());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_status_is_preserved_as_unknown() {
|
||||
let payload = serde_json::from_value::<StatusPayload>(json!({
|
||||
"summary": {
|
||||
"id": "sum-1",
|
||||
"name": "OpenAI",
|
||||
"components": [
|
||||
{"id": "cmp-1", "name": "Codex", "status_page_id": "page-1"}
|
||||
],
|
||||
"affected_components": [
|
||||
{"component_id": "cmp-1", "status": "custom_status"}
|
||||
]
|
||||
}
|
||||
}))
|
||||
.expect("valid payload");
|
||||
|
||||
let status = derive_component_health(&payload, "Codex").expect("codex component exists");
|
||||
|
||||
assert_eq!(status, ComponentHealth::Unknown);
|
||||
assert!(!status.is_operational());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_component_returns_error() {
|
||||
let payload = serde_json::from_value::<StatusPayload>(json!({
|
||||
"summary": {
|
||||
"id": "sum-1",
|
||||
"name": "OpenAI",
|
||||
"components": [],
|
||||
"affected_components": []
|
||||
}
|
||||
}))
|
||||
.expect("valid payload");
|
||||
|
||||
let error =
|
||||
derive_component_health(&payload, "Codex").expect_err("missing component should error");
|
||||
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("Component \"Codex\" not found in status summary")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user