mirror of
https://github.com/openai/codex.git
synced 2026-05-02 20:32:04 +03:00
Moves Code Mode to a new crate with no dependencies on codex. This
create encodes the code mode semantics that we want for lifetime,
mounting, tool calling.
The model-facing surface is mostly unchanged. `exec` still runs raw
JavaScript, `wait` still resumes or terminates a `cell_id`, nested tools
are still available through `tools.*`, and helpers like `text`, `image`,
`store`, `load`, `notify`, `yield_control`, and `exit` still exist.
The major change is underneath that surface:
- Old code mode was an external Node runtime.
- New code mode is an in-process V8 runtime embedded directly in Rust.
- Old code mode managed cells inside a long-lived Node runner process.
- New code mode manages cells in Rust, with one V8 runtime thread per
active `exec`.
- Old code mode used JSON protocol messages over child stdin/stdout plus
Node worker-thread messages.
- New code mode uses Rust channels and direct V8 callbacks/events.
This PR also fixes the two migration regressions that fell out of that
substrate change:
- `wait { terminate: true }` now waits for the V8 runtime to actually
stop before reporting termination.
- synchronous top-level `exit()` now succeeds again instead of surfacing
as a script error.
---
- `core/src/tools/code_mode/*` is now mostly an adapter layer for the
public `exec` / `wait` tools.
- `code-mode/src/service.rs` owns cell sessions and async control flow
in Rust.
- `code-mode/src/runtime/*.rs` owns the embedded V8 isolate and
JavaScript execution.
- each `exec` spawns a dedicated runtime thread plus a Rust
session-control task.
- helper globals are installed directly into the V8 context instead of
being injected through a source prelude.
- helper modules like `tools.js` and `@openai/code_mode` are synthesized
through V8 module resolution callbacks in Rust.
---
Also added a benchmark for showing the speed of init and use of a code
mode env:
```
$ cargo bench -p codex-code-mode --bench exec_overhead -- --samples 30 --warm-iterations 25 --tool-counts 0,32,128
Finished [`bench` profile [optimized]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 0.18s
Running benches/exec_overhead.rs (target/release/deps/exec_overhead-008c440d800545ae)
exec_overhead: samples=30, warm_iterations=25, tool_counts=[0, 32, 128]
scenario tools samples warmups iters mean/exec p95/exec rssΔ p50 rssΔ max
cold_exec 0 30 0 1 1.13ms 1.20ms 8.05MiB 8.06MiB
warm_exec 0 30 1 25 473.43us 512.49us 912.00KiB 1.33MiB
cold_exec 32 30 0 1 1.03ms 1.15ms 8.08MiB 8.11MiB
warm_exec 32 30 1 25 509.73us 545.76us 960.00KiB 1.30MiB
cold_exec 128 30 0 1 1.14ms 1.19ms 8.30MiB 8.34MiB
warm_exec 128 30 1 25 575.08us 591.03us 736.00KiB 864.00KiB
memory uses a fresh-process max RSS delta for each scenario
```
---------
Co-authored-by: Codex <noreply@openai.com>
139 lines
5.3 KiB
Rust
139 lines
5.3 KiB
Rust
use super::RuntimeState;
|
|
use super::callbacks::exit_callback;
|
|
use super::callbacks::image_callback;
|
|
use super::callbacks::load_callback;
|
|
use super::callbacks::notify_callback;
|
|
use super::callbacks::store_callback;
|
|
use super::callbacks::text_callback;
|
|
use super::callbacks::tool_callback;
|
|
use super::callbacks::yield_control_callback;
|
|
|
|
pub(super) fn install_globals(scope: &mut v8::PinScope<'_, '_>) -> Result<(), String> {
|
|
let global = scope.get_current_context().global(scope);
|
|
let console = v8::String::new(scope, "console")
|
|
.ok_or_else(|| "failed to allocate global `console`".to_string())?;
|
|
if global.delete(scope, console.into()) != Some(true) {
|
|
return Err("failed to remove global `console`".to_string());
|
|
}
|
|
|
|
let tools = build_tools_object(scope)?;
|
|
let all_tools = build_all_tools_value(scope)?;
|
|
let text = helper_function(scope, "text", text_callback)?;
|
|
let image = helper_function(scope, "image", image_callback)?;
|
|
let store = helper_function(scope, "store", store_callback)?;
|
|
let load = helper_function(scope, "load", load_callback)?;
|
|
let notify = helper_function(scope, "notify", notify_callback)?;
|
|
let yield_control = helper_function(scope, "yield_control", yield_control_callback)?;
|
|
let exit = helper_function(scope, "exit", exit_callback)?;
|
|
|
|
set_global(scope, global, "tools", tools.into())?;
|
|
set_global(scope, global, "ALL_TOOLS", all_tools)?;
|
|
set_global(scope, global, "text", text.into())?;
|
|
set_global(scope, global, "image", image.into())?;
|
|
set_global(scope, global, "store", store.into())?;
|
|
set_global(scope, global, "load", load.into())?;
|
|
set_global(scope, global, "notify", notify.into())?;
|
|
set_global(scope, global, "yield_control", yield_control.into())?;
|
|
set_global(scope, global, "exit", exit.into())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_tools_object<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
) -> Result<v8::Local<'s, v8::Object>, String> {
|
|
let tools = v8::Object::new(scope);
|
|
let enabled_tools = scope
|
|
.get_slot::<RuntimeState>()
|
|
.map(|state| state.enabled_tools.clone())
|
|
.unwrap_or_default();
|
|
|
|
for tool in enabled_tools {
|
|
let name = v8::String::new(scope, &tool.global_name)
|
|
.ok_or_else(|| "failed to allocate tool name".to_string())?;
|
|
let function = tool_function(scope, &tool.tool_name)?;
|
|
tools.set(scope, name.into(), function.into());
|
|
}
|
|
Ok(tools)
|
|
}
|
|
|
|
fn build_all_tools_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
) -> Result<v8::Local<'s, v8::Value>, String> {
|
|
let enabled_tools = scope
|
|
.get_slot::<RuntimeState>()
|
|
.map(|state| state.enabled_tools.clone())
|
|
.unwrap_or_default();
|
|
let array = v8::Array::new(scope, enabled_tools.len() as i32);
|
|
let name_key = v8::String::new(scope, "name")
|
|
.ok_or_else(|| "failed to allocate ALL_TOOLS name key".to_string())?;
|
|
let description_key = v8::String::new(scope, "description")
|
|
.ok_or_else(|| "failed to allocate ALL_TOOLS description key".to_string())?;
|
|
|
|
for (index, tool) in enabled_tools.iter().enumerate() {
|
|
let item = v8::Object::new(scope);
|
|
let name = v8::String::new(scope, &tool.global_name)
|
|
.ok_or_else(|| "failed to allocate ALL_TOOLS name".to_string())?;
|
|
let description = v8::String::new(scope, &tool.description)
|
|
.ok_or_else(|| "failed to allocate ALL_TOOLS description".to_string())?;
|
|
|
|
if item.set(scope, name_key.into(), name.into()) != Some(true) {
|
|
return Err("failed to set ALL_TOOLS name".to_string());
|
|
}
|
|
if item.set(scope, description_key.into(), description.into()) != Some(true) {
|
|
return Err("failed to set ALL_TOOLS description".to_string());
|
|
}
|
|
if array.set_index(scope, index as u32, item.into()) != Some(true) {
|
|
return Err("failed to append ALL_TOOLS metadata".to_string());
|
|
}
|
|
}
|
|
|
|
Ok(array.into())
|
|
}
|
|
|
|
fn helper_function<'s, F>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
name: &str,
|
|
callback: F,
|
|
) -> Result<v8::Local<'s, v8::Function>, String>
|
|
where
|
|
F: v8::MapFnTo<v8::FunctionCallback>,
|
|
{
|
|
let name =
|
|
v8::String::new(scope, name).ok_or_else(|| "failed to allocate helper name".to_string())?;
|
|
let template = v8::FunctionTemplate::builder(callback)
|
|
.data(name.into())
|
|
.build(scope);
|
|
template
|
|
.get_function(scope)
|
|
.ok_or_else(|| "failed to create helper function".to_string())
|
|
}
|
|
|
|
fn tool_function<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
tool_name: &str,
|
|
) -> Result<v8::Local<'s, v8::Function>, String> {
|
|
let data = v8::String::new(scope, tool_name)
|
|
.ok_or_else(|| "failed to allocate tool callback data".to_string())?;
|
|
let template = v8::FunctionTemplate::builder(tool_callback)
|
|
.data(data.into())
|
|
.build(scope);
|
|
template
|
|
.get_function(scope)
|
|
.ok_or_else(|| "failed to create tool function".to_string())
|
|
}
|
|
|
|
fn set_global<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
name: &str,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> Result<(), String> {
|
|
let key = v8::String::new(scope, name)
|
|
.ok_or_else(|| format!("failed to allocate global `{name}`"))?;
|
|
if global.set(scope, key.into(), value) == Some(true) {
|
|
Ok(())
|
|
} else {
|
|
Err(format!("failed to set global `{name}`"))
|
|
}
|
|
}
|