mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
Add per-session code-mode execution limits (#37114)
## What changed - Add `create_session_with_limits` and session-scoped cell execution limits. - Clamp execute and wait yield times to the session's `max_yield_time_ms` without terminating the running cell. - Negotiate support with remote code-mode hosts and include non-default limits in `session/open`, while keeping unlimited sessions compatible with hosts and providers that do not support limits. ## Testing - Cover yield-time clamping, zero-timeout behavior, and isolation between sessions. - Cover wire serialization, capability negotiation, unsupported hosts, and shared process-host execution. GitOrigin-RevId: 9517321cd605bb87f93eeaa6ba331cc2e346e582
This commit is contained in:
@@ -30,11 +30,13 @@ use super::WireImageDetail;
|
||||
use super::WireNestedToolCall;
|
||||
use super::WireResult;
|
||||
use super::WireRuntimeResponse;
|
||||
use super::WireSessionCellExecutionLimits;
|
||||
use super::WireToolDefinition;
|
||||
use super::WireToolKind;
|
||||
use super::WireToolName;
|
||||
use super::WireWaitOutcome;
|
||||
use super::WireWaitRequest;
|
||||
use crate::CodeModeSessionCellExecutionLimits;
|
||||
use crate::ExecuteRequest;
|
||||
|
||||
fn session_id() -> SessionId {
|
||||
@@ -363,6 +365,61 @@ fn handshake_v1_variants_are_pinned() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_session_serializes_optional_cell_execution_limits() {
|
||||
assert_wire_round_trip(
|
||||
HostRequest::OpenSession {
|
||||
session_id: session_id(),
|
||||
cell_execution_limits: Some(WireSessionCellExecutionLimits {
|
||||
max_yield_time_ms: Some(250),
|
||||
max_heap_size_bytes: Some(16 * 1024 * 1024),
|
||||
}),
|
||||
},
|
||||
json!({
|
||||
"method": "session/open",
|
||||
"sessionId": "session-1",
|
||||
"cellExecutionLimits": {
|
||||
"maxYieldTimeMs": 250,
|
||||
"maxHeapSizeBytes": 16 * 1024 * 1024,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_cell_execution_limits_convert_between_domain_and_wire() {
|
||||
let domain_limits = CodeModeSessionCellExecutionLimits {
|
||||
max_yield_time_ms: Some(250),
|
||||
max_heap_size_bytes: Some(16_usize * 1024 * 1024),
|
||||
};
|
||||
let wire_limits = WireSessionCellExecutionLimits {
|
||||
max_yield_time_ms: Some(250),
|
||||
max_heap_size_bytes: Some(16_u64 * 1024 * 1024),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
WireSessionCellExecutionLimits::try_from(domain_limits.clone())
|
||||
.expect("domain limits convert to wire limits"),
|
||||
wire_limits
|
||||
);
|
||||
assert_eq!(
|
||||
CodeModeSessionCellExecutionLimits::try_from(wire_limits)
|
||||
.expect("wire limits convert to domain limits"),
|
||||
domain_limits
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
#[test]
|
||||
fn session_cell_execution_limits_reject_heap_sizes_that_exceed_usize() {
|
||||
let wire_limits = WireSessionCellExecutionLimits {
|
||||
max_yield_time_ms: None,
|
||||
max_heap_size_bytes: Some(u64::from(u32::MAX) + 1),
|
||||
};
|
||||
|
||||
assert!(CodeModeSessionCellExecutionLimits::try_from(wire_limits).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_to_host_v1_variants_are_pinned() {
|
||||
let execute_request = execute_request();
|
||||
@@ -371,6 +428,7 @@ fn client_to_host_v1_variants_are_pinned() {
|
||||
request_id(/*value*/ 1),
|
||||
HostRequest::OpenSession {
|
||||
session_id: session_id(),
|
||||
cell_execution_limits: None,
|
||||
},
|
||||
json!({ "method": "session/open", "sessionId": "session-1" }),
|
||||
),
|
||||
@@ -810,6 +868,17 @@ fn every_nested_v1_object_rejects_unknown_fields() {
|
||||
}))
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
serde_json::from_value::<HostRequest>(json!({
|
||||
"method": "session/open",
|
||||
"sessionId": "session-1",
|
||||
"cellExecutionLimits": {
|
||||
"maxYieldTimeMs": 250,
|
||||
"unexpected": true,
|
||||
},
|
||||
}))
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
serde_json::from_value::<WireExecuteRequest>(json!({
|
||||
"tool_call_id": "call-1",
|
||||
|
||||
@@ -17,6 +17,7 @@ use super::WireCellId;
|
||||
use super::WireExecuteRequest;
|
||||
use super::WireNestedToolCall;
|
||||
use super::WireRuntimeResponse;
|
||||
use super::WireSessionCellExecutionLimits;
|
||||
use super::WireWaitOutcome;
|
||||
use super::WireWaitRequest;
|
||||
|
||||
@@ -242,7 +243,11 @@ impl HostToClient {
|
||||
#[serde(deny_unknown_fields, tag = "method", rename_all_fields = "camelCase")]
|
||||
pub enum HostRequest {
|
||||
#[serde(rename = "session/open")]
|
||||
OpenSession { session_id: SessionId },
|
||||
OpenSession {
|
||||
session_id: SessionId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
cell_execution_limits: Option<WireSessionCellExecutionLimits>,
|
||||
},
|
||||
#[serde(rename = "session/execute")]
|
||||
Execute {
|
||||
session_id: SessionId,
|
||||
|
||||
@@ -16,6 +16,9 @@ pub const MAX_PENDING_DELEGATE_CALLS: usize = 1_024;
|
||||
/// Optional second WebSocket carrying delegate callbacks and their responses.
|
||||
pub const DUAL_WEBSOCKET_CAPABILITY: &str = "dual-websocket-v1";
|
||||
|
||||
/// Negotiated support for cell execution resource limits on `session/open`.
|
||||
pub const SESSION_RESOURCE_LIMITS_CAPABILITY: &str = "session-cell-execution-resource-limits";
|
||||
|
||||
/// Selects one socket of a negotiated dual-WebSocket connection.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum TransportLane {
|
||||
@@ -44,6 +47,7 @@ pub use payload::WireExecuteRequest;
|
||||
pub use payload::WireImageDetail;
|
||||
pub use payload::WireNestedToolCall;
|
||||
pub use payload::WireRuntimeResponse;
|
||||
pub use payload::WireSessionCellExecutionLimits;
|
||||
pub use payload::WireToolDefinition;
|
||||
pub use payload::WireToolKind;
|
||||
pub use payload::WireToolName;
|
||||
|
||||
@@ -7,6 +7,7 @@ use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::CellId;
|
||||
use crate::CodeModeNestedToolCall;
|
||||
use crate::CodeModeSessionCellExecutionLimits;
|
||||
use crate::CodeModeToolKind;
|
||||
use crate::ExecuteRequest;
|
||||
use crate::FunctionCallOutputContentItem;
|
||||
@@ -16,6 +17,38 @@ use crate::ToolDefinition;
|
||||
use crate::WaitOutcome;
|
||||
use crate::WaitRequest;
|
||||
|
||||
/// The per-cell execution limits carried by a V1 session-open request.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct WireSessionCellExecutionLimits {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_yield_time_ms: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_heap_size_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
impl TryFrom<CodeModeSessionCellExecutionLimits> for WireSessionCellExecutionLimits {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
fn try_from(value: CodeModeSessionCellExecutionLimits) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
max_yield_time_ms: value.max_yield_time_ms,
|
||||
max_heap_size_bytes: value.max_heap_size_bytes.map(u64::try_from).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WireSessionCellExecutionLimits> for CodeModeSessionCellExecutionLimits {
|
||||
type Error = TryFromIntError;
|
||||
|
||||
fn try_from(value: WireSessionCellExecutionLimits) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
max_yield_time_ms: value.max_yield_time_ms,
|
||||
max_heap_size_bytes: value.max_heap_size_bytes.map(usize::try_from).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A cell identifier with a wire representation owned by protocol V1.
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(transparent)]
|
||||
|
||||
Reference in New Issue
Block a user