mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
42e22f3bde
## Summary This PR adds an **experimental, feature-gated `js_repl` core runtime** so models can execute JavaScript in a persistent REPL context across tool calls. The implementation integrates with existing feature gating, tool registration, prompt composition, config/schema docs, and tests. ## What changed - Added new experimental feature flag: `features.js_repl`. - Added freeform `js_repl` tool and companion `js_repl_reset` tool. - Gated tool availability behind `Feature::JsRepl`. - Added conditional prompt-section injection for JS REPL instructions via marker-based prompt processing. - Implemented JS REPL handlers, including freeform parsing and pragma support (timeout/reset controls). - Added runtime resolution order for Node: 1. `CODEX_JS_REPL_NODE_PATH` 2. `js_repl_node_path` in config 3. `PATH` - Added JS runtime assets/version files and updated docs/schema. ## Why This enables richer agent workflows that require incremental JavaScript execution with preserved state, while keeping rollout safe behind an explicit feature flag. ## Testing Coverage includes: - Feature-flag gating behavior for tool exposure. - Freeform parser/pragma handling edge cases. - Runtime behavior (state persistence across calls and top-level `await` support). ## Usage ```toml [features] js_repl = true ``` Optional runtime override: - `CODEX_JS_REPL_NODE_PATH`, or - `js_repl_node_path` in config. #### [git stack](https://github.com/magus/git-stack-cli) - 👉 `1` https://github.com/openai/codex/pull/10674 - ⏳ `2` https://github.com/openai/codex/pull/10672 - ⏳ `3` https://github.com/openai/codex/pull/10671 - ⏳ `4` https://github.com/openai/codex/pull/10673 - ⏳ `5` https://github.com/openai/codex/pull/10670
116 lines
3.4 KiB
Rust
116 lines
3.4 KiB
Rust
pub mod context;
|
|
pub mod events;
|
|
pub(crate) mod handlers;
|
|
pub mod js_repl;
|
|
pub mod orchestrator;
|
|
pub mod parallel;
|
|
pub mod registry;
|
|
pub mod router;
|
|
pub mod runtimes;
|
|
pub mod sandboxing;
|
|
pub mod spec;
|
|
|
|
use crate::exec::ExecToolCallOutput;
|
|
use crate::truncate::TruncationPolicy;
|
|
use crate::truncate::formatted_truncate_text;
|
|
use crate::truncate::truncate_text;
|
|
pub use router::ToolRouter;
|
|
use serde::Serialize;
|
|
|
|
// Telemetry preview limits: keep log events smaller than model budgets.
|
|
pub(crate) const TELEMETRY_PREVIEW_MAX_BYTES: usize = 2 * 1024; // 2 KiB
|
|
pub(crate) const TELEMETRY_PREVIEW_MAX_LINES: usize = 64; // lines
|
|
pub(crate) const TELEMETRY_PREVIEW_TRUNCATION_NOTICE: &str =
|
|
"[... telemetry preview truncated ...]";
|
|
|
|
/// Format the combined exec output for sending back to the model.
|
|
/// Includes exit code and duration metadata; truncates large bodies safely.
|
|
pub fn format_exec_output_for_model_structured(
|
|
exec_output: &ExecToolCallOutput,
|
|
truncation_policy: TruncationPolicy,
|
|
) -> String {
|
|
let ExecToolCallOutput {
|
|
exit_code,
|
|
duration,
|
|
..
|
|
} = exec_output;
|
|
|
|
#[derive(Serialize)]
|
|
struct ExecMetadata {
|
|
exit_code: i32,
|
|
duration_seconds: f32,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct ExecOutput<'a> {
|
|
output: &'a str,
|
|
metadata: ExecMetadata,
|
|
}
|
|
|
|
// round to 1 decimal place
|
|
let duration_seconds = ((duration.as_secs_f32()) * 10.0).round() / 10.0;
|
|
|
|
let formatted_output = format_exec_output_str(exec_output, truncation_policy);
|
|
|
|
let payload = ExecOutput {
|
|
output: &formatted_output,
|
|
metadata: ExecMetadata {
|
|
exit_code: *exit_code,
|
|
duration_seconds,
|
|
},
|
|
};
|
|
|
|
#[expect(clippy::expect_used)]
|
|
serde_json::to_string(&payload).expect("serialize ExecOutput")
|
|
}
|
|
|
|
pub fn format_exec_output_for_model_freeform(
|
|
exec_output: &ExecToolCallOutput,
|
|
truncation_policy: TruncationPolicy,
|
|
) -> String {
|
|
// round to 1 decimal place
|
|
let duration_seconds = ((exec_output.duration.as_secs_f32()) * 10.0).round() / 10.0;
|
|
|
|
let content = build_content_with_timeout(exec_output);
|
|
|
|
let total_lines = content.lines().count();
|
|
|
|
let formatted_output = truncate_text(&content, truncation_policy);
|
|
|
|
let mut sections = Vec::new();
|
|
|
|
sections.push(format!("Exit code: {}", exec_output.exit_code));
|
|
sections.push(format!("Wall time: {duration_seconds} seconds"));
|
|
if total_lines != formatted_output.lines().count() {
|
|
sections.push(format!("Total output lines: {total_lines}"));
|
|
}
|
|
|
|
sections.push("Output:".to_string());
|
|
sections.push(formatted_output);
|
|
|
|
sections.join("\n")
|
|
}
|
|
|
|
pub fn format_exec_output_str(
|
|
exec_output: &ExecToolCallOutput,
|
|
truncation_policy: TruncationPolicy,
|
|
) -> String {
|
|
let content = build_content_with_timeout(exec_output);
|
|
|
|
// Truncate for model consumption before serialization.
|
|
formatted_truncate_text(&content, truncation_policy)
|
|
}
|
|
|
|
/// Extracts exec output content and prepends a timeout message if the command timed out.
|
|
fn build_content_with_timeout(exec_output: &ExecToolCallOutput) -> String {
|
|
if exec_output.timed_out {
|
|
format!(
|
|
"command timed out after {} milliseconds\n{}",
|
|
exec_output.duration.as_millis(),
|
|
exec_output.aggregated_output.text
|
|
)
|
|
} else {
|
|
exec_output.aggregated_output.text.clone()
|
|
}
|
|
}
|