Rename exec session IDs to cell IDs (#14510)

- Update the code-mode executor, wait handler, and protocol plumbing to
use cell IDs instead of session IDs for node communication
- Switch tool metadata, wait description, and suite tests to refer to
cell IDs so user-visible messages match the new terminology

**Testing**
- Not run (not requested)
This commit is contained in:
pakrym-oai 2026-03-12 14:05:30 -07:00 committed by GitHub
parent 11812383c5
commit 04e14bdf23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 71 additions and 72 deletions

View file

@ -32,7 +32,7 @@ impl CodeModeExecuteHandler {
let stored_values = service.stored_values().await;
let source =
build_source(&code, &enabled_tools).map_err(FunctionCallError::RespondToModel)?;
let session_id = service.allocate_session_id().await;
let cell_id = service.allocate_cell_id().await;
let request_id = service.allocate_request_id().await;
let process_slot = service
.ensure_started()
@ -41,7 +41,7 @@ impl CodeModeExecuteHandler {
let started_at = std::time::Instant::now();
let message = HostToNodeMessage::Start {
request_id: request_id.clone(),
session_id,
cell_id: cell_id.clone(),
default_yield_time_ms: super::DEFAULT_EXEC_YIELD_TIME_MS,
enabled_tools,
stored_values,
@ -62,7 +62,7 @@ impl CodeModeExecuteHandler {
Ok(message) => message,
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
};
handle_node_message(&exec, session_id, message, None, started_at).await
handle_node_message(&exec, cell_id, message, None, started_at).await
};
match result {
Ok(CodeModeSessionProgress::Finished(output))

View file

@ -57,7 +57,7 @@ enum CodeModeSessionProgress {
enum CodeModeExecutionStatus {
Completed,
Failed,
Running(i32),
Running(String),
Terminated,
}
@ -79,7 +79,7 @@ pub(crate) fn wait_tool_description() -> &'static str {
async fn handle_node_message(
exec: &ExecContext,
session_id: i32,
cell_id: String,
message: protocol::NodeToHostMessage,
poll_max_output_tokens: Option<Option<usize>>,
started_at: std::time::Instant,
@ -91,7 +91,7 @@ async fn handle_node_message(
delta_items = truncate_code_mode_result(delta_items, poll_max_output_tokens.flatten());
prepend_script_status(
&mut delta_items,
CodeModeExecutionStatus::Running(session_id),
CodeModeExecutionStatus::Running(cell_id),
started_at.elapsed(),
);
Ok(CodeModeSessionProgress::Yielded {
@ -161,8 +161,8 @@ fn prepend_script_status(
match status {
CodeModeExecutionStatus::Completed => "Script completed".to_string(),
CodeModeExecutionStatus::Failed => "Script failed".to_string(),
CodeModeExecutionStatus::Running(session_id) => {
format!("Script running with session ID {session_id}")
CodeModeExecutionStatus::Running(cell_id) => {
format!("Script running with cell ID {cell_id}")
}
CodeModeExecutionStatus::Terminated => "Script terminated".to_string(),
}

View file

@ -41,7 +41,7 @@ pub(super) struct CodeModeToolCall {
pub(super) enum HostToNodeMessage {
Start {
request_id: String,
session_id: i32,
cell_id: String,
default_yield_time_ms: u64,
enabled_tools: Vec<EnabledTool>,
stored_values: HashMap<String, JsonValue>,
@ -49,12 +49,12 @@ pub(super) enum HostToNodeMessage {
},
Poll {
request_id: String,
session_id: i32,
cell_id: String,
yield_time_ms: u64,
},
Terminate {
request_id: String,
session_id: i32,
cell_id: String,
},
Response {
request_id: String,

View file

@ -486,7 +486,7 @@ function createProtocol() {
}
if (message.type === 'poll') {
const session = sessions.get(message.session_id);
const session = sessions.get(message.cell_id);
if (session) {
session.request_id = String(message.request_id);
if (session.pending_result) {
@ -500,7 +500,7 @@ function createProtocol() {
request_id: message.request_id,
content_items: [],
stored_values: {},
error_text: `exec session ${message.session_id} not found`,
error_text: `exec cell ${message.cell_id} not found`,
max_output_tokens_per_exec_call: DEFAULT_MAX_OUTPUT_TOKENS_PER_EXEC_CALL,
});
}
@ -508,7 +508,7 @@ function createProtocol() {
}
if (message.type === 'terminate') {
const session = sessions.get(message.session_id);
const session = sessions.get(message.cell_id);
if (session) {
session.request_id = String(message.request_id);
void terminateSession(protocol, sessions, session);
@ -518,7 +518,7 @@ function createProtocol() {
request_id: message.request_id,
content_items: [],
stored_values: {},
error_text: `exec session ${message.session_id} not found`,
error_text: `exec cell ${message.cell_id} not found`,
max_output_tokens_per_exec_call: DEFAULT_MAX_OUTPUT_TOKENS_PER_EXEC_CALL,
});
}
@ -591,7 +591,7 @@ function startSession(protocol, sessions, start) {
completed: false,
content_items: [],
default_yield_time_ms: normalizeYieldTime(start.default_yield_time_ms),
id: start.session_id,
id: start.cell_id,
initial_yield_timer: null,
initial_yield_triggered: false,
max_output_tokens_per_exec_call: DEFAULT_MAX_OUTPUT_TOKENS_PER_EXEC_CALL,

View file

@ -24,7 +24,7 @@ pub(crate) struct CodeModeService {
js_repl_node_path: Option<PathBuf>,
stored_values: Mutex<HashMap<String, JsonValue>>,
process: Arc<Mutex<Option<CodeModeProcess>>>,
next_session_id: Mutex<i32>,
next_cell_id: Mutex<u64>,
}
impl CodeModeService {
@ -33,7 +33,7 @@ impl CodeModeService {
js_repl_node_path,
stored_values: Mutex::new(HashMap::new()),
process: Arc::new(Mutex::new(None)),
next_session_id: Mutex::new(1),
next_cell_id: Mutex::new(1),
}
}
@ -95,11 +95,11 @@ impl CodeModeService {
Some(process.worker(exec, tool_runtime))
}
pub(crate) async fn allocate_session_id(&self) -> i32 {
let mut next_session_id = self.next_session_id.lock().await;
let session_id = *next_session_id;
*next_session_id = next_session_id.saturating_add(1);
session_id
pub(crate) async fn allocate_cell_id(&self) -> String {
let mut next_cell_id = self.next_cell_id.lock().await;
let cell_id = *next_cell_id;
*next_cell_id = next_cell_id.saturating_add(1);
cell_id.to_string()
}
pub(crate) async fn allocate_request_id(&self) -> String {

View file

@ -1,8 +1,8 @@
- Use `exec_wait` only after `exec` returns `Script running with session ID ...`.
- `session_id` identifies the running `exec` session to resume.
- Use `exec_wait` only after `exec` returns `Script running with cell ID ...`.
- `cell_id` identifies the running `exec` cell to resume.
- `yield_time_ms` controls how long to wait for more output before yielding again. If omitted, `exec_wait` uses its default wait timeout.
- `max_tokens` limits how much new output this wait call returns.
- `terminate: true` stops the running session instead of waiting for more output.
- `exec_wait` returns only the new output since the last yield, or the final completion or termination result for that session.
- If the session is still running, `exec_wait` may yield again with the same `session_id`.
- If the session has already finished, `exec_wait` returns the completed result and closes the session.
- `terminate: true` stops the running cell instead of waiting for more output.
- `exec_wait` returns only the new output since the last yield, or the final completion or termination result for that cell.
- If the cell is still running, `exec_wait` may yield again with the same `cell_id`.
- If the cell has already finished, `exec_wait` returns the completed result and closes the cell.

View file

@ -20,7 +20,7 @@ pub struct CodeModeWaitHandler;
#[derive(Debug, Deserialize)]
struct ExecWaitArgs {
session_id: i32,
cell_id: String,
#[serde(default = "default_wait_yield_time_ms")]
yield_time_ms: u64,
#[serde(default)]
@ -73,12 +73,12 @@ impl ToolHandler for CodeModeWaitHandler {
let message = if args.terminate {
HostToNodeMessage::Terminate {
request_id: request_id.clone(),
session_id: args.session_id,
cell_id: args.cell_id.clone(),
}
} else {
HostToNodeMessage::Poll {
request_id: request_id.clone(),
session_id: args.session_id,
cell_id: args.cell_id.clone(),
yield_time_ms: args.yield_time_ms,
}
};
@ -111,7 +111,7 @@ impl ToolHandler for CodeModeWaitHandler {
};
handle_node_message(
&exec,
args.session_id,
args.cell_id,
message,
Some(args.max_tokens),
started_at,

View file

@ -622,9 +622,9 @@ fn create_write_stdin_tool() -> ToolSpec {
fn create_exec_wait_tool() -> ToolSpec {
let properties = BTreeMap::from([
(
"session_id".to_string(),
JsonSchema::Number {
description: Some("Identifier of the running exec session.".to_string()),
"cell_id".to_string(),
JsonSchema::String {
description: Some("Identifier of the running exec cell.".to_string()),
},
),
(
@ -647,7 +647,7 @@ fn create_exec_wait_tool() -> ToolSpec {
(
"terminate".to_string(),
JsonSchema::Boolean {
description: Some("Whether to terminate the running exec session.".to_string()),
description: Some("Whether to terminate the running exec cell.".to_string()),
},
),
]);
@ -655,13 +655,13 @@ fn create_exec_wait_tool() -> ToolSpec {
ToolSpec::Function(ResponsesApiTool {
name: WAIT_TOOL_NAME.to_string(),
description: format!(
"Waits on a yielded `{PUBLIC_TOOL_NAME}` session and returns new output or completion.\n{}",
"Waits on a yielded `{PUBLIC_TOOL_NAME}` cell and returns new output or completion.\n{}",
code_mode_wait_tool_description().trim()
),
strict: false,
parameters: JsonSchema::Object {
properties,
required: Some(vec!["session_id".to_string()]),
required: Some(vec!["cell_id".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,

View file

@ -52,12 +52,11 @@ fn text_item(items: &[Value], index: usize) -> &str {
.expect("content item should be input_text")
}
fn extract_running_session_id(text: &str) -> i32 {
text.strip_prefix("Script running with session ID ")
fn extract_running_cell_id(text: &str) -> String {
text.strip_prefix("Script running with cell ID ")
.and_then(|rest| rest.split('\n').next())
.expect("running header should contain a session ID")
.parse()
.expect("session ID should parse as i32")
.expect("running header should contain a cell ID")
.to_string()
}
fn wait_for_file_source(path: &Path) -> Result<String> {
@ -422,12 +421,12 @@ output_text("phase 3");
assert_regex_match(
concat!(
r"(?s)\A",
r"Script running with session ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
),
text_item(&first_items, 0),
);
assert_eq!(text_item(&first_items, 1), "phase 1");
let session_id = extract_running_session_id(text_item(&first_items, 0));
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
responses::mount_sse_once(
&server,
@ -437,7 +436,7 @@ output_text("phase 3");
"call-2",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_id,
"cell_id": cell_id.clone(),
"yield_time_ms": 1_000,
}))?,
),
@ -463,13 +462,13 @@ output_text("phase 3");
assert_regex_match(
concat!(
r"(?s)\A",
r"Script running with session ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
),
text_item(&second_items, 0),
);
assert_eq!(
extract_running_session_id(text_item(&second_items, 0)),
session_id
extract_running_cell_id(text_item(&second_items, 0)),
cell_id
);
assert_eq!(text_item(&second_items, 1), "phase 2");
@ -481,7 +480,7 @@ output_text("phase 3");
"call-3",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_id,
"cell_id": cell_id.clone(),
"yield_time_ms": 1_000,
}))?,
),
@ -565,12 +564,12 @@ while (true) {}
assert_regex_match(
concat!(
r"(?s)\A",
r"Script running with session ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
),
text_item(&first_items, 0),
);
assert_eq!(text_item(&first_items, 1), "phase 1");
let session_id = extract_running_session_id(text_item(&first_items, 0));
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
responses::mount_sse_once(
&server,
@ -580,7 +579,7 @@ while (true) {}
"call-2",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_id,
"cell_id": cell_id.clone(),
"terminate": true,
}))?,
),
@ -674,7 +673,7 @@ output_text("session b done");
let first_request = first_completion.single_request();
let first_items = custom_tool_output_items(&first_request, "call-1");
assert_eq!(first_items.len(), 2);
let session_a_id = extract_running_session_id(text_item(&first_items, 0));
let session_a_id = extract_running_cell_id(text_item(&first_items, 0));
assert_eq!(text_item(&first_items, 1), "session a start");
responses::mount_sse_once(
@ -700,7 +699,7 @@ output_text("session b done");
let second_request = second_completion.single_request();
let second_items = custom_tool_output_items(&second_request, "call-2");
assert_eq!(second_items.len(), 2);
let session_b_id = extract_running_session_id(text_item(&second_items, 0));
let session_b_id = extract_running_cell_id(text_item(&second_items, 0));
assert_eq!(text_item(&second_items, 1), "session b start");
assert_ne!(session_a_id, session_b_id);
@ -713,7 +712,7 @@ output_text("session b done");
"call-3",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_a_id,
"cell_id": session_a_id.clone(),
"yield_time_ms": 1_000,
}))?,
),
@ -753,7 +752,7 @@ output_text("session b done");
"call-4",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_b_id,
"cell_id": session_b_id.clone(),
"yield_time_ms": 1_000,
}))?,
),
@ -835,7 +834,7 @@ output_text("phase 2");
let first_request = first_completion.single_request();
let first_items = custom_tool_output_items(&first_request, "call-1");
assert_eq!(first_items.len(), 2);
let session_id = extract_running_session_id(text_item(&first_items, 0));
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
assert_eq!(text_item(&first_items, 1), "phase 1");
responses::mount_sse_once(
@ -846,7 +845,7 @@ output_text("phase 2");
"call-2",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_id,
"cell_id": cell_id.clone(),
"terminate": true,
}))?,
),
@ -937,7 +936,7 @@ async fn code_mode_exec_wait_returns_error_for_unknown_session() -> Result<()> {
"call-1",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": 999_999,
"cell_id": "999999",
"yield_time_ms": 1_000,
}))?,
),
@ -954,7 +953,7 @@ async fn code_mode_exec_wait_returns_error_for_unknown_session() -> Result<()> {
)
.await;
test.submit_turn("wait on an unknown exec session").await?;
test.submit_turn("wait on an unknown exec cell").await?;
let request = completion.single_request();
let (_, success) = request
@ -973,7 +972,7 @@ async fn code_mode_exec_wait_returns_error_for_unknown_session() -> Result<()> {
);
assert_eq!(
text_item(&items, 1),
"Script error:\nexec session 999999 not found"
"Script error:\nexec cell 999999 not found"
);
Ok(())
@ -1046,7 +1045,7 @@ output_text("session b done");
let first_request = first_completion.single_request();
let first_items = custom_tool_output_items(&first_request, "call-1");
assert_eq!(first_items.len(), 2);
let session_a_id = extract_running_session_id(text_item(&first_items, 0));
let session_a_id = extract_running_cell_id(text_item(&first_items, 0));
assert_eq!(text_item(&first_items, 1), "session a start");
responses::mount_sse_once(
@ -1072,7 +1071,7 @@ output_text("session b done");
let second_request = second_completion.single_request();
let second_items = custom_tool_output_items(&second_request, "call-2");
assert_eq!(second_items.len(), 2);
let session_b_id = extract_running_session_id(text_item(&second_items, 0));
let session_b_id = extract_running_cell_id(text_item(&second_items, 0));
assert_eq!(text_item(&second_items, 1), "session b start");
fs::write(&session_a_gate, "ready")?;
@ -1084,7 +1083,7 @@ output_text("session b done");
"call-3",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_b_id,
"cell_id": session_b_id.clone(),
"yield_time_ms": 1_000,
}))?,
),
@ -1109,12 +1108,12 @@ output_text("session b done");
assert_regex_match(
concat!(
r"(?s)\A",
r"Script running with session ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
),
text_item(&third_items, 0),
);
assert_eq!(
extract_running_session_id(text_item(&third_items, 0)),
extract_running_cell_id(text_item(&third_items, 0)),
session_b_id
);
@ -1134,7 +1133,7 @@ output_text("session b done");
"call-4",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_a_id,
"cell_id": session_a_id.clone(),
"terminate": true,
}))?,
),
@ -1234,7 +1233,7 @@ output_text("after yield");
assert_regex_match(
concat!(
r"(?s)\A",
r"Script running with session ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
r"Script running with cell ID \d+\nWall time \d+\.\d seconds\nOutput:\n\z"
),
text_item(&first_items, 0),
);
@ -1327,7 +1326,7 @@ output_text("token one token two token three token four token five token six tok
let first_items = custom_tool_output_items(&first_request, "call-1");
assert_eq!(first_items.len(), 2);
assert_eq!(text_item(&first_items, 1), "phase 1");
let session_id = extract_running_session_id(text_item(&first_items, 0));
let cell_id = extract_running_cell_id(text_item(&first_items, 0));
fs::write(&completion_gate, "ready")?;
responses::mount_sse_once(
@ -1338,7 +1337,7 @@ output_text("token one token two token three token four token five token six tok
"call-2",
"exec_wait",
&serde_json::to_string(&serde_json::json!({
"session_id": session_id,
"cell_id": cell_id.clone(),
"yield_time_ms": 1_000,
"max_tokens": 6,
}))?,