diff --git a/.codespellrc b/.codespellrc index 87e3468c66ed..838b7e874e46 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,6 +1,6 @@ [codespell] # Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new,*meriyah.umd.min.js +skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new check-hidden = true ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE,PASE,SEH diff --git a/.github/actions/prepare-bazel-ci/action.yml b/.github/actions/prepare-bazel-ci/action.yml index 78f5aeb9a348..48c6ba74b4ea 100644 --- a/.github/actions/prepare-bazel-ci/action.yml +++ b/.github/actions/prepare-bazel-ci/action.yml @@ -8,7 +8,7 @@ inputs: description: Logical namespace used to keep concurrent Bazel jobs from reserving the same repository cache key. required: true install-test-prereqs: - description: Install Node.js and DotSlash for Bazel-backed test jobs. + description: Install DotSlash for Bazel-backed test jobs. required: false default: "false" outputs: diff --git a/.github/actions/setup-bazel-ci/action.yml b/.github/actions/setup-bazel-ci/action.yml index 008e87c49695..881209fd818e 100644 --- a/.github/actions/setup-bazel-ci/action.yml +++ b/.github/actions/setup-bazel-ci/action.yml @@ -5,7 +5,7 @@ inputs: description: Target triple used for cache namespacing. required: true install-test-prereqs: - description: Install Node.js and DotSlash for Bazel-backed test jobs. + description: Install DotSlash for Bazel-backed test jobs. required: false default: "false" outputs: @@ -16,12 +16,6 @@ outputs: runs: using: composite steps: - - name: Set up Node.js for js_repl tests - if: inputs.install-test-prereqs == 'true' - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version-file: codex-rs/node-version.txt - # Some integration tests rely on DotSlash being installed. # See https://github.com/openai/codex/pull/7617. - name: Install DotSlash diff --git a/.github/scripts/run-bazel-ci.sh b/.github/scripts/run-bazel-ci.sh index cf2d4ce3400f..b81e0a4d577a 100755 --- a/.github/scripts/run-bazel-ci.sh +++ b/.github/scripts/run-bazel-ci.sh @@ -4,7 +4,6 @@ set -euo pipefail print_failed_bazel_test_logs=0 print_failed_bazel_action_summary=0 -use_node_test_env=0 remote_download_toplevel=0 windows_msvc_host_platform=0 @@ -18,10 +17,6 @@ while [[ $# -gt 0 ]]; do print_failed_bazel_action_summary=1 shift ;; - --use-node-test-env) - use_node_test_env=1 - shift - ;; --remote-download-toplevel) remote_download_toplevel=1 shift @@ -42,7 +37,7 @@ while [[ $# -gt 0 ]]; do done if [[ $# -eq 0 ]]; then - echo "Usage: $0 [--print-failed-test-logs] [--print-failed-action-summary] [--use-node-test-env] [--remote-download-toplevel] [--windows-msvc-host-platform] -- -- " >&2 + echo "Usage: $0 [--print-failed-test-logs] [--print-failed-action-summary] [--remote-download-toplevel] [--windows-msvc-host-platform] -- -- " >&2 exit 1 fi @@ -249,16 +244,6 @@ if [[ ${#bazel_args[@]} -eq 0 || ${#bazel_targets[@]} -eq 0 ]]; then exit 1 fi -if [[ $use_node_test_env -eq 1 ]]; then - # Bazel test sandboxes on macOS may resolve an older Homebrew `node` - # before the `actions/setup-node` runtime on PATH. - node_bin="$(which node)" - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - node_bin="$(cygpath -w "${node_bin}")" - fi - bazel_args+=("--test_env=CODEX_JS_REPL_NODE_PATH=${node_bin}") -fi - post_config_bazel_args=() if [[ "${RUNNER_OS:-}" == "Windows" && $windows_msvc_host_platform -eq 1 ]]; then has_host_platform_override=0 diff --git a/.github/workflows/Dockerfile.bazel b/.github/workflows/Dockerfile.bazel index 4f85409f943e..51c199dcc3d8 100644 --- a/.github/workflows/Dockerfile.bazel +++ b/.github/workflows/Dockerfile.bazel @@ -8,25 +8,9 @@ FROM ubuntu:24.04 RUN apt-get update && \ apt-get install -y --no-install-recommends \ - curl git python3 ca-certificates xz-utils && \ + curl git python3 ca-certificates && \ rm -rf /var/lib/apt/lists/* -COPY codex-rs/node-version.txt /tmp/node-version.txt - -RUN set -eux; \ - node_arch="$(dpkg --print-architecture)"; \ - case "${node_arch}" in \ - amd64) node_dist_arch="x64" ;; \ - arm64) node_dist_arch="arm64" ;; \ - *) echo "unsupported architecture: ${node_arch}"; exit 1 ;; \ - esac; \ - node_version="$(tr -d '[:space:]' , - /// Optional absolute path to the Node runtime used by `js_repl`. + /// Deprecated: ignored. + #[schemars(skip)] pub js_repl_node_path: Option, - /// Ordered list of directories to search for Node modules in `js_repl`. + /// Deprecated: ignored. + #[schemars(skip)] pub js_repl_node_module_dirs: Option>, /// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution. diff --git a/codex-rs/config/src/profile_toml.rs b/codex-rs/config/src/profile_toml.rs index 642770ff7e60..f6f63191b5ad 100644 --- a/codex-rs/config/src/profile_toml.rs +++ b/codex-rs/config/src/profile_toml.rs @@ -41,8 +41,11 @@ pub struct ConfigProfile { pub chatgpt_base_url: Option, /// Optional path to a file containing model instructions. pub model_instructions_file: Option, + /// Deprecated: ignored. + #[schemars(skip)] pub js_repl_node_path: Option, - /// Ordered list of directories to search for Node modules in `js_repl`. + /// Deprecated: ignored. + #[schemars(skip)] pub js_repl_node_module_dirs: Option>, /// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution. pub zsh_path: Option, diff --git a/codex-rs/core/BUILD.bazel b/codex-rs/core/BUILD.bazel index df5f4da1fabb..cfa077ff1762 100644 --- a/codex-rs/core/BUILD.bazel +++ b/codex-rs/core/BUILD.bazel @@ -19,9 +19,7 @@ codex_rust_crate( "Cargo.toml", ], allow_empty = True, - ) + [ - "//codex-rs:node-version.txt", - ], + ), rustc_env = { # Keep manifest-root path lookups inside the Bazel execroot for code # that relies on env!("CARGO_MANIFEST_DIR"). diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 030c36a8b682..a009fc2a55f5 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -583,16 +583,6 @@ "include_permissions_instructions": { "type": "boolean" }, - "js_repl_node_module_dirs": { - "description": "Ordered list of directories to search for Node modules in `js_repl`.", - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" - }, - "js_repl_node_path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, "model": { "type": "string" }, @@ -2849,21 +2839,6 @@ "description": "System instructions.", "type": "string" }, - "js_repl_node_module_dirs": { - "description": "Ordered list of directories to search for Node modules in `js_repl`.", - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" - }, - "js_repl_node_path": { - "allOf": [ - { - "$ref": "#/definitions/AbsolutePathBuf" - } - ], - "description": "Optional absolute path to the Node runtime used by `js_repl`." - }, "log_dir": { "allOf": [ { diff --git a/codex-rs/core/src/agent/role.rs b/codex-rs/core/src/agent/role.rs index 9569c02d7149..0ee1de760c18 100644 --- a/codex-rs/core/src/agent/role.rs +++ b/codex-rs/core/src/agent/role.rs @@ -267,7 +267,6 @@ mod reload { model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()), codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(), - js_repl_node_path: config.js_repl_node_path.clone(), ..Default::default() } } diff --git a/codex-rs/core/src/agents_md.rs b/codex-rs/core/src/agents_md.rs index 582835464750..b7fb7b11ce0f 100644 --- a/codex-rs/core/src/agents_md.rs +++ b/codex-rs/core/src/agents_md.rs @@ -42,41 +42,6 @@ pub const LOCAL_AGENTS_MD_FILENAME: &str = "AGENTS.override.md"; /// be concatenated with the following separator. const AGENTS_MD_SEPARATOR: &str = "\n\n--- project-doc ---\n\n"; -fn render_js_repl_instructions(config: &Config) -> Option { - if !config.features.enabled(Feature::JsRepl) { - return None; - } - - let mut section = String::from("## JavaScript REPL (Node)\n"); - section.push_str( - "- Use `js_repl` for Node-backed JavaScript with top-level await in a persistent kernel.\n", - ); - section.push_str("- `js_repl` is a freeform/custom tool. Direct `js_repl` calls must send raw JavaScript tool input (optionally with first-line `// codex-js-repl: timeout_ms=15000`). Do not wrap code in JSON (for example `{\"code\":\"...\"}`), quotes, or markdown code fences.\n"); - section.push_str( - "- Helpers: `codex.cwd`, `codex.homeDir`, `codex.tmpDir`, `codex.tool(name, args?)`, and `codex.emitImage(imageLike)`.\n", - ); - section.push_str("- `codex.tool` executes a normal tool call and resolves to the raw tool output object. Use it for shell and non-shell tools alike. Nested tool outputs stay inside JavaScript unless you emit them explicitly.\n"); - section.push_str("- `codex.emitImage(...)` adds one image to the outer `js_repl` function output each time you call it, so you can call it multiple times to emit multiple images. It accepts a data URL, a single `input_image` item, an object like `{ bytes, mimeType }` containing encoded PNG/JPEG/WebP/GIF bytes, or a raw tool response object with exactly one image and no text. It rejects mixed text-and-image content.\n"); - section.push_str("- `codex.tool(...)` and `codex.emitImage(...)` keep stable helper identities across cells. Saved references and persisted objects can reuse them in later cells, but async callbacks that fire after a cell finishes still fail because no exec is active.\n"); - section.push_str("- Request full-resolution image processing with `detail: \"original\"` only when the `view_image` tool schema includes a `detail` argument. The same availability applies to `codex.emitImage(...)`: if `view_image.detail` is present, you may also pass `detail: \"original\"` there. Use this when high-fidelity image perception or precise localization is needed, especially for CUA agents.\n"); - section.push_str("- Raw MCP image blocks can request the same behavior by returning `_meta: { \"codex/imageDetail\": \"original\" }` on the image content item.\n"); - section.push_str("- Example of sharing an in-memory Playwright screenshot: `await codex.emitImage({ bytes: await page.screenshot({ type: \"jpeg\", quality: 85 }), mimeType: \"image/jpeg\", detail: \"original\" })`.\n"); - section.push_str("- Example of sharing a local image tool result: `await codex.emitImage(codex.tool(\"view_image\", { path: \"/absolute/path\", detail: \"original\" }))`.\n"); - section.push_str("- When encoding an image to send with `codex.emitImage(...)` or `view_image`, prefer JPEG at about 85 quality when lossy compression is acceptable; use PNG when transparency or lossless detail matters. Smaller uploads are faster and less likely to hit size limits.\n"); - section.push_str("- Top-level bindings persist across cells. If a cell throws, prior bindings remain available and bindings that finished initializing before the throw often remain usable in later cells. For code you plan to reuse across cells, prefer declaring or assigning it in direct top-level statements before operations that might throw. If you hit `SyntaxError: Identifier 'x' has already been declared`, first reuse the existing binding, reassign a previously declared `let`, or pick a new descriptive name. Use `{ ... }` only for a short temporary block when you specifically need local scratch names; do not wrap an entire cell in block scope if you want those names reusable later. Reset the kernel with `js_repl_reset` only when you need a clean state.\n"); - section.push_str("- Top-level static import declarations (for example `import x from \"./file.js\"`) are currently unsupported in `js_repl`; use dynamic imports with `await import(\"pkg\")`, `await import(\"./file.js\")`, or `await import(\"/abs/path/file.mjs\")` instead. Imported local files must be ESM `.js`/`.mjs` files and run in the same REPL VM context. Bare package imports always resolve from REPL-global search roots (`CODEX_JS_REPL_NODE_MODULE_DIRS`, then cwd), not relative to the imported file location. Local files may statically import only other local relative/absolute/`file://` `.js`/`.mjs` files; package and builtin imports from local files must stay dynamic. `import.meta.resolve()` returns importable strings such as `file://...`, bare package names, and `node:...` specifiers. Local file modules reload between execs, while top-level bindings persist until `js_repl_reset`.\n"); - - if config.features.enabled(Feature::JsReplToolsOnly) { - section.push_str("- Do not call tools directly; use `js_repl` + `codex.tool(...)` for all tool calls, including shell commands.\n"); - section - .push_str("- MCP tools (if any) can also be called by name via `codex.tool(...)`.\n"); - } - - section.push_str("- Avoid direct access to `process.stdout` / `process.stderr` / `process.stdin`; it can corrupt the JSON line protocol. Use `console.log`, `codex.tool(...)`, and `codex.emitImage(...)`."); - - Some(section) -} - /// Resolves AGENTS.md files into model-visible user instructions and source /// paths. pub struct AgentsMdManager<'a> { @@ -147,13 +112,6 @@ impl<'a> AgentsMdManager<'a> { } }; - if let Some(js_repl_section) = render_js_repl_instructions(self.config) { - if !output.is_empty() { - output.push_str("\n\n"); - } - output.push_str(&js_repl_section); - } - if self.config.features.enabled(Feature::ChildAgentsMd) { if !output.is_empty() { output.push_str("\n\n"); diff --git a/codex-rs/core/src/agents_md_tests.rs b/codex-rs/core/src/agents_md_tests.rs index e163eba1f658..a3a754482361 100644 --- a/codex-rs/core/src/agents_md_tests.rs +++ b/codex-rs/core/src/agents_md_tests.rs @@ -199,40 +199,6 @@ async fn zero_byte_limit_disables_discovery() { assert_eq!(discovery, Vec::::new()); } -#[tokio::test] -async fn js_repl_instructions_are_appended_when_enabled() { - let tmp = tempfile::tempdir().expect("tempdir"); - let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await; - cfg.features - .enable(Feature::JsRepl) - .expect("test config should allow js_repl"); - - let res = get_user_instructions(&cfg) - .await - .expect("js_repl instructions expected"); - let expected = "## JavaScript REPL (Node)\n- Use `js_repl` for Node-backed JavaScript with top-level await in a persistent kernel.\n- `js_repl` is a freeform/custom tool. Direct `js_repl` calls must send raw JavaScript tool input (optionally with first-line `// codex-js-repl: timeout_ms=15000`). Do not wrap code in JSON (for example `{\"code\":\"...\"}`), quotes, or markdown code fences.\n- Helpers: `codex.cwd`, `codex.homeDir`, `codex.tmpDir`, `codex.tool(name, args?)`, and `codex.emitImage(imageLike)`.\n- `codex.tool` executes a normal tool call and resolves to the raw tool output object. Use it for shell and non-shell tools alike. Nested tool outputs stay inside JavaScript unless you emit them explicitly.\n- `codex.emitImage(...)` adds one image to the outer `js_repl` function output each time you call it, so you can call it multiple times to emit multiple images. It accepts a data URL, a single `input_image` item, an object like `{ bytes, mimeType }` containing encoded PNG/JPEG/WebP/GIF bytes, or a raw tool response object with exactly one image and no text. It rejects mixed text-and-image content.\n- `codex.tool(...)` and `codex.emitImage(...)` keep stable helper identities across cells. Saved references and persisted objects can reuse them in later cells, but async callbacks that fire after a cell finishes still fail because no exec is active.\n- Request full-resolution image processing with `detail: \"original\"` only when the `view_image` tool schema includes a `detail` argument. The same availability applies to `codex.emitImage(...)`: if `view_image.detail` is present, you may also pass `detail: \"original\"` there. Use this when high-fidelity image perception or precise localization is needed, especially for CUA agents.\n- Raw MCP image blocks can request the same behavior by returning `_meta: { \"codex/imageDetail\": \"original\" }` on the image content item.\n- Example of sharing an in-memory Playwright screenshot: `await codex.emitImage({ bytes: await page.screenshot({ type: \"jpeg\", quality: 85 }), mimeType: \"image/jpeg\", detail: \"original\" })`.\n- Example of sharing a local image tool result: `await codex.emitImage(codex.tool(\"view_image\", { path: \"/absolute/path\", detail: \"original\" }))`.\n- When encoding an image to send with `codex.emitImage(...)` or `view_image`, prefer JPEG at about 85 quality when lossy compression is acceptable; use PNG when transparency or lossless detail matters. Smaller uploads are faster and less likely to hit size limits.\n- Top-level bindings persist across cells. If a cell throws, prior bindings remain available and bindings that finished initializing before the throw often remain usable in later cells. For code you plan to reuse across cells, prefer declaring or assigning it in direct top-level statements before operations that might throw. If you hit `SyntaxError: Identifier 'x' has already been declared`, first reuse the existing binding, reassign a previously declared `let`, or pick a new descriptive name. Use `{ ... }` only for a short temporary block when you specifically need local scratch names; do not wrap an entire cell in block scope if you want those names reusable later. Reset the kernel with `js_repl_reset` only when you need a clean state.\n- Top-level static import declarations (for example `import x from \"./file.js\"`) are currently unsupported in `js_repl`; use dynamic imports with `await import(\"pkg\")`, `await import(\"./file.js\")`, or `await import(\"/abs/path/file.mjs\")` instead. Imported local files must be ESM `.js`/`.mjs` files and run in the same REPL VM context. Bare package imports always resolve from REPL-global search roots (`CODEX_JS_REPL_NODE_MODULE_DIRS`, then cwd), not relative to the imported file location. Local files may statically import only other local relative/absolute/`file://` `.js`/`.mjs` files; package and builtin imports from local files must stay dynamic. `import.meta.resolve()` returns importable strings such as `file://...`, bare package names, and `node:...` specifiers. Local file modules reload between execs, while top-level bindings persist until `js_repl_reset`.\n- Avoid direct access to `process.stdout` / `process.stderr` / `process.stdin`; it can corrupt the JSON line protocol. Use `console.log`, `codex.tool(...)`, and `codex.emitImage(...)`."; - assert_eq!(res, expected); -} - -#[tokio::test] -async fn js_repl_tools_only_instructions_are_feature_gated() { - let tmp = tempfile::tempdir().expect("tempdir"); - let mut cfg = make_config(&tmp, /*limit*/ 4096, /*instructions*/ None).await; - let mut features = cfg.features.get().clone(); - features - .enable(Feature::JsRepl) - .enable(Feature::JsReplToolsOnly); - cfg.features - .set(features) - .expect("test config should allow js_repl tool restrictions"); - - let res = get_user_instructions(&cfg) - .await - .expect("js_repl instructions expected"); - let expected = "## JavaScript REPL (Node)\n- Use `js_repl` for Node-backed JavaScript with top-level await in a persistent kernel.\n- `js_repl` is a freeform/custom tool. Direct `js_repl` calls must send raw JavaScript tool input (optionally with first-line `// codex-js-repl: timeout_ms=15000`). Do not wrap code in JSON (for example `{\"code\":\"...\"}`), quotes, or markdown code fences.\n- Helpers: `codex.cwd`, `codex.homeDir`, `codex.tmpDir`, `codex.tool(name, args?)`, and `codex.emitImage(imageLike)`.\n- `codex.tool` executes a normal tool call and resolves to the raw tool output object. Use it for shell and non-shell tools alike. Nested tool outputs stay inside JavaScript unless you emit them explicitly.\n- `codex.emitImage(...)` adds one image to the outer `js_repl` function output each time you call it, so you can call it multiple times to emit multiple images. It accepts a data URL, a single `input_image` item, an object like `{ bytes, mimeType }` containing encoded PNG/JPEG/WebP/GIF bytes, or a raw tool response object with exactly one image and no text. It rejects mixed text-and-image content.\n- `codex.tool(...)` and `codex.emitImage(...)` keep stable helper identities across cells. Saved references and persisted objects can reuse them in later cells, but async callbacks that fire after a cell finishes still fail because no exec is active.\n- Request full-resolution image processing with `detail: \"original\"` only when the `view_image` tool schema includes a `detail` argument. The same availability applies to `codex.emitImage(...)`: if `view_image.detail` is present, you may also pass `detail: \"original\"` there. Use this when high-fidelity image perception or precise localization is needed, especially for CUA agents.\n- Raw MCP image blocks can request the same behavior by returning `_meta: { \"codex/imageDetail\": \"original\" }` on the image content item.\n- Example of sharing an in-memory Playwright screenshot: `await codex.emitImage({ bytes: await page.screenshot({ type: \"jpeg\", quality: 85 }), mimeType: \"image/jpeg\", detail: \"original\" })`.\n- Example of sharing a local image tool result: `await codex.emitImage(codex.tool(\"view_image\", { path: \"/absolute/path\", detail: \"original\" }))`.\n- When encoding an image to send with `codex.emitImage(...)` or `view_image`, prefer JPEG at about 85 quality when lossy compression is acceptable; use PNG when transparency or lossless detail matters. Smaller uploads are faster and less likely to hit size limits.\n- Top-level bindings persist across cells. If a cell throws, prior bindings remain available and bindings that finished initializing before the throw often remain usable in later cells. For code you plan to reuse across cells, prefer declaring or assigning it in direct top-level statements before operations that might throw. If you hit `SyntaxError: Identifier 'x' has already been declared`, first reuse the existing binding, reassign a previously declared `let`, or pick a new descriptive name. Use `{ ... }` only for a short temporary block when you specifically need local scratch names; do not wrap an entire cell in block scope if you want those names reusable later. Reset the kernel with `js_repl_reset` only when you need a clean state.\n- Top-level static import declarations (for example `import x from \"./file.js\"`) are currently unsupported in `js_repl`; use dynamic imports with `await import(\"pkg\")`, `await import(\"./file.js\")`, or `await import(\"/abs/path/file.mjs\")` instead. Imported local files must be ESM `.js`/`.mjs` files and run in the same REPL VM context. Bare package imports always resolve from REPL-global search roots (`CODEX_JS_REPL_NODE_MODULE_DIRS`, then cwd), not relative to the imported file location. Local files may statically import only other local relative/absolute/`file://` `.js`/`.mjs` files; package and builtin imports from local files must stay dynamic. `import.meta.resolve()` returns importable strings such as `file://...`, bare package names, and `node:...` specifiers. Local file modules reload between execs, while top-level bindings persist until `js_repl_reset`.\n- Do not call tools directly; use `js_repl` + `codex.tool(...)` for all tool calls, including shell commands.\n- MCP tools (if any) can also be called by name via `codex.tool(...)`.\n- Avoid direct access to `process.stdout` / `process.stderr` / `process.stdin`; it can corrupt the JSON line protocol. Use `console.log`, `codex.tool(...)`, and `codex.emitImage(...)`."; - assert_eq!(res, expected); -} - /// When both system instructions and AGENTS.md docs are present the two /// should be concatenated with the separator. #[tokio::test] diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 2686173208e5..fdd36156020e 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -5275,8 +5275,6 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { codex_self_exe: None, codex_linux_sandbox_exe: None, main_execve_wrapper_exe: None, - js_repl_node_path: None, - js_repl_node_module_dirs: Vec::new(), zsh_path: None, hide_agent_reasoning: false, show_raw_agent_reasoning: false, @@ -5473,8 +5471,6 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { codex_self_exe: None, codex_linux_sandbox_exe: None, main_execve_wrapper_exe: None, - js_repl_node_path: None, - js_repl_node_module_dirs: Vec::new(), zsh_path: None, hide_agent_reasoning: false, show_raw_agent_reasoning: false, @@ -5625,8 +5621,6 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { codex_self_exe: None, codex_linux_sandbox_exe: None, main_execve_wrapper_exe: None, - js_repl_node_path: None, - js_repl_node_module_dirs: Vec::new(), zsh_path: None, hide_agent_reasoning: false, show_raw_agent_reasoning: false, @@ -5762,8 +5756,6 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { codex_self_exe: None, codex_linux_sandbox_exe: None, main_execve_wrapper_exe: None, - js_repl_node_path: None, - js_repl_node_module_dirs: Vec::new(), zsh_path: None, hide_agent_reasoning: false, show_raw_agent_reasoning: false, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 33fe18d1f487..11ae66de01ba 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -495,12 +495,6 @@ pub struct Config { /// code via [`ConfigOverrides`]. pub main_execve_wrapper_exe: Option, - /// Optional absolute path to the Node runtime used by `js_repl`. - pub js_repl_node_path: Option, - - /// Ordered list of directories to search for Node modules in `js_repl`. - pub js_repl_node_module_dirs: Vec, - /// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution. pub zsh_path: Option, @@ -1422,8 +1416,6 @@ pub struct ConfigOverrides { pub codex_self_exe: Option, pub codex_linux_sandbox_exe: Option, pub main_execve_wrapper_exe: Option, - pub js_repl_node_path: Option, - pub js_repl_node_module_dirs: Option>, pub zsh_path: Option, pub base_instructions: Option, pub developer_instructions: Option, @@ -1642,8 +1634,6 @@ impl Config { codex_self_exe, codex_linux_sandbox_exe, main_execve_wrapper_exe, - js_repl_node_path: js_repl_node_path_override, - js_repl_node_module_dirs: js_repl_node_module_dirs_override, zsh_path: zsh_path_override, base_instructions, developer_instructions, @@ -2177,20 +2167,6 @@ impl Config { ) .await?; let compact_prompt = compact_prompt.or(file_compact_prompt); - let js_repl_node_path = js_repl_node_path_override - .or(config_profile.js_repl_node_path.map(Into::into)) - .or(cfg.js_repl_node_path.map(Into::into)); - let js_repl_node_module_dirs = js_repl_node_module_dirs_override - .or_else(|| { - config_profile - .js_repl_node_module_dirs - .map(|dirs| dirs.into_iter().map(Into::into).collect::>()) - }) - .or_else(|| { - cfg.js_repl_node_module_dirs - .map(|dirs| dirs.into_iter().map(Into::into).collect::>()) - }) - .unwrap_or_default(); let zsh_path = zsh_path_override .or(config_profile.zsh_path.map(Into::into)) .or(cfg.zsh_path.map(Into::into)); @@ -2414,8 +2390,6 @@ impl Config { codex_self_exe, codex_linux_sandbox_exe, main_execve_wrapper_exe, - js_repl_node_path, - js_repl_node_module_dirs, zsh_path, hide_agent_reasoning: cfg.hide_agent_reasoning.unwrap_or(false), diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index db372c69441a..429bdce5eca4 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -148,8 +148,6 @@ struct GuardianReviewSessionReuseKey { mcp_servers: Constrained>, codex_linux_sandbox_exe: Option, main_execve_wrapper_exe: Option, - js_repl_node_path: Option, - js_repl_node_module_dirs: Vec, zsh_path: Option, features: ManagedFeatures, include_apply_patch_tool: bool, @@ -175,8 +173,6 @@ impl GuardianReviewSessionReuseKey { mcp_servers: spawn_config.mcp_servers.clone(), codex_linux_sandbox_exe: spawn_config.codex_linux_sandbox_exe.clone(), main_execve_wrapper_exe: spawn_config.main_execve_wrapper_exe.clone(), - js_repl_node_path: spawn_config.js_repl_node_path.clone(), - js_repl_node_module_dirs: spawn_config.js_repl_node_module_dirs.clone(), zsh_path: spawn_config.zsh_path.clone(), features: spawn_config.features.clone(), include_apply_patch_tool: spawn_config.include_apply_patch_tool, diff --git a/codex-rs/core/src/original_image_detail.rs b/codex-rs/core/src/original_image_detail.rs index adfed321b8bb..47d57d9a4751 100644 --- a/codex-rs/core/src/original_image_detail.rs +++ b/codex-rs/core/src/original_image_detail.rs @@ -1,3 +1,2 @@ pub(crate) use codex_tools::can_request_original_image_detail; -pub(crate) use codex_tools::normalize_output_image_detail; pub(crate) use codex_tools::sanitize_original_image_detail; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index b643be065fed..71003e6a038b 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -293,8 +293,6 @@ use crate::tasks::GhostSnapshotTask; use crate::tasks::ReviewTask; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; -use crate::tools::js_repl::JsReplHandle; -use crate::tools::js_repl::resolve_compatible_node; use crate::tools::network_approval::NetworkApprovalService; use crate::tools::network_approval::build_blocked_request_observer; use crate::tools::network_approval::build_network_policy_decider; @@ -500,34 +498,6 @@ impl Codex { let _ = config.features.disable(Feature::Collab); } - if config.features.enabled(Feature::JsRepl) - && let Err(err) = resolve_compatible_node(config.js_repl_node_path.as_deref()).await - { - let _ = config.features.disable(Feature::JsRepl); - let _ = config.features.disable(Feature::JsReplToolsOnly); - let message = if config.features.enabled(Feature::JsRepl) { - format!( - "`js_repl` remains enabled because enterprise requirements pin it on, but the configured Node runtime is unavailable or incompatible. {err}" - ) - } else { - format!( - "Disabled `js_repl` for this session because the configured Node runtime is unavailable or incompatible. {err}" - ) - }; - warn!("{message}"); - config.startup_warnings.push(message); - } - if config.features.enabled(Feature::CodeMode) - && let Err(err) = resolve_compatible_node(config.js_repl_node_path.as_deref()).await - { - let message = format!( - "Disabled `exec` for this session because the configured Node runtime is unavailable or incompatible. {err}" - ); - warn!("{message}"); - let _ = config.features.disable(Feature::CodeMode); - config.startup_warnings.push(message); - } - let user_instructions = AgentsMdManager::new(&config) .user_instructions(environment.as_deref()) .await; diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 4a995d85ed4c..9d502ab1d707 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -136,7 +136,6 @@ pub(super) async fn spawn_review_thread( codex_self_exe: parent_turn_context.codex_self_exe.clone(), codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(), tool_call_gate: Arc::new(ReadinessFlag::new()), - js_repl: Arc::clone(&sess.js_repl), dynamic_tools: parent_turn_context.dynamic_tools.clone(), truncation_policy: model_info.truncation_policy.into(), turn_metadata_state, diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index cbc060b0ce05..1773b256e0d7 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -25,7 +25,6 @@ pub(crate) struct Session { pub(super) idle_pending_input: Mutex>, // TODO (jif) merge with mailbox! pub(crate) guardian_review_session: GuardianReviewSessionManager, pub(crate) services: SessionServices, - pub(super) js_repl: Arc, pub(super) next_internal_sub_id: AtomicU64, } @@ -766,18 +765,12 @@ impl Session { config.features.enabled(Feature::RuntimeMetrics), Self::build_model_client_beta_features_header(config.as_ref()), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new( - config.js_repl_node_path.clone(), - ), + code_mode_service: crate::tools::code_mode::CodeModeService::new(), environment_manager, }; services .model_client .set_window_generation(window_generation); - let js_repl = Arc::new(JsReplHandle::with_node_path( - config.js_repl_node_path.clone(), - config.js_repl_node_module_dirs.clone(), - )); let (out_of_band_elicitation_paused, _out_of_band_elicitation_paused_rx) = watch::channel(false); @@ -798,7 +791,6 @@ impl Session { idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: GuardianReviewSessionManager::default(), services, - js_repl, next_internal_sub_id: AtomicU64::new(0), }); if let Some(network_policy_decider_session) = network_policy_decider_session { diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 3208f97dcb67..6dd26524cf5f 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -3324,15 +3324,9 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { config.features.enabled(Feature::RuntimeMetrics), Session::build_model_client_beta_features_header(config.as_ref()), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new( - config.js_repl_node_path.clone(), - ), + code_mode_service: crate::tools::code_mode::CodeModeService::new(), environment_manager: Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()), }; - let js_repl = Arc::new(JsReplHandle::with_node_path( - config.js_repl_node_path.clone(), - config.js_repl_node_module_dirs.clone(), - )); let plugin_outcome = services .plugins_manager @@ -3366,7 +3360,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { turn_environments, session_configuration.cwd.clone(), "turn_id".to_string(), - Arc::clone(&js_repl), skills_outcome, ); @@ -3387,7 +3380,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(), services, - js_repl, next_internal_sub_id: AtomicU64::new(0), }; @@ -4687,15 +4679,9 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( config.features.enabled(Feature::RuntimeMetrics), Session::build_model_client_beta_features_header(config.as_ref()), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new( - config.js_repl_node_path.clone(), - ), + code_mode_service: crate::tools::code_mode::CodeModeService::new(), environment_manager: Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()), }; - let js_repl = Arc::new(JsReplHandle::with_node_path( - config.js_repl_node_path.clone(), - config.js_repl_node_module_dirs.clone(), - )); let plugin_outcome = services .plugins_manager @@ -4729,7 +4715,6 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( turn_environments, session_configuration.cwd.clone(), "turn_id".to_string(), - Arc::clone(&js_repl), skills_outcome, )); @@ -4750,7 +4735,6 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( idle_pending_input: Mutex::new(Vec::new()), guardian_review_session: crate::guardian::GuardianReviewSessionManager::default(), services, - js_repl, next_internal_sub_id: AtomicU64::new(0), }); diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index f3ca9d37b909..d2e6b5a214f1 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -87,7 +87,6 @@ pub(crate) struct TurnContext { pub(crate) codex_linux_sandbox_exe: Option, pub(crate) tool_call_gate: Arc, pub(crate) truncation_policy: TruncationPolicy, - pub(crate) js_repl: Arc, pub(crate) dynamic_tools: Vec, pub(crate) turn_metadata_state: Arc, pub(crate) turn_skills: TurnSkillsContext, @@ -227,7 +226,6 @@ impl TurnContext { codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.clone(), tool_call_gate: Arc::new(ReadinessFlag::new()), truncation_policy, - js_repl: Arc::clone(&self.js_repl), dynamic_tools: self.dynamic_tools.clone(), turn_metadata_state: self.turn_metadata_state.clone(), turn_skills: self.turn_skills.clone(), @@ -406,7 +404,6 @@ impl Session { environments: Vec, cwd: AbsolutePathBuf, sub_id: String, - js_repl: Arc, skills_outcome: Arc, ) -> TurnContext { let reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); @@ -497,7 +494,6 @@ impl Session { codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), tool_call_gate: Arc::new(ReadinessFlag::new()), truncation_policy: model_info.truncation_policy.into(), - js_repl, dynamic_tools: session_configuration.dynamic_tools.clone(), turn_metadata_state, turn_skills: TurnSkillsContext::new(skills_outcome), @@ -682,7 +678,6 @@ impl Session { turn_environments, cwd, sub_id, - Arc::clone(&self.js_repl), skills_outcome, ); turn_context.realtime_active = self.conversation.running_state().await.is_some(); diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index b0ec96cfedbb..f981b62ba710 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -677,14 +677,6 @@ impl Session { .await; } - pub(crate) async fn cleanup_after_interrupt(&self, turn_context: &Arc) { - if let Some(manager) = turn_context.js_repl.manager_if_initialized() - && let Err(err) = manager.interrupt_turn_exec(&turn_context.sub_id).await - { - warn!("failed to interrupt js_repl kernel: {err}"); - } - } - async fn handle_task_abort(self: &Arc, task: RunningTask, reason: TurnAbortReason) { let sub_id = task.turn_context.sub_id.clone(); if task.cancellation_token.is_cancelled() { @@ -713,23 +705,19 @@ impl Session { .abort(session_ctx, Arc::clone(&task.turn_context)) .await; - if reason == TurnAbortReason::Interrupted { - self.cleanup_after_interrupt(&task.turn_context).await; - - if let Some(marker) = interrupted_turn_history_marker( + if reason == TurnAbortReason::Interrupted + && let Some(marker) = interrupted_turn_history_marker( InterruptedTurnHistoryMarker::from_config(task.turn_context.config.as_ref()), - ) { - self.record_into_history(std::slice::from_ref(&marker), task.turn_context.as_ref()) - .await; - self.persist_rollout_items(&[RolloutItem::ResponseItem(marker)]) - .await; - // Ensure the marker is durably visible before emitting TurnAborted: some clients - // synchronously re-read the rollout on receipt of the abort event. - if let Err(err) = self.flush_rollout().await { - warn!( - "failed to flush interrupted-turn marker before emitting TurnAborted: {err}" - ); - } + ) + { + self.record_into_history(std::slice::from_ref(&marker), task.turn_context.as_ref()) + .await; + self.persist_rollout_items(&[RolloutItem::ResponseItem(marker)]) + .await; + // Ensure the marker is durably visible before emitting TurnAborted: some clients + // synchronously re-read the rollout on receipt of the abort event. + if let Err(err) = self.flush_rollout().await { + warn!("failed to flush interrupted-turn marker before emitting TurnAborted: {err}"); } } diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index 8032b9f31863..0bfd080ae0f6 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -3,7 +3,6 @@ mod response_adapter; mod wait_handler; use std::collections::HashSet; -use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -62,7 +61,7 @@ pub(crate) struct CodeModeService { } impl CodeModeService { - pub(crate) fn new(_js_repl_node_path: Option) -> Self { + pub(crate) fn new() -> Self { Self { inner: codex_code_mode::CodeModeService::new(), } diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index 89aef248a67a..f65baeb6dcd6 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -33,7 +33,6 @@ pub type SharedTurnDiffTracker = Arc>; #[derive(Clone, Debug, Eq, PartialEq)] pub enum ToolCallSource { Direct, - JsRepl, CodeMode { /// Runtime cell that issued the nested tool request. cell_id: String, diff --git a/codex-rs/core/src/tools/handlers/js_repl.rs b/codex-rs/core/src/tools/handlers/js_repl.rs deleted file mode 100644 index 906e1bb63792..000000000000 --- a/codex-rs/core/src/tools/handlers/js_repl.rs +++ /dev/null @@ -1,300 +0,0 @@ -use serde_json::Value as JsonValue; -use std::sync::Arc; -use std::time::Duration; -use std::time::Instant; - -use crate::function_tool::FunctionCallError; -use crate::tools::context::FunctionToolOutput; -use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolPayload; -use crate::tools::events::ToolEmitter; -use crate::tools::events::ToolEventCtx; -use crate::tools::events::ToolEventFailure; -use crate::tools::events::ToolEventStage; -use crate::tools::handlers::parse_arguments; -use crate::tools::js_repl::JS_REPL_PRAGMA_PREFIX; -use crate::tools::js_repl::JsReplArgs; -use crate::tools::registry::ToolHandler; -use crate::tools::registry::ToolKind; -use codex_features::Feature; -use codex_protocol::exec_output::ExecToolCallOutput; -use codex_protocol::exec_output::StreamOutput; -use codex_protocol::models::FunctionCallOutputContentItem; -use codex_protocol::protocol::ExecCommandSource; - -pub struct JsReplHandler; -pub struct JsReplResetHandler; - -fn join_outputs(stdout: &str, stderr: &str) -> String { - if stdout.is_empty() { - stderr.to_string() - } else if stderr.is_empty() { - stdout.to_string() - } else { - format!("{stdout}\n{stderr}") - } -} - -fn build_js_repl_exec_output( - output: &str, - error: Option<&str>, - duration: Duration, -) -> ExecToolCallOutput { - let stdout = output.to_string(); - let stderr = error.unwrap_or("").to_string(); - let aggregated_output = join_outputs(&stdout, &stderr); - ExecToolCallOutput { - exit_code: if error.is_some() { 1 } else { 0 }, - stdout: StreamOutput::new(stdout), - stderr: StreamOutput::new(stderr), - aggregated_output: StreamOutput::new(aggregated_output), - duration, - timed_out: false, - } -} - -async fn emit_js_repl_exec_begin( - session: &crate::session::session::Session, - turn: &crate::session::turn_context::TurnContext, - call_id: &str, -) { - let emitter = ToolEmitter::shell( - vec!["js_repl".to_string()], - turn.cwd.clone(), - ExecCommandSource::Agent, - /*freeform*/ false, - ); - let ctx = ToolEventCtx::new(session, turn, call_id, /*turn_diff_tracker*/ None); - emitter.emit(ctx, ToolEventStage::Begin).await; -} - -async fn emit_js_repl_exec_end( - session: &crate::session::session::Session, - turn: &crate::session::turn_context::TurnContext, - call_id: &str, - output: &str, - error: Option<&str>, - duration: Duration, -) { - let exec_output = build_js_repl_exec_output(output, error, duration); - let emitter = ToolEmitter::shell( - vec!["js_repl".to_string()], - turn.cwd.clone(), - ExecCommandSource::Agent, - /*freeform*/ false, - ); - let ctx = ToolEventCtx::new(session, turn, call_id, /*turn_diff_tracker*/ None); - let stage = if error.is_some() { - ToolEventStage::Failure(ToolEventFailure::Output(exec_output)) - } else { - ToolEventStage::Success(exec_output) - }; - emitter.emit(ctx, stage).await; -} -impl ToolHandler for JsReplHandler { - type Output = FunctionToolOutput; - - fn kind(&self) -> ToolKind { - ToolKind::Function - } - - fn matches_kind(&self, payload: &ToolPayload) -> bool { - matches!( - payload, - ToolPayload::Function { .. } | ToolPayload::Custom { .. } - ) - } - - async fn handle(&self, invocation: ToolInvocation) -> Result { - let ToolInvocation { - session, - turn, - cancellation_token, - tracker, - payload, - call_id, - .. - } = invocation; - - if !session.features().enabled(Feature::JsRepl) { - return Err(FunctionCallError::RespondToModel( - "js_repl is disabled by feature flag".to_string(), - )); - } - - let args = match payload { - ToolPayload::Function { arguments } => parse_arguments(&arguments)?, - ToolPayload::Custom { input } => parse_freeform_args(&input)?, - _ => { - return Err(FunctionCallError::RespondToModel( - "js_repl expects custom or function payload".to_string(), - )); - } - }; - let manager = turn.js_repl.manager().await?; - let started_at = Instant::now(); - emit_js_repl_exec_begin(session.as_ref(), turn.as_ref(), &call_id).await; - let result = manager - .execute_with_cancellation( - Arc::clone(&session), - Arc::clone(&turn), - cancellation_token, - tracker, - args, - ) - .await; - let result = match result { - Ok(result) => result, - Err(err) => { - let message = err.to_string(); - emit_js_repl_exec_end( - session.as_ref(), - turn.as_ref(), - &call_id, - "", - Some(&message), - started_at.elapsed(), - ) - .await; - return Err(err); - } - }; - - let content = result.output; - let mut items = Vec::with_capacity(result.content_items.len() + 1); - if !content.is_empty() { - items.push(FunctionCallOutputContentItem::InputText { - text: content.clone(), - }); - } - items.extend(result.content_items); - - emit_js_repl_exec_end( - session.as_ref(), - turn.as_ref(), - &call_id, - &content, - /*error*/ None, - started_at.elapsed(), - ) - .await; - - if items.is_empty() { - Ok(FunctionToolOutput::from_text(content, Some(true))) - } else { - Ok(FunctionToolOutput::from_content(items, Some(true))) - } - } -} - -impl ToolHandler for JsReplResetHandler { - type Output = FunctionToolOutput; - - fn kind(&self) -> ToolKind { - ToolKind::Function - } - - async fn handle(&self, invocation: ToolInvocation) -> Result { - if !invocation.session.features().enabled(Feature::JsRepl) { - return Err(FunctionCallError::RespondToModel( - "js_repl is disabled by feature flag".to_string(), - )); - } - let manager = invocation.turn.js_repl.manager().await?; - manager.reset().await?; - Ok(FunctionToolOutput::from_text( - "js_repl kernel reset".to_string(), - Some(true), - )) - } -} - -fn parse_freeform_args(input: &str) -> Result { - if input.trim().is_empty() { - return Err(FunctionCallError::RespondToModel( - "js_repl expects raw JavaScript tool input (non-empty). Provide JS source text, optionally with first-line `// codex-js-repl: ...`." - .to_string(), - )); - } - - let mut args = JsReplArgs { - code: input.to_string(), - timeout_ms: None, - }; - - let mut lines = input.splitn(2, '\n'); - let first_line = lines.next().unwrap_or_default(); - let rest = lines.next().unwrap_or_default(); - let trimmed = first_line.trim_start(); - let Some(pragma) = trimmed.strip_prefix(JS_REPL_PRAGMA_PREFIX) else { - reject_json_or_quoted_source(&args.code)?; - return Ok(args); - }; - - let mut timeout_ms: Option = None; - let directive = pragma.trim(); - if !directive.is_empty() { - for token in directive.split_whitespace() { - let (key, value) = token.split_once('=').ok_or_else(|| { - FunctionCallError::RespondToModel(format!( - "js_repl pragma expects space-separated key=value pairs (supported keys: timeout_ms); got `{token}`" - )) - })?; - match key { - "timeout_ms" => { - if timeout_ms.is_some() { - return Err(FunctionCallError::RespondToModel( - "js_repl pragma specifies timeout_ms more than once".to_string(), - )); - } - let parsed = value.parse::().map_err(|_| { - FunctionCallError::RespondToModel(format!( - "js_repl pragma timeout_ms must be an integer; got `{value}`" - )) - })?; - timeout_ms = Some(parsed); - } - _ => { - return Err(FunctionCallError::RespondToModel(format!( - "js_repl pragma only supports timeout_ms; got `{key}`" - ))); - } - } - } - } - - if rest.trim().is_empty() { - return Err(FunctionCallError::RespondToModel( - "js_repl pragma must be followed by JavaScript source on subsequent lines".to_string(), - )); - } - - reject_json_or_quoted_source(rest)?; - args.code = rest.to_string(); - args.timeout_ms = timeout_ms; - Ok(args) -} - -fn reject_json_or_quoted_source(code: &str) -> Result<(), FunctionCallError> { - let trimmed = code.trim(); - if trimmed.starts_with("```") { - return Err(FunctionCallError::RespondToModel( - "js_repl expects raw JavaScript source, not markdown code fences. Resend plain JS only (optional first line `// codex-js-repl: ...`)." - .to_string(), - )); - } - let Ok(value) = serde_json::from_str::(trimmed) else { - return Ok(()); - }; - match value { - JsonValue::Object(_) | JsonValue::String(_) => Err(FunctionCallError::RespondToModel( - "js_repl is a freeform tool and expects raw JavaScript source. Resend plain JS only (optional first line `// codex-js-repl: ...`); do not send JSON (`{\"code\":...}`), quoted code, or markdown fences." - .to_string(), - )), - _ => Ok(()), - } -} - -#[cfg(test)] -#[path = "js_repl_tests.rs"] -mod tests; diff --git a/codex-rs/core/src/tools/handlers/js_repl_tests.rs b/codex-rs/core/src/tools/handlers/js_repl_tests.rs deleted file mode 100644 index 0f3274409b92..000000000000 --- a/codex-rs/core/src/tools/handlers/js_repl_tests.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::time::Duration; - -use super::parse_freeform_args; -use crate::session::tests::make_session_and_context_with_rx; -use codex_protocol::protocol::EventMsg; -use codex_protocol::protocol::ExecCommandSource; -use pretty_assertions::assert_eq; - -#[test] -fn parse_freeform_args_without_pragma() { - let args = parse_freeform_args("console.log('ok');").expect("parse args"); - assert_eq!(args.code, "console.log('ok');"); - assert_eq!(args.timeout_ms, None); -} - -#[test] -fn parse_freeform_args_with_pragma() { - let input = "// codex-js-repl: timeout_ms=15000\nconsole.log('ok');"; - let args = parse_freeform_args(input).expect("parse args"); - assert_eq!(args.code, "console.log('ok');"); - assert_eq!(args.timeout_ms, Some(15_000)); -} - -#[test] -fn parse_freeform_args_rejects_unknown_key() { - let err = parse_freeform_args("// codex-js-repl: nope=1\nconsole.log('ok');") - .expect_err("expected error"); - assert_eq!( - err.to_string(), - "js_repl pragma only supports timeout_ms; got `nope`" - ); -} - -#[test] -fn parse_freeform_args_rejects_reset_key() { - let err = parse_freeform_args("// codex-js-repl: reset=true\nconsole.log('ok');") - .expect_err("expected error"); - assert_eq!( - err.to_string(), - "js_repl pragma only supports timeout_ms; got `reset`" - ); -} - -#[test] -fn parse_freeform_args_rejects_json_wrapped_code() { - let err = parse_freeform_args(r#"{"code":"await doThing()"}"#).expect_err("expected error"); - assert_eq!( - err.to_string(), - "js_repl is a freeform tool and expects raw JavaScript source. Resend plain JS only (optional first line `// codex-js-repl: ...`); do not send JSON (`{\"code\":...}`), quoted code, or markdown fences." - ); -} - -#[tokio::test] -async fn emit_js_repl_exec_end_sends_event() { - let (session, turn, rx) = make_session_and_context_with_rx().await; - super::emit_js_repl_exec_end( - session.as_ref(), - turn.as_ref(), - "call-1", - "hello", - /*error*/ None, - Duration::from_millis(12), - ) - .await; - - let event = tokio::time::timeout(Duration::from_secs(5), async { - loop { - let event = rx.recv().await.expect("event"); - if let EventMsg::ExecCommandEnd(end) = event.msg { - break end; - } - } - }) - .await - .expect("timed out waiting for exec end"); - - assert_eq!(event.call_id, "call-1"); - assert_eq!(event.turn_id, turn.sub_id); - assert_eq!(event.command, vec!["js_repl".to_string()]); - assert_eq!(event.cwd, turn.cwd); - assert_eq!(event.source, ExecCommandSource::Agent); - assert_eq!(event.interaction_input, None); - assert_eq!(event.stdout, "hello"); - assert_eq!(event.stderr, ""); - assert!(event.aggregated_output.contains("hello")); - assert_eq!(event.exit_code, 0); - assert_eq!(event.duration, Duration::from_millis(12)); - assert!(event.formatted_output.contains("hello")); - assert!(!event.parsed_cmd.is_empty()); -} diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 7878c1092c5f..757b0d94bddb 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -1,7 +1,6 @@ pub(crate) mod agent_jobs; pub(crate) mod apply_patch; mod dynamic; -mod js_repl; mod list_dir; mod mcp; mod mcp_resource; @@ -37,8 +36,6 @@ pub use apply_patch::ApplyPatchHandler; use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::protocol::AskForApproval; pub use dynamic::DynamicToolHandler; -pub use js_repl::JsReplHandler; -pub use js_repl::JsReplResetHandler; pub use list_dir::ListDirHandler; pub use mcp::McpHandler; pub use mcp_resource::McpResourceHandler; diff --git a/codex-rs/core/src/tools/js_repl/kernel.js b/codex-rs/core/src/tools/js_repl/kernel.js deleted file mode 100644 index 3b1972a8494f..000000000000 --- a/codex-rs/core/src/tools/js_repl/kernel.js +++ /dev/null @@ -1,1833 +0,0 @@ -// Node-based kernel for js_repl. -// Communicates over JSON lines on stdin/stdout. -// Requires Node started with --experimental-vm-modules. - -const { Buffer } = require("node:buffer"); -const { AsyncLocalStorage } = require("node:async_hooks"); -const crypto = require("node:crypto"); -const fs = require("node:fs"); -const { builtinModules, createRequire } = require("node:module"); -const { performance } = require("node:perf_hooks"); -const path = require("node:path"); -const { URL, URLSearchParams, fileURLToPath, pathToFileURL } = require( - "node:url", -); -const { inspect, TextDecoder, TextEncoder } = require("node:util"); -const vm = require("node:vm"); - -const { SourceTextModule, SyntheticModule } = vm; -const meriyahPromise = import("./meriyah.umd.min.js").then( - (m) => m.default ?? m, -); - -// vm contexts start with very few globals. Populate common Node/web globals -// so snippets and dependencies behave like a normal modern JS runtime. -const context = vm.createContext({}); -context.globalThis = context; -context.global = context; -context.Buffer = Buffer; -context.console = console; -context.URL = URL; -context.URLSearchParams = URLSearchParams; -if (typeof TextEncoder !== "undefined") { - context.TextEncoder = TextEncoder; -} -if (typeof TextDecoder !== "undefined") { - context.TextDecoder = TextDecoder; -} -if (typeof AbortController !== "undefined") { - context.AbortController = AbortController; -} -if (typeof AbortSignal !== "undefined") { - context.AbortSignal = AbortSignal; -} -if (typeof structuredClone !== "undefined") { - context.structuredClone = structuredClone; -} -if (typeof fetch !== "undefined") { - context.fetch = fetch; -} -if (typeof Headers !== "undefined") { - context.Headers = Headers; -} -if (typeof Request !== "undefined") { - context.Request = Request; -} -if (typeof Response !== "undefined") { - context.Response = Response; -} -if (typeof performance !== "undefined") { - context.performance = performance; -} -context.crypto = crypto.webcrypto ?? crypto; -context.setTimeout = setTimeout; -context.clearTimeout = clearTimeout; -context.setInterval = setInterval; -context.clearInterval = clearInterval; -context.queueMicrotask = queueMicrotask; -if (typeof setImmediate !== "undefined") { - context.setImmediate = setImmediate; - context.clearImmediate = clearImmediate; -} -context.atob = (data) => Buffer.from(data, "base64").toString("binary"); -context.btoa = (data) => Buffer.from(data, "binary").toString("base64"); - -/** - * @typedef {{ name: string, kind: "const"|"let"|"var"|"function"|"class" }} Binding - */ - -// REPL state model: -// - Every exec is compiled as a fresh ESM "cell". -// - `previousModule` is the most recently committed module namespace. -// - `previousBindings` tracks which top-level names should be carried forward. -// Each new cell imports a synthetic view of the previous namespace and -// redeclares those names so user variables behave like a persistent REPL. -let previousModule = null; -/** @type {Binding[]} */ -let previousBindings = []; -let cellCounter = 0; -let internalBindingCounter = 0; -const internalBindingSalt = (() => { - const raw = process.env.CODEX_THREAD_ID ?? ""; - const sanitized = raw.replace(/[^A-Za-z0-9_$]/g, "_"); - return sanitized || "session"; -})(); -let activeExecId = null; -let fatalExitScheduled = false; - -const builtinModuleSet = new Set([ - ...builtinModules, - ...builtinModules.map((name) => `node:${name}`), -]); -const deniedBuiltinModules = new Set([ - "process", - "node:process", - "child_process", - "node:child_process", - "worker_threads", - "node:worker_threads", -]); - -function toNodeBuiltinSpecifier(specifier) { - return specifier.startsWith("node:") ? specifier : `node:${specifier}`; -} - -function isDeniedBuiltin(specifier) { - const normalized = specifier.startsWith("node:") - ? specifier.slice(5) - : specifier; - return ( - deniedBuiltinModules.has(specifier) || deniedBuiltinModules.has(normalized) - ); -} - -/** @type {Map void>} */ -const pendingTool = new Map(); -/** @type {Map void>} */ -const pendingEmitImage = new Map(); -let toolCounter = 0; -let emitImageCounter = 0; -const execContextStorage = new AsyncLocalStorage(); -const cwd = process.cwd(); -const tmpDir = process.env.CODEX_JS_TMP_DIR || cwd; -const homeDir = process.env.HOME ?? null; -const nodeModuleDirEnv = process.env.CODEX_JS_REPL_NODE_MODULE_DIRS ?? ""; -const moduleSearchBases = (() => { - const bases = []; - const seen = new Set(); - for (const entry of nodeModuleDirEnv.split(path.delimiter)) { - const trimmed = entry.trim(); - if (!trimmed) { - continue; - } - const resolved = path.isAbsolute(trimmed) - ? trimmed - : path.resolve(process.cwd(), trimmed); - const base = - path.basename(resolved) === "node_modules" - ? path.dirname(resolved) - : resolved; - if (seen.has(base)) { - continue; - } - seen.add(base); - bases.push(base); - } - if (!seen.has(cwd)) { - bases.push(cwd); - } - return bases; -})(); - -const importResolveConditions = new Set(["node", "import"]); -const requireByBase = new Map(); -const linkedFileModules = new Map(); -const linkedNativeModules = new Map(); -const linkedModuleEvaluations = new Map(); - -function clearLocalFileModuleCaches() { - linkedFileModules.clear(); - linkedModuleEvaluations.clear(); -} - -function canonicalizePath(value) { - try { - return fs.realpathSync.native(value); - } catch { - return value; - } -} - -function resolveResultToUrl(resolved) { - if (resolved.kind === "builtin") { - return resolved.specifier; - } - if (resolved.kind === "file") { - return pathToFileURL(resolved.path).href; - } - if (resolved.kind === "package") { - return resolved.specifier; - } - throw new Error(`Unsupported module resolution kind: ${resolved.kind}`); -} - -function setImportMeta(meta, mod, isMain = false) { - meta.url = pathToFileURL(mod.identifier).href; - meta.filename = mod.identifier; - meta.dirname = path.dirname(mod.identifier); - meta.main = isMain; - meta.resolve = (specifier) => - resolveResultToUrl(resolveSpecifier(specifier, mod.identifier)); -} - -function getRequireForBase(base) { - let req = requireByBase.get(base); - if (!req) { - req = createRequire(path.join(base, "__codex_js_repl__.cjs")); - requireByBase.set(base, req); - } - return req; -} - -function isModuleNotFoundError(err) { - return ( - err?.code === "MODULE_NOT_FOUND" || err?.code === "ERR_MODULE_NOT_FOUND" - ); -} - -function isWithinBaseNodeModules(base, resolvedPath) { - const canonicalBase = canonicalizePath(base); - const canonicalResolved = canonicalizePath(resolvedPath); - const nodeModulesRoot = path.resolve(canonicalBase, "node_modules"); - const relative = path.relative(nodeModulesRoot, canonicalResolved); - return ( - relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative) - ); -} - -function isExplicitRelativePathSpecifier(specifier) { - return ( - specifier.startsWith("./") || - specifier.startsWith("../") || - specifier.startsWith(".\\") || - specifier.startsWith("..\\") - ); -} - -function isFileUrlSpecifier(specifier) { - if (typeof specifier !== "string" || !specifier.startsWith("file:")) { - return false; - } - try { - return new URL(specifier).protocol === "file:"; - } catch { - return false; - } -} - -function isPathSpecifier(specifier) { - if ( - typeof specifier !== "string" || - !specifier || - specifier.trim() !== specifier - ) { - return false; - } - return ( - isExplicitRelativePathSpecifier(specifier) || - path.isAbsolute(specifier) || - isFileUrlSpecifier(specifier) - ); -} - -function isBarePackageSpecifier(specifier) { - if ( - typeof specifier !== "string" || - !specifier || - specifier.trim() !== specifier - ) { - return false; - } - if (specifier.startsWith("./") || specifier.startsWith("../")) { - return false; - } - if (specifier.startsWith("/") || specifier.startsWith("\\")) { - return false; - } - if (path.isAbsolute(specifier)) { - return false; - } - if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(specifier)) { - return false; - } - if (specifier.includes("\\")) { - return false; - } - return true; -} - -function resolveBareSpecifier(specifier) { - let firstResolutionError = null; - - for (const base of moduleSearchBases) { - try { - const resolved = getRequireForBase(base).resolve(specifier, { - conditions: importResolveConditions, - }); - if (isWithinBaseNodeModules(base, resolved)) { - return resolved; - } - // Ignore resolutions that escape this base via parent node_modules lookup. - } catch (err) { - if (isModuleNotFoundError(err)) { - continue; - } - if (!firstResolutionError) { - firstResolutionError = err; - } - } - } - - if (firstResolutionError) { - throw firstResolutionError; - } - return null; -} - -function resolvePathSpecifier(specifier, referrerIdentifier = null) { - let candidate; - if (isFileUrlSpecifier(specifier)) { - try { - candidate = fileURLToPath(new URL(specifier)); - } catch (err) { - throw new Error(`Failed to resolve module "${specifier}": ${err.message}`); - } - } else { - const baseDir = - referrerIdentifier && path.isAbsolute(referrerIdentifier) - ? path.dirname(referrerIdentifier) - : process.cwd(); - candidate = path.isAbsolute(specifier) - ? specifier - : path.resolve(baseDir, specifier); - } - - let resolvedPath; - try { - resolvedPath = fs.realpathSync.native(candidate); - } catch (err) { - if (err?.code === "ENOENT") { - throw new Error(`Module not found: ${specifier}`); - } - throw new Error(`Failed to resolve module "${specifier}": ${err.message}`); - } - - let stats; - try { - stats = fs.statSync(resolvedPath); - } catch (err) { - if (err?.code === "ENOENT") { - throw new Error(`Module not found: ${specifier}`); - } - throw new Error(`Failed to inspect module "${specifier}": ${err.message}`); - } - - if (!stats.isFile()) { - throw new Error( - `Unsupported import specifier "${specifier}" in js_repl. Directory imports are not supported.`, - ); - } - - const extension = path.extname(resolvedPath).toLowerCase(); - if (extension !== ".js" && extension !== ".mjs") { - throw new Error( - `Unsupported import specifier "${specifier}" in js_repl. Only .js and .mjs files are supported.`, - ); - } - - return { kind: "file", path: resolvedPath }; -} - -function resolveSpecifier(specifier, referrerIdentifier = null) { - if (specifier.startsWith("node:") || builtinModuleSet.has(specifier)) { - if (isDeniedBuiltin(specifier)) { - throw new Error( - `Importing module "${specifier}" is not allowed in js_repl`, - ); - } - return { kind: "builtin", specifier: toNodeBuiltinSpecifier(specifier) }; - } - - if (isPathSpecifier(specifier)) { - return resolvePathSpecifier(specifier, referrerIdentifier); - } - - if (!isBarePackageSpecifier(specifier)) { - throw new Error( - `Unsupported import specifier "${specifier}" in js_repl. Use a package name like "lodash" or "@scope/pkg", or a relative/absolute/file:// .js/.mjs path.`, - ); - } - - const resolvedBare = resolveBareSpecifier(specifier); - if (!resolvedBare) { - throw new Error(`Module not found: ${specifier}`); - } - - return { kind: "package", path: resolvedBare, specifier }; -} - -function importNativeResolved(resolved) { - if (resolved.kind === "builtin") { - return import(resolved.specifier); - } - if (resolved.kind === "package") { - return import(pathToFileURL(resolved.path).href); - } - throw new Error(`Unsupported module resolution kind: ${resolved.kind}`); -} - -async function loadLinkedNativeModule(resolved) { - const key = - resolved.kind === "builtin" - ? `builtin:${resolved.specifier}` - : `package:${resolved.path}`; - let modulePromise = linkedNativeModules.get(key); - if (!modulePromise) { - modulePromise = (async () => { - const namespace = await importNativeResolved(resolved); - const exportNames = Object.getOwnPropertyNames(namespace); - return new SyntheticModule( - exportNames, - function initSyntheticModule() { - for (const name of exportNames) { - this.setExport(name, namespace[name]); - } - }, - { context }, - ); - })(); - linkedNativeModules.set(key, modulePromise); - } - return modulePromise; -} - -async function loadLinkedFileModule(modulePath) { - let module = linkedFileModules.get(modulePath); - if (!module) { - const source = fs.readFileSync(modulePath, "utf8"); - module = new SourceTextModule(source, { - context, - identifier: modulePath, - initializeImportMeta(meta, mod) { - setImportMeta(meta, mod, false); - }, - importModuleDynamically(specifier, referrer) { - return importResolved(resolveSpecifier(specifier, referrer?.identifier)); - }, - }); - linkedFileModules.set(modulePath, module); - } - if (module.status === "unlinked") { - await module.link(async (specifier, referencingModule) => { - const resolved = resolveSpecifier(specifier, referencingModule?.identifier); - if (resolved.kind !== "file") { - throw new Error( - `Static import "${specifier}" is not supported from js_repl local files. Use await import("${specifier}") instead.`, - ); - } - return loadLinkedFileModule(resolved.path); - }); - } - return module; -} - -async function loadLinkedModule(resolved) { - if (resolved.kind === "file") { - return loadLinkedFileModule(resolved.path); - } - if (resolved.kind === "builtin" || resolved.kind === "package") { - return loadLinkedNativeModule(resolved); - } - throw new Error(`Unsupported module resolution kind: ${resolved.kind}`); -} - -async function importResolved(resolved) { - if (resolved.kind === "file") { - const module = await loadLinkedFileModule(resolved.path); - let evaluation = linkedModuleEvaluations.get(resolved.path); - if (!evaluation) { - evaluation = module.evaluate(); - linkedModuleEvaluations.set(resolved.path, evaluation); - } - await evaluation; - return module.namespace; - } - return importNativeResolved(resolved); -} - -function collectPatternNames(pattern, kind, map) { - if (!pattern) return; - switch (pattern.type) { - case "Identifier": - if (!map.has(pattern.name)) map.set(pattern.name, kind); - return; - case "ObjectPattern": - for (const prop of pattern.properties ?? []) { - if (prop.type === "Property") { - collectPatternNames(prop.value, kind, map); - } else if (prop.type === "RestElement") { - collectPatternNames(prop.argument, kind, map); - } - } - return; - case "ArrayPattern": - for (const elem of pattern.elements ?? []) { - if (!elem) continue; - if (elem.type === "RestElement") { - collectPatternNames(elem.argument, kind, map); - } else { - collectPatternNames(elem, kind, map); - } - } - return; - case "AssignmentPattern": - collectPatternNames(pattern.left, kind, map); - return; - case "RestElement": - collectPatternNames(pattern.argument, kind, map); - return; - default: - return; - } -} - -function collectBindings(ast) { - const map = new Map(); - for (const stmt of ast.body ?? []) { - if (stmt.type === "VariableDeclaration") { - const kind = stmt.kind; - for (const decl of stmt.declarations) { - collectPatternNames(decl.id, kind, map); - } - } else if (stmt.type === "FunctionDeclaration" && stmt.id) { - map.set(stmt.id.name, "function"); - } else if (stmt.type === "ClassDeclaration" && stmt.id) { - map.set(stmt.id.name, "class"); - } else if (stmt.type === "ForStatement") { - if ( - stmt.init && - stmt.init.type === "VariableDeclaration" && - stmt.init.kind === "var" - ) { - for (const decl of stmt.init.declarations) { - collectPatternNames(decl.id, "var", map); - } - } - } else if ( - stmt.type === "ForInStatement" || - stmt.type === "ForOfStatement" - ) { - if ( - stmt.left && - stmt.left.type === "VariableDeclaration" && - stmt.left.kind === "var" - ) { - for (const decl of stmt.left.declarations) { - collectPatternNames(decl.id, "var", map); - } - } - } - } - return Array.from(map.entries()).map(([name, kind]) => ({ name, kind })); -} - -function collectPatternBindingNames(pattern) { - const map = new Map(); - collectPatternNames(pattern, "binding", map); - return Array.from(map.keys()); -} - -function nextInternalBindingName() { - // We intentionally do not scan user-declared names here. Internal helpers use - // a per-thread salt plus a counter instead. A user could still collide by - // deliberately spelling the exact generated name, but the thread-id salt - // keeps accidental collisions negligible while avoiding more AST bookkeeping. - return `__codex_internal_commit_${internalBindingSalt}_${internalBindingCounter++}`; -} - -function buildMarkCommittedExpression(names, markCommittedFnName) { - const serializedNames = names.map((name) => JSON.stringify(name)).join(", "); - return `(${markCommittedFnName}(${serializedNames}), undefined)`; -} - -function tryReadBindingValue(module, bindingName) { - if (!module) { - return { ok: false, value: undefined }; - } - - try { - return { ok: true, value: module.namespace[bindingName] }; - } catch { - return { ok: false, value: undefined }; - } -} - -function instrumentVariableDeclarationSource( - code, - declaration, - markCommittedFnName, -) { - if (!declaration.declarations?.length) { - return code.slice(declaration.start, declaration.end); - } - - const prefix = code.slice(declaration.start, declaration.declarations[0].start); - const suffix = code.slice( - declaration.declarations[declaration.declarations.length - 1].end, - declaration.end, - ); - const parts = []; - - for (const decl of declaration.declarations) { - parts.push(code.slice(decl.start, decl.end)); - - const names = collectPatternBindingNames(decl.id); - if (names.length > 0) { - const helperName = nextInternalBindingName(); - parts.push( - `${helperName} = ${buildMarkCommittedExpression(names, markCommittedFnName)}`, - ); - } - } - - return `${prefix}${parts.join(", ")}${suffix}`; -} - -function instrumentLoopBody(code, body, names, guardName, markCommittedFnName) { - const marker = `if (${guardName}) { ${guardName} = false; ${markCommittedFnName}(${names - .map((name) => JSON.stringify(name)) - .join(", ")}); }`; - const bodyCode = code.slice(body.start, body.end); - - if (body.type === "BlockStatement") { - return `{ ${marker}${bodyCode.slice(1)}`; - } - - return `{ ${marker} ${bodyCode} }`; -} - -function applyReplacements(code, replacements) { - let instrumentedCode = code; - - for (const replacement of replacements.sort((a, b) => b.start - a.start)) { - instrumentedCode = - instrumentedCode.slice(0, replacement.start) + - replacement.text + - instrumentedCode.slice(replacement.end); - } - - return instrumentedCode; -} - -function collectHoistedVarDeclarationStarts(ast) { - const varDeclarationStarts = new Map(); - - const recordDeclarationStart = (map, name, start) => { - const existingStart = map.get(name); - if (existingStart === undefined || start < existingStart) { - map.set(name, start); - } - }; - - const recordVarDeclarationStarts = (declaration) => { - for (const name of collectPatternBindingNames(declaration.id)) { - recordDeclarationStart(varDeclarationStarts, name, declaration.start); - } - }; - - for (const stmt of ast.body ?? []) { - if (stmt.type === "VariableDeclaration" && stmt.kind === "var") { - for (const declaration of stmt.declarations ?? []) { - recordVarDeclarationStarts(declaration); - } - continue; - } - - if ( - stmt.type === "ForStatement" && - stmt.init?.type === "VariableDeclaration" && - stmt.init.kind === "var" - ) { - for (const declaration of stmt.init.declarations ?? []) { - recordVarDeclarationStarts(declaration); - } - continue; - } - - if ( - (stmt.type === "ForInStatement" || stmt.type === "ForOfStatement") && - stmt.left?.type === "VariableDeclaration" && - stmt.left.kind === "var" - ) { - for (const declaration of stmt.left.declarations ?? []) { - recordVarDeclarationStarts(declaration); - } - } - } - - return varDeclarationStarts; -} - -function collectFutureVarWriteReplacements( - code, - ast, - { - helperDeclarations = null, - markCommittedFnName = null, - } = {}, -) { - // Failed-cell hoisted tracking intentionally stays small here. We only mark - // direct top-level writes to future `var` bindings, plus top-level - // declaration-site markers handled later in `instrumentCurrentBindings`. - // We do not recurse through nested statement structure because that quickly - // requires real lexical-scope tracking for blocks, loop scopes, catch - // bindings, and similar shadowing cases. Supported write recovery is limited - // to direct top-level expression statements such as `x = 1`, `x += 1`, - // `x++`, and logical assignments. - const varDeclarationStarts = collectHoistedVarDeclarationStarts(ast); - if (varDeclarationStarts.size === 0) { - return []; - } - const replacements = []; - const replacementKeys = new Set(); - - if (!markCommittedFnName) { - throw new Error( - "collectFutureVarWriteReplacements expected a commit marker binding name", - ); - } - - const addReplacement = (start, end, text) => { - const key = `${start}:${end}`; - if (!replacementKeys.has(key)) { - replacementKeys.add(key); - replacements.push({ start, end, text }); - } - }; - - const getFutureVarName = (identifier) => { - if (!identifier || identifier.type !== "Identifier") { - return null; - } - - const declarationStart = varDeclarationStarts.get(identifier.name); - if ( - declarationStart === undefined || - identifier.start >= declarationStart - ) { - return null; - } - - return identifier.name; - }; - - const instrumentUpdateExpression = (node, identifier) => { - const bindingName = getFutureVarName(identifier); - if (!bindingName) { - return false; - } - - addReplacement( - node.start, - node.end, - `(${markCommittedFnName}(${JSON.stringify(bindingName)}), ${code.slice( - node.start, - node.end, - )})`, - ); - return true; - }; - - const instrumentAssignmentExpression = (node) => { - if (node.left.type !== "Identifier") { - return false; - } - - const bindingName = getFutureVarName(node.left); - if (!bindingName) { - return false; - } - - if ( - node.operator === "&&=" || - node.operator === "||=" || - node.operator === "??=" - ) { - if (!helperDeclarations) { - throw new Error( - "collectFutureVarWriteReplacements expected helperDeclarations for logical assignment rewriting", - ); - } - - const helperName = nextInternalBindingName(); - helperDeclarations.push(`let ${helperName};`); - const shortCircuitOperator = - node.operator === "&&=" - ? "&&" - : node.operator === "||=" - ? "||" - : "??"; - addReplacement( - node.start, - node.end, - `((${helperName} = ${node.left.name}), ${helperName} ${shortCircuitOperator} ((${node.left.name} = ${code.slice(node.right.start, node.right.end)}), ${buildMarkCommittedExpression([bindingName], markCommittedFnName)}, ${node.left.name}))`, - ); - return true; - } - - addReplacement( - node.start, - node.end, - `((${code.slice(node.start, node.end)}), ${buildMarkCommittedExpression([bindingName], markCommittedFnName)}, ${node.left.name})`, - ); - return true; - }; - - const unwrapParenthesizedExpression = (node) => { - let current = node; - while (current?.type === "ParenthesizedExpression") { - current = current.expression; - } - return current; - }; - - for (const statement of ast.body ?? []) { - if (statement.type !== "ExpressionStatement") { - continue; - } - - const expression = unwrapParenthesizedExpression(statement.expression); - if (!expression) { - continue; - } - - if ( - expression.type === "UpdateExpression" && - expression.argument.type === "Identifier" - ) { - instrumentUpdateExpression(expression, expression.argument); - continue; - } - - if (expression.type === "AssignmentExpression") { - instrumentAssignmentExpression(expression); - } - } - - return replacements; -} - -function instrumentCurrentBindings( - code, - ast, - currentBindings, - priorBindings, - markCommittedFnName, -) { - if (currentBindings.length === 0) { - return code; - } - - const replacements = []; - - for (const stmt of ast.body ?? []) { - if (stmt.type === "VariableDeclaration") { - replacements.push({ - start: stmt.start, - end: stmt.end, - text: instrumentVariableDeclarationSource( - code, - stmt, - markCommittedFnName, - ), - }); - continue; - } - - if (stmt.type === "FunctionDeclaration" && stmt.id) { - replacements.push({ - start: stmt.start, - end: stmt.end, - // Keep function source text stable for things like `foo.toString()`. - // Pre-declaration uses are tracked separately by instrumenting the - // top-level expressions that actually read the hoisted function value. - text: `${code.slice(stmt.start, stmt.end)}\n;${markCommittedFnName}(${JSON.stringify(stmt.id.name)});`, - }); - continue; - } - - if (stmt.type === "ClassDeclaration" && stmt.id) { - replacements.push({ - start: stmt.start, - end: stmt.end, - text: `${code.slice(stmt.start, stmt.end)}\n;${markCommittedFnName}(${JSON.stringify(stmt.id.name)});`, - }); - continue; - } - - if ( - stmt.type === "ForStatement" && - stmt.init && - stmt.init.type === "VariableDeclaration" && - stmt.init.kind === "var" - ) { - replacements.push({ - start: stmt.start, - end: stmt.end, - text: `${code.slice(stmt.start, stmt.init.start)}${instrumentVariableDeclarationSource( - code, - stmt.init, - markCommittedFnName, - )}${code.slice(stmt.init.end, stmt.end)}`, - }); - continue; - } - - if ( - (stmt.type === "ForInStatement" || stmt.type === "ForOfStatement") && - stmt.left && - stmt.left.type === "VariableDeclaration" && - stmt.left.kind === "var" - ) { - const names = stmt.left.declarations.flatMap((decl) => - collectPatternBindingNames(decl.id), - ); - if (names.length > 0) { - const guardName = nextInternalBindingName(); - replacements.push({ - start: stmt.start, - end: stmt.end, - // Mark top-level `for...in` / `for...of` vars on the first body - // execution instead of every iteration. This keeps hot loops cheap - // after the first pass while still preserving vars for the common - // case where the loop actually ran before a later throw. - // - // The tradeoff is that `for (var x of []) {}` in a failed cell will - // not carry `x` forward as `undefined`, because the body never runs - // and the one-time marker never fires. We accept that edge case: - // `var` is redeclarable, and the only lost state is an unassigned - // `undefined` from an empty top-level loop in a cell that later - // fails. - text: `let ${guardName} = true;\n${code.slice( - stmt.start, - stmt.body.start, - )}${instrumentLoopBody( - code, - stmt.body, - names, - guardName, - markCommittedFnName, - )}`, - }); - } - } - } - - return applyReplacements(code, replacements); -} - -async function buildModuleSource(code) { - const meriyah = await meriyahPromise; - const ast = meriyah.parseModule(code, { - next: true, - module: true, - ranges: true, - loc: false, - disableWebCompat: true, - }); - const currentBindings = collectBindings(ast); - const priorBindings = previousModule ? previousBindings : []; - const helperDeclarations = []; - const markCommittedFnName = nextInternalBindingName(); - const markPreludeCompletedFnName = nextInternalBindingName(); - helperDeclarations.push( - // `import.meta` is syntax-level and cannot be shadowed by user bindings - // like `const globalThis = ...`, so alias the marker helper through it - // once in the prelude and use that stable local binding everywhere. - // Then delete the raw import.meta hooks so user code cannot spoof - // committed bindings by calling them directly. - `const ${markCommittedFnName} = import.meta.__codexInternalMarkCommittedBindings;`, - `const ${markPreludeCompletedFnName} = import.meta.__codexInternalMarkPreludeCompleted;`, - "delete import.meta.__codexInternalMarkCommittedBindings;", - "delete import.meta.__codexInternalMarkPreludeCompleted;", - ); - const writeInstrumentedCode = applyReplacements( - code, - collectFutureVarWriteReplacements(code, ast, { - helperDeclarations, - markCommittedFnName, - }), - ); - const instrumentedAst = meriyah.parseModule(writeInstrumentedCode, { - next: true, - module: true, - ranges: true, - loc: false, - disableWebCompat: true, - }); - const instrumentedCode = instrumentCurrentBindings( - writeInstrumentedCode, - instrumentedAst, - currentBindings, - priorBindings, - markCommittedFnName, - ); - - let prelude = ""; - if (previousModule && priorBindings.length) { - // Recreate carried bindings before running user code in this new cell. - prelude += 'import * as __prev from "@prev";\n'; - prelude += priorBindings - .map((b) => { - const keyword = - b.kind === "var" ? "var" : b.kind === "const" ? "const" : "let"; - return `${keyword} ${b.name} = __prev.${b.name};`; - }) - .join("\n"); - prelude += "\n"; - } - if (helperDeclarations.length > 0) { - prelude += `${helperDeclarations.join("\n")}\n`; - } - prelude += `${markPreludeCompletedFnName}();\n`; - - const mergedBindings = new Map(); - for (const binding of priorBindings) { - mergedBindings.set(binding.name, binding.kind); - } - for (const binding of currentBindings) { - mergedBindings.set(binding.name, binding.kind); - } - // Export the merged binding set so the next cell can import it through @prev. - const exportNames = Array.from(mergedBindings.keys()); - const exportStmt = exportNames.length - ? `\nexport { ${exportNames.join(", ")} };` - : ""; - - const nextBindings = Array.from(mergedBindings, ([name, kind]) => ({ - name, - kind, - })); - return { - source: `${prelude}${instrumentedCode}${exportStmt}`, - currentBindings, - nextBindings, - priorBindings, - }; -} - -function canReadCommittedBinding(module, binding) { - if ( - !module || - binding.kind === "var" || - binding.kind === "function" - ) { - return false; - } - - return tryReadBindingValue(module, binding.name).ok; -} -// Failed cells keep prior bindings plus the current-cell bindings whose -// initialization definitely ran before the throw. That means: -// - lexical bindings (`const` / `let` / `class`) can fall back to namespace -// readability, which preserves names whose initialization already completed -// even when a later step in the same declarator throws -// - `var` / `function` bindings only persist when an explicit declaration-site -// or write-site marker fired, so unreached hoisted bindings do not become -// ghost bindings in later cells -function collectCommittedBindings( - module, - priorBindings, - currentBindings, - committedCurrentBindingNames, -) { - const mergedBindings = new Map(); - let committedCurrentBindingCount = 0; - - for (const binding of priorBindings) { - mergedBindings.set(binding.name, binding.kind); - } - - for (const binding of currentBindings) { - if ( - committedCurrentBindingNames.has(binding.name) || - canReadCommittedBinding(module, binding) - ) { - mergedBindings.set(binding.name, binding.kind); - committedCurrentBindingCount += 1; - } - } - - return { - bindings: Array.from(mergedBindings, ([name, kind]) => ({ name, kind })), - committedCurrentBindingCount, - }; -} - -function send(message) { - process.stdout.write(JSON.stringify(message)); - process.stdout.write("\n"); -} - -function formatErrorMessage(error) { - if (error && typeof error === "object" && "message" in error) { - return error.message ? String(error.message) : String(error); - } - return String(error); -} - -function sendFatalExecResultSync(kind, error) { - if (!activeExecId) { - return; - } - const payload = { - type: "exec_result", - id: activeExecId, - ok: false, - output: "", - error: `js_repl kernel ${kind}: ${formatErrorMessage(error)}; kernel reset. Catch or handle async errors (including Promise rejections and EventEmitter 'error' events) to avoid kernel termination.`, - }; - try { - fs.writeSync(process.stdout.fd, `${JSON.stringify(payload)}\n`); - } catch { - // Best effort only; the host will still surface stdout EOF diagnostics. - } -} - -function getCurrentExecState() { - const execState = execContextStorage.getStore(); - if (!execState || typeof execState.id !== "string" || !execState.id) { - throw new Error("js_repl exec context not found"); - } - return execState; -} - -function scheduleFatalExit(kind, error) { - if (fatalExitScheduled) { - process.exitCode = 1; - return; - } - fatalExitScheduled = true; - sendFatalExecResultSync(kind, error); - - try { - fs.writeSync( - process.stderr.fd, - `js_repl kernel ${kind}: ${formatErrorMessage(error)}\n`, - ); - } catch { - // ignore - } - - // The host will observe stdout EOF, reset kernel state, and restart on demand. - setImmediate(() => { - process.exit(1); - }); -} - -function formatLog(args) { - return args - .map((arg) => - typeof arg === "string" ? arg : inspect(arg, { depth: 4, colors: false }), - ) - .join(" "); -} - -function withCapturedConsole(ctx, fn) { - const logs = []; - const original = ctx.console ?? console; - const captured = { - ...original, - log: (...args) => { - logs.push(formatLog(args)); - }, - info: (...args) => { - logs.push(formatLog(args)); - }, - warn: (...args) => { - logs.push(formatLog(args)); - }, - error: (...args) => { - logs.push(formatLog(args)); - }, - debug: (...args) => { - logs.push(formatLog(args)); - }, - }; - ctx.console = captured; - return fn(logs).finally(() => { - ctx.console = original; - }); -} - -function isPlainObject(value) { - return Boolean(value) && typeof value === "object" && !Array.isArray(value); -} - -function toByteArray(value) { - if (value instanceof Uint8Array) { - return value; - } - if (value instanceof ArrayBuffer) { - return new Uint8Array(value); - } - if (ArrayBuffer.isView(value)) { - return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); - } - return null; -} - -function encodeByteImage(bytes, mimeType, detail) { - if (bytes.byteLength === 0) { - throw new Error("codex.emitImage expected non-empty bytes"); - } - if (typeof mimeType !== "string" || !mimeType) { - throw new Error("codex.emitImage expected a non-empty mimeType"); - } - assertEmitImageMimeType(mimeType); - const image_url = `data:${mimeType};base64,${Buffer.from(bytes).toString("base64")}`; - return { image_url, detail }; -} - -function parseImageDetail(detail) { - if (detail == null) { - return undefined; - } - if (typeof detail !== "string" || !detail) { - throw new Error("codex.emitImage expected detail to be a non-empty string"); - } - if (!["auto", "low", "high", "original"].includes(detail)) { - throw new Error( - 'codex.emitImage expected detail to be one of "auto", "low", "high", or "original"', - ); - } - return detail; -} - -function normalizeEmitImageUrl(value) { - if (typeof value !== "string" || !value) { - throw new Error("codex.emitImage expected a non-empty image_url"); - } - if (!/^data:/i.test(value)) { - throw new Error("codex.emitImage only accepts data URLs"); - } - const mimeType = parseDataUrlMimeType(value); - assertEmitImageMimeType(mimeType); - return value; -} - -const SUPPORTED_EMIT_IMAGE_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/gif", -]; - -function parseDataUrlMimeType(dataUrl) { - const commaIndex = dataUrl.indexOf(","); - if (commaIndex < 0) { - throw new Error("codex.emitImage expected a valid image data URL"); - } - const mediaType = dataUrl.slice("data:".length, commaIndex).split(";")[0]; - if (!mediaType) { - throw new Error("codex.emitImage expected image data URL to include a MIME type"); - } - return mediaType; -} - -function assertEmitImageMimeType(mimeType) { - const normalized = typeof mimeType === "string" ? mimeType.toLowerCase() : ""; - if (!SUPPORTED_EMIT_IMAGE_MIME_TYPES.includes(normalized)) { - const supportedTypes = `${SUPPORTED_EMIT_IMAGE_MIME_TYPES.slice(0, -1).join(", ")}, or ${ - SUPPORTED_EMIT_IMAGE_MIME_TYPES[SUPPORTED_EMIT_IMAGE_MIME_TYPES.length - 1] - }`; - throw new Error( - `codex.emitImage only supports ${supportedTypes}`, - ); - } -} - -function parseInputImageItem(value) { - if (!isPlainObject(value) || value.type !== "input_image") { - return null; - } - return { - images: [ - { - image_url: normalizeEmitImageUrl(value.image_url), - detail: parseImageDetail(value.detail), - }, - ], - textCount: 0, - }; -} - -function parseContentItems(items) { - if (!Array.isArray(items)) { - return null; - } - - const images = []; - let textCount = 0; - for (const item of items) { - if (!isPlainObject(item) || typeof item.type !== "string") { - throw new Error("codex.emitImage received malformed content items"); - } - if (item.type === "input_image") { - images.push({ - image_url: normalizeEmitImageUrl(item.image_url), - detail: parseImageDetail(item.detail), - }); - continue; - } - if (item.type === "input_text" || item.type === "output_text") { - textCount += 1; - continue; - } - throw new Error( - `codex.emitImage does not support content item type "${item.type}"`, - ); - } - - return { images, textCount }; -} - -function parseByteImageValue(value) { - if (!isPlainObject(value) || !("bytes" in value)) { - return null; - } - const bytes = toByteArray(value.bytes); - if (!bytes) { - throw new Error( - "codex.emitImage expected bytes to be Buffer, Uint8Array, ArrayBuffer, or ArrayBufferView", - ); - } - const detail = parseImageDetail(value.detail); - return encodeByteImage(bytes, value.mimeType, detail); -} - -function parseToolOutput(output) { - if (typeof output === "string") { - return { - images: [], - textCount: output.length > 0 ? 1 : 0, - }; - } - - const parsedItems = parseContentItems(output); - if (parsedItems) { - return parsedItems; - } - - throw new Error("codex.emitImage received an unsupported tool output shape"); -} - -function normalizeMcpImageData(data, mimeType) { - if (typeof data !== "string" || !data) { - throw new Error("codex.emitImage expected MCP image data"); - } - if (/^data:/i.test(data)) { - return data; - } - const normalizedMimeType = - typeof mimeType === "string" && mimeType ? mimeType : "application/octet-stream"; - return `data:${normalizedMimeType};base64,${data}`; -} - -function parseMcpImageDetail(meta) { - if (!isPlainObject(meta)) { - return undefined; - } - const detail = meta["codex/imageDetail"]; - if ( - typeof detail !== "string" || - !["auto", "low", "high", "original"].includes(detail) - ) { - return undefined; - } - return detail; -} - -function parseMcpToolResult(result) { - if (typeof result === "string") { - return { images: [], textCount: result.length > 0 ? 1 : 0 }; - } - - if (!isPlainObject(result)) { - throw new Error("codex.emitImage received an unsupported MCP result"); - } - - if ("Err" in result) { - const error = result.Err; - return { images: [], textCount: typeof error === "string" && error ? 1 : 0 }; - } - - if (!("Ok" in result)) { - throw new Error("codex.emitImage received an unsupported MCP result"); - } - - const ok = result.Ok; - if (!isPlainObject(ok) || !Array.isArray(ok.content)) { - throw new Error("codex.emitImage received malformed MCP content"); - } - - const images = []; - let textCount = 0; - for (const item of ok.content) { - if (!isPlainObject(item) || typeof item.type !== "string") { - throw new Error("codex.emitImage received malformed MCP content"); - } - if (item.type === "image") { - images.push({ - image_url: normalizeMcpImageData(item.data, item.mimeType ?? item.mime_type), - detail: parseMcpImageDetail(item._meta), - }); - continue; - } - if (item.type === "text") { - textCount += 1; - continue; - } - throw new Error( - `codex.emitImage does not support MCP content type "${item.type}"`, - ); - } - - return { images, textCount }; -} - -function requireSingleImage(parsed) { - if (parsed.textCount > 0) { - throw new Error("codex.emitImage does not accept mixed text and image content"); - } - if (parsed.images.length !== 1) { - throw new Error("codex.emitImage expected exactly one image"); - } - return parsed.images[0]; -} - -function normalizeEmitImageValue(value) { - if (typeof value === "string") { - return { image_url: normalizeEmitImageUrl(value) }; - } - - const directItem = parseInputImageItem(value); - if (directItem) { - return requireSingleImage(directItem); - } - - const byteImage = parseByteImageValue(value); - if (byteImage) { - return byteImage; - } - - const directItems = parseContentItems(value); - if (directItems) { - return requireSingleImage(directItems); - } - - if (!isPlainObject(value)) { - throw new Error("codex.emitImage received an unsupported value"); - } - - if (value.type === "message") { - return requireSingleImage(parseContentItems(value.content)); - } - - if ( - value.type === "function_call_output" || - value.type === "custom_tool_call_output" - ) { - return requireSingleImage(parseToolOutput(value.output)); - } - - if (value.type === "mcp_tool_call_output") { - return requireSingleImage(parseMcpToolResult(value.result)); - } - - if ("output" in value) { - return requireSingleImage(parseToolOutput(value.output)); - } - - if ("content" in value) { - return requireSingleImage(parseContentItems(value.content)); - } - - throw new Error("codex.emitImage received an unsupported value"); -} - -const codex = { - cwd, - homeDir, - tmpDir, - tool(toolName, args) { - let execState; - try { - execState = getCurrentExecState(); - } catch (error) { - return Promise.reject(error); - } - if (typeof toolName !== "string" || !toolName) { - return Promise.reject(new Error("codex.tool expects a tool name string")); - } - const id = `${execState.id}-tool-${toolCounter++}`; - let argumentsJson = "{}"; - if (typeof args === "string") { - argumentsJson = args; - } else if (typeof args !== "undefined") { - argumentsJson = JSON.stringify(args); - } - - return new Promise((resolve, reject) => { - const payload = { - type: "run_tool", - id, - exec_id: execState.id, - tool_name: toolName, - arguments: argumentsJson, - }; - send(payload); - pendingTool.set(id, (res) => { - if (!res.ok) { - reject(new Error(res.error || "tool failed")); - return; - } - resolve(res.response); - }); - }); - }, - emitImage(imageLike) { - let execState; - try { - execState = getCurrentExecState(); - } catch (error) { - return { - then(onFulfilled, onRejected) { - return Promise.reject(error).then(onFulfilled, onRejected); - }, - catch(onRejected) { - return Promise.reject(error).catch(onRejected); - }, - finally(onFinally) { - return Promise.reject(error).finally(onFinally); - }, - }; - } - const operation = (async () => { - const normalized = normalizeEmitImageValue(await imageLike); - const id = `${execState.id}-emit-image-${emitImageCounter++}`; - const payload = { - type: "emit_image", - id, - exec_id: execState.id, - image_url: normalized.image_url, - detail: normalized.detail ?? null, - }; - send(payload); - return new Promise((resolve, reject) => { - pendingEmitImage.set(id, (res) => { - if (!res.ok) { - reject(new Error(res.error || "emitImage failed")); - return; - } - resolve(); - }); - }); - })(); - - const observation = { observed: false }; - const trackedOperation = operation.then( - () => ({ ok: true, error: null, observation }), - (error) => ({ ok: false, error, observation }), - ); - execState.pendingBackgroundTasks.add(trackedOperation); - return { - then(onFulfilled, onRejected) { - observation.observed = true; - return operation.then(onFulfilled, onRejected); - }, - catch(onRejected) { - observation.observed = true; - return operation.catch(onRejected); - }, - finally(onFinally) { - observation.observed = true; - return operation.finally(onFinally); - }, - }; - }, -}; - -async function handleExec(message) { - clearLocalFileModuleCaches(); - activeExecId = message.id; - const execState = { - id: message.id, - pendingBackgroundTasks: new Set(), - }; - - let module = null; - /** @type {Binding[]} */ - let currentBindings = []; - /** @type {Binding[]} */ - let nextBindings = []; - /** @type {Binding[]} */ - let priorBindings = previousBindings; - let moduleLinked = false; - let preludeCompleted = false; - const committedCurrentBindingNames = new Set(); - const markCommittedBindings = (...names) => { - for (const name of names) { - committedCurrentBindingNames.add(name); - } - }; - const markPreludeCompleted = () => { - preludeCompleted = true; - }; - - try { - const code = typeof message.code === "string" ? message.code : ""; - const builtSource = await buildModuleSource(code); - const source = builtSource.source; - currentBindings = builtSource.currentBindings; - nextBindings = builtSource.nextBindings; - priorBindings = builtSource.priorBindings; - let output = ""; - - context.codex = codex; - context.tmpDir = tmpDir; - - await execContextStorage.run(execState, async () => { - await withCapturedConsole(context, async (logs) => { - const cellIdentifier = path.join( - cwd, - `.codex_js_repl_cell_${cellCounter++}.mjs`, - ); - module = new SourceTextModule(source, { - context, - identifier: cellIdentifier, - initializeImportMeta(meta, mod) { - setImportMeta(meta, mod, true); - meta.__codexInternalMarkCommittedBindings = markCommittedBindings; - meta.__codexInternalMarkPreludeCompleted = markPreludeCompleted; - }, - importModuleDynamically(specifier, referrer) { - return importResolved(resolveSpecifier(specifier, referrer?.identifier)); - }, - }); - - await module.link(async (specifier) => { - if (specifier === "@prev" && previousModule) { - const exportNames = previousBindings.map((b) => b.name); - // Build a synthetic module snapshot of the prior cell's exports. - // This is the bridge that carries values from cell N to cell N+1. - const synthetic = new SyntheticModule( - exportNames, - function initSynthetic() { - for (const binding of previousBindings) { - this.setExport( - binding.name, - previousModule.namespace[binding.name], - ); - } - }, - { context }, - ); - return synthetic; - } - throw new Error( - `Top-level static import "${specifier}" is not supported in js_repl. Use await import("${specifier}") instead.`, - ); - }); - moduleLinked = true; - - await module.evaluate(); - if (execState.pendingBackgroundTasks.size > 0) { - const backgroundResults = await Promise.all([ - ...execState.pendingBackgroundTasks, - ]); - const firstUnhandledBackgroundError = backgroundResults.find( - (result) => !result.ok && !result.observation.observed, - ); - if (firstUnhandledBackgroundError) { - throw firstUnhandledBackgroundError.error; - } - } - output = logs.join("\n"); - }); - }); - - previousModule = module; - previousBindings = nextBindings; - - send({ - type: "exec_result", - id: message.id, - ok: true, - output, - error: null, - }); - } catch (error) { - const { bindings: committedBindings, committedCurrentBindingCount } = - collectCommittedBindings( - moduleLinked ? module : null, - priorBindings, - currentBindings, - committedCurrentBindingNames, - ); - // Preserve the last successfully linked module across link-time failures. - // A module whose link step failed cannot safely back @prev because reading - // its namespace throws before evaluation ever begins. Likewise, if a - // linked module failed before its prelude recreated carried bindings, keep - // the old module so @prev still points at the last cell whose prelude and - // body actually established the carried values. Once the prelude has run, - // promote the failed module even if it only updated existing bindings. - if ( - module && - moduleLinked && - (committedCurrentBindingCount > 0 || - (preludeCompleted && priorBindings.length > 0)) - ) { - previousModule = module; - previousBindings = committedBindings; - } - send({ - type: "exec_result", - id: message.id, - ok: false, - output: "", - error: error && error.message ? error.message : String(error), - }); - } finally { - if (activeExecId === message.id) { - activeExecId = null; - } - } -} - -function handleToolResult(message) { - const resolver = pendingTool.get(message.id); - if (resolver) { - pendingTool.delete(message.id); - resolver(message); - } -} - -function handleEmitImageResult(message) { - const resolver = pendingEmitImage.get(message.id); - if (resolver) { - pendingEmitImage.delete(message.id); - resolver(message); - } -} - -let queue = Promise.resolve(); -let pendingInputSegments = []; - -process.on("uncaughtException", (error) => { - scheduleFatalExit("uncaught exception", error); -}); - -process.on("unhandledRejection", (reason) => { - scheduleFatalExit("unhandled rejection", reason); -}); - -function handleInputLine(line) { - if (!line.trim()) { - return; - } - - let message; - try { - message = JSON.parse(line); - } catch { - return; - } - - if (message.type === "exec") { - queue = queue.then(() => handleExec(message)); - return; - } - if (message.type === "run_tool_result") { - handleToolResult(message); - return; - } - if (message.type === "emit_image_result") { - handleEmitImageResult(message); - } -} - -function takePendingInputFrame() { - if (pendingInputSegments.length === 0) { - return null; - } - - // Keep raw stdin chunks queued until a full JSONL frame is ready so we only - // assemble the frame bytes once. - const frame = - pendingInputSegments.length === 1 - ? pendingInputSegments[0] - : Buffer.concat(pendingInputSegments); - pendingInputSegments = []; - return frame; -} - -function handleInputFrame(frame) { - if (!frame) { - return; - } - - if (frame[frame.length - 1] === 0x0d) { - frame = frame.subarray(0, frame.length - 1); - } - handleInputLine(frame.toString("utf8")); -} - -process.stdin.on("data", (chunk) => { - const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - let segmentStart = 0; - let frameEnd = input.indexOf(0x0a); - while (frameEnd !== -1) { - pendingInputSegments.push(input.subarray(segmentStart, frameEnd)); - handleInputFrame(takePendingInputFrame()); - segmentStart = frameEnd + 1; - frameEnd = input.indexOf(0x0a, segmentStart); - } - if (segmentStart < input.length) { - pendingInputSegments.push(input.subarray(segmentStart)); - } -}); - -process.stdin.on("end", () => { - handleInputFrame(takePendingInputFrame()); -}); diff --git a/codex-rs/core/src/tools/js_repl/meriyah.umd.min.js b/codex-rs/core/src/tools/js_repl/meriyah.umd.min.js deleted file mode 100644 index e853b9b29ee0..000000000000 --- a/codex-rs/core/src/tools/js_repl/meriyah.umd.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Meriyah v7.0.0 - * Source: npm package meriyah@7.0.0 (dist/meriyah.umd.min.js) - * License: ISC (see third_party/meriyah/LICENSE) - */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).meriyah={})}(this,(function(e){"use strict";const t=((e,t)=>{const r=new Uint32Array(69632);let n=0,o=0;for(;n<2597;){const a=e[n++];if(a<0)o-=a;else{let i=e[n++];2&a&&(i=t[i]),1&a?r.fill(i,o,o+=e[n++]):r[o++]=i}}return r})([-1,2,26,2,27,2,5,-1,0,77595648,3,44,2,3,0,14,2,61,2,62,3,0,3,0,3168796671,0,4294956992,2,1,2,0,2,41,3,0,4,0,4294966523,3,0,4,2,16,2,63,2,0,0,4294836735,0,3221225471,0,4294901942,2,64,0,134152192,3,0,2,0,4294951935,3,0,2,0,2683305983,0,2684354047,2,17,2,0,0,4294961151,3,0,2,2,19,2,0,0,608174079,2,0,2,58,2,7,2,6,0,4286643967,3,0,2,2,1,3,0,3,0,4294901711,2,40,0,4089839103,0,2961209759,0,1342439375,0,4294543342,0,3547201023,0,1577204103,0,4194240,0,4294688750,2,2,0,80831,0,4261478351,0,4294549486,2,2,0,2967484831,0,196559,0,3594373100,0,3288319768,0,8469959,0,65472,2,3,0,4093640191,0,929054175,0,65487,0,4294828015,0,4092591615,0,1885355487,0,982991,2,3,2,0,0,2163244511,0,4227923919,0,4236247022,2,69,0,4284449919,0,851904,2,4,2,12,0,67076095,-1,2,70,0,1073741743,0,4093607775,-1,0,50331649,0,3265266687,2,33,0,4294844415,0,4278190047,2,20,2,137,-1,3,0,2,2,23,2,0,2,9,2,0,2,15,2,22,3,0,10,2,72,2,0,2,73,2,74,2,75,2,0,2,76,2,0,2,11,0,261632,2,25,3,0,2,2,13,2,4,3,0,18,2,77,2,5,3,0,2,2,78,0,2151677951,2,29,2,10,0,909311,3,0,2,0,814743551,2,48,0,67090432,3,0,2,2,42,2,0,2,6,2,0,2,30,2,8,0,268374015,2,108,2,51,2,0,2,79,0,134153215,-1,2,7,2,0,2,8,0,2684354559,0,67044351,0,3221160064,2,9,2,18,3,0,2,2,53,0,1046528,3,0,3,2,10,2,0,2,127,0,4294960127,2,9,2,6,2,11,0,4294377472,2,12,3,0,16,2,13,2,0,2,80,2,9,2,0,2,81,2,82,2,83,0,12288,2,54,0,1048577,2,84,2,14,-1,2,14,0,131042,2,85,2,86,2,87,2,0,2,34,-83,3,0,7,0,1046559,2,0,2,15,2,0,0,2147516671,2,21,3,88,2,2,0,-16,2,89,0,524222462,2,4,2,0,0,4269801471,2,4,3,0,2,2,28,2,16,3,0,2,2,49,2,0,-1,2,17,-16,3,0,206,-2,3,0,692,2,71,-1,2,17,2,9,3,0,8,2,91,2,18,2,0,0,3220242431,3,0,3,2,19,2,92,2,93,3,0,2,2,94,2,0,2,20,2,95,2,0,0,4351,2,0,2,10,3,0,2,0,67043391,0,3909091327,2,0,2,24,2,10,2,20,3,0,2,0,67076097,2,8,2,0,2,21,0,67059711,0,4236247039,3,0,2,0,939524103,0,8191999,2,99,2,100,2,22,2,23,3,0,3,0,67057663,3,0,349,2,101,2,102,2,7,-264,3,0,11,2,24,3,0,2,2,32,-1,0,3774349439,2,103,2,104,3,0,2,2,19,2,105,3,0,10,2,9,2,17,2,0,2,46,2,0,2,31,2,106,2,25,0,1638399,0,57344,2,107,3,0,3,2,20,2,26,2,27,2,5,2,28,2,0,2,8,2,109,-1,2,110,2,111,2,112,-1,3,0,3,2,12,-2,2,0,2,29,-3,0,536870912,-4,2,20,2,0,2,36,0,1,2,0,2,65,2,6,2,12,2,9,2,0,2,113,-1,3,0,4,2,9,2,23,2,114,2,7,2,0,2,115,2,0,2,116,2,117,2,118,2,0,2,10,3,0,9,2,21,2,30,2,31,2,119,2,120,-2,2,121,2,122,2,30,2,21,2,8,-2,2,123,2,30,3,32,2,-1,2,0,2,39,-2,0,4277137519,0,2269118463,-1,3,20,2,-1,2,33,2,38,2,0,3,30,2,2,35,2,19,-3,3,0,2,2,34,-1,2,0,2,35,2,0,2,35,2,0,2,47,2,0,0,4294950463,2,37,-7,2,0,0,203775,2,125,0,4227858432,2,20,2,43,2,36,2,17,2,37,2,17,2,124,2,21,3,0,2,2,38,0,2151677888,2,0,2,12,0,4294901764,2,145,2,0,2,56,2,55,0,5242879,3,0,2,0,402644511,-1,2,128,2,39,0,3,-1,2,129,2,130,2,0,0,67045375,2,40,0,4226678271,0,3766565279,0,2039759,2,132,2,41,0,1046437,0,6,3,0,2,0,3288270847,0,3,3,0,2,0,67043519,-5,2,0,0,4282384383,0,1056964609,-1,3,0,2,0,67043345,-1,2,0,2,42,2,23,2,50,2,11,2,59,2,38,-5,2,0,2,12,-3,3,0,2,0,2147484671,2,133,0,4190109695,2,52,-2,2,134,0,4244635647,0,27,2,0,2,8,2,43,2,0,2,66,2,17,2,0,2,42,-3,2,31,-2,2,0,2,45,2,57,2,44,2,45,2,135,2,46,0,8388351,-2,2,136,0,3028287487,2,47,2,138,0,33259519,2,23,2,7,2,48,-7,2,21,0,4294836223,0,3355443199,0,134152199,-2,2,67,-2,3,0,28,2,32,-3,3,0,3,2,49,3,0,6,2,50,-81,2,17,3,0,2,2,36,3,0,33,2,25,2,30,3,0,124,2,12,3,0,18,2,38,-213,2,0,2,32,-54,3,0,17,2,42,2,8,2,23,2,0,2,8,2,23,2,51,2,0,2,21,2,52,2,139,2,25,-13,2,0,2,53,-6,3,0,2,-1,2,140,2,10,-1,3,0,2,0,4294936575,2,0,0,4294934783,-2,0,8323099,3,0,230,2,30,2,54,2,8,-3,3,0,3,2,35,-271,2,141,3,0,9,2,142,2,143,2,55,3,0,11,2,7,-72,3,0,3,2,144,0,1677656575,-130,2,26,-16,2,0,2,24,2,38,-16,0,4161266656,0,4071,0,15360,-4,0,28,-13,3,0,2,2,56,2,0,2,146,2,147,2,60,2,0,2,148,2,149,2,150,3,0,10,2,151,2,152,2,22,3,56,2,3,153,2,3,57,2,0,4294954999,2,0,-16,2,0,2,90,2,0,0,2105343,0,4160749584,0,65534,-34,2,8,2,155,-6,0,4194303871,0,4294903771,2,0,2,58,2,98,-3,2,0,0,1073684479,0,17407,-9,2,17,2,49,2,0,2,32,-14,2,17,2,32,-6,2,17,2,12,-6,2,8,0,3225419775,-7,2,156,3,0,6,0,8323103,-1,3,0,2,2,59,-37,2,60,2,157,2,158,2,159,2,160,2,161,-105,2,26,-32,3,0,1335,-1,3,0,136,2,9,3,0,180,2,24,3,0,233,2,162,3,0,18,2,9,-77,3,0,16,2,9,-47,3,0,154,2,6,3,0,264,2,32,-22116,3,0,7,2,25,-6130,3,5,2,-1,0,69207040,3,44,2,3,0,14,2,61,2,62,-3,0,3168731136,0,4294956864,2,1,2,0,2,41,3,0,4,0,4294966275,3,0,4,2,16,2,63,2,0,2,34,-1,2,17,2,64,-1,2,0,0,2047,0,4294885376,3,0,2,0,3145727,0,2617294944,0,4294770688,2,25,2,65,3,0,2,0,131135,2,96,0,70256639,0,71303167,0,272,2,42,2,6,0,65279,2,0,2,48,-1,2,97,2,66,0,4278255616,0,4294836227,0,4294549473,0,600178175,0,2952806400,0,268632067,0,4294543328,0,57540095,0,1577058304,0,1835008,0,4294688736,2,68,2,67,0,33554435,2,131,2,68,0,2952790016,0,131075,0,3594373096,0,67094296,2,67,-1,0,4294828e3,0,603979263,0,922746880,0,3,0,4294828001,0,602930687,0,1879048192,0,393219,0,4294828016,0,671088639,0,2154840064,0,4227858435,0,4236247008,2,69,2,38,-1,2,4,0,917503,2,38,-1,2,70,0,537788335,0,4026531935,-1,0,1,-1,2,33,2,71,0,7936,-3,2,0,0,2147485695,0,1010761728,0,4292984930,0,16387,2,0,2,15,2,22,3,0,10,2,72,2,0,2,73,2,74,2,75,2,0,2,76,2,0,2,12,-1,2,25,3,0,2,2,13,2,4,3,0,18,2,77,2,5,3,0,2,2,78,0,2147745791,3,19,2,0,122879,2,0,2,10,0,276824064,-2,3,0,2,2,42,2,0,0,4294903295,2,0,2,30,2,8,-1,2,17,2,51,2,0,2,79,2,48,-1,2,21,2,0,2,29,-2,0,128,-2,2,28,2,10,0,8160,-1,2,126,0,4227907585,2,0,2,37,2,0,2,50,0,4227915776,2,9,2,6,2,11,-1,0,74440192,3,0,6,-2,3,0,8,2,13,2,0,2,80,2,9,2,0,2,81,2,82,2,83,-3,2,84,2,14,-3,2,85,2,86,2,87,2,0,2,34,-83,3,0,7,0,817183,2,0,2,15,2,0,0,33023,2,21,3,88,2,-17,2,89,0,524157950,2,4,2,0,2,90,2,4,2,0,2,22,2,28,2,16,3,0,2,2,49,2,0,-1,2,17,-16,3,0,206,-2,3,0,692,2,71,-1,2,17,2,9,3,0,8,2,91,0,3072,2,0,0,2147516415,2,9,3,0,2,2,25,2,92,2,93,3,0,2,2,94,2,0,2,20,2,95,0,4294965179,0,7,2,0,2,10,2,93,2,10,-1,0,1761345536,2,96,0,4294901823,2,38,2,20,2,97,2,35,2,98,0,2080440287,2,0,2,34,2,154,0,3296722943,2,0,0,1046675455,0,939524101,0,1837055,2,99,2,100,2,22,2,23,3,0,3,0,7,3,0,349,2,101,2,102,2,7,-264,3,0,11,2,24,3,0,2,2,32,-1,0,2700607615,2,103,2,104,3,0,2,2,19,2,105,3,0,10,2,9,2,17,2,0,2,46,2,0,2,31,2,106,-3,2,107,3,0,3,2,20,-1,3,5,2,2,108,2,0,2,8,2,109,-1,2,110,2,111,2,112,-1,3,0,3,2,12,-2,2,0,2,29,-8,2,20,2,0,2,36,-1,2,0,2,65,2,6,2,30,2,9,2,0,2,113,-1,3,0,4,2,9,2,17,2,114,2,7,2,0,2,115,2,0,2,116,2,117,2,118,2,0,2,10,3,0,9,2,21,2,30,2,31,2,119,2,120,-2,2,121,2,122,2,30,2,21,2,8,-2,2,123,2,30,3,32,2,-1,2,0,2,39,-2,0,4277075969,2,30,-1,3,20,2,-1,2,33,2,124,2,0,3,30,2,2,35,2,19,-3,3,0,2,2,34,-1,2,0,2,35,2,0,2,35,2,0,2,50,2,96,0,4294934591,2,37,-7,2,0,0,197631,2,125,-1,2,20,2,43,2,37,2,17,0,3,2,17,2,124,2,21,2,126,2,127,-1,0,2490368,2,126,2,25,2,17,2,34,2,126,2,38,0,4294901904,0,4718591,2,126,2,35,0,335544350,-1,2,128,0,2147487743,0,1,-1,2,129,2,130,2,8,-1,2,131,2,68,0,3758161920,0,3,2,132,0,12582911,0,655360,-1,2,0,2,29,0,2147485568,0,3,2,0,2,25,0,176,-5,2,0,2,49,0,251658240,-1,2,0,2,25,0,16,-1,2,0,0,16779263,-2,2,12,-1,2,38,-5,2,0,2,18,-3,3,0,2,2,54,2,133,0,2147549183,0,2,-2,2,134,2,36,0,10,0,4294965249,0,67633151,0,4026597376,2,0,0,536871935,2,17,2,0,2,42,-6,2,0,0,1,2,57,2,49,0,1,2,135,2,25,-3,2,136,2,36,2,137,2,138,0,16778239,2,17,2,7,-8,2,35,0,4294836212,2,10,-3,2,67,-2,3,0,28,2,32,-3,3,0,3,2,49,3,0,6,2,50,-81,2,17,3,0,2,2,36,3,0,33,2,25,0,126,3,0,124,2,12,3,0,18,2,38,-213,2,9,-55,3,0,17,2,42,2,8,2,17,2,0,2,8,2,17,2,58,2,0,2,25,2,50,2,139,2,25,-13,2,0,2,71,-6,3,0,2,-1,2,140,2,10,-1,3,0,2,0,67583,-1,2,105,-2,0,8126475,3,0,230,2,30,2,54,2,8,-3,3,0,3,2,35,-271,2,141,3,0,9,2,142,2,143,2,55,3,0,11,2,7,-72,3,0,3,2,144,2,145,-187,3,0,2,2,56,2,0,2,146,2,147,2,60,2,0,2,148,2,149,2,150,3,0,10,2,151,2,152,2,22,3,56,2,3,153,2,3,57,2,2,154,-57,2,8,2,155,-7,2,17,2,0,2,58,-4,2,0,0,1065361407,0,16384,-9,2,17,2,58,2,0,2,18,-14,2,17,2,18,-6,2,17,0,81919,-6,2,8,0,3223273399,-7,2,156,3,0,6,2,124,-1,3,0,2,0,2063,-37,2,60,2,157,2,158,2,159,2,160,2,161,-138,3,0,1335,-1,3,0,136,2,9,3,0,180,2,24,3,0,233,2,162,3,0,18,2,9,-77,3,0,16,2,9,-47,3,0,154,2,6,3,0,264,2,32,-28252],[4294967295,4294967291,4092460543,4294828031,4294967294,134217726,4294903807,268435455,2147483647,1073741823,1048575,3892314111,134217727,1061158911,536805376,4294910143,4294901759,4294901760,4095,262143,536870911,8388607,4160749567,4294902783,4294918143,65535,67043328,2281701374,4294967264,2097151,4194303,255,67108863,4294967039,511,524287,131071,63,127,3238002687,4294549487,4290772991,33554431,4294901888,4286578687,67043329,4294770687,67043583,1023,32767,15,2047999,67043343,67051519,2147483648,4294902e3,4292870143,4294966783,16383,67047423,4294967279,262083,20511,41943039,493567,4294959104,603979775,65536,602799615,805044223,4294965206,8191,1031749119,4294917631,2134769663,4286578493,4282253311,4294942719,33540095,4294905855,2868854591,1608515583,265232348,534519807,2147614720,1060109444,4093640016,17376,2139062143,224,4169138175,4294909951,4286578688,4294967292,4294965759,4294836224,4294966272,4294967280,32768,8289918,4294934399,4294901775,4294965375,1602223615,4294967259,4294443008,268369920,4292804608,4294967232,486341884,4294963199,3087007615,1073692671,4128527,4279238655,4294902015,4160684047,4290246655,469499899,4294967231,134086655,4294966591,2445279231,3670015,31,252,4294967288,16777215,4294705151,3221208447,4294902271,4294549472,4294921215,4285526655,4294966527,4294705152,4294966143,64,4294966719,3774873592,4194303999,1877934080,262151,2555904,536807423,67043839,3758096383,3959414372,3755993023,2080374783,4294835295,4294967103,4160749565,4294934527,4087,2016,2147446655,184024726,2862017156,1593309078,268434431,268434414,4294901761]),r=e=>!!(1&t[34816+(e>>>5)]>>>e),n=[0,0,0,0,0,0,0,0,0,0,1032,0,0,2056,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8192,0,3,0,0,8192,0,0,0,256,0,33024,0,0,242,242,114,114,114,114,114,114,594,594,0,0,16384,0,0,0,0,67,67,67,67,67,67,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,1,0,0,4099,0,71,71,71,71,71,71,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,16384,0,0,0,0],o=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0];function i(e){return e<=127?o[e]>0:r(e)}function s(e){return e<=127?a[e]>0:(e=>!!(1&t[0+(e>>>5)]>>>e))(e)||8204===e||8205===e}function c(e){return e.column++,e.currentChar=e.source.charCodeAt(++e.index)}function l(e){const t=e.currentChar;if(55296!=(64512&t))return 0;const r=e.source.charCodeAt(e.index+1);return 56320!=(64512&r)?0:65536+((1023&t)<<10)+(1023&r)}function u(e,t){e.currentChar=e.source.charCodeAt(++e.index),e.flags|=1,4&t||(e.column=0,e.line++)}function p(e){e.flags|=1,e.currentChar=e.source.charCodeAt(++e.index),e.column=0,e.line++}function d(e){return e<65?e-48:e-65+10&15}function g(e){switch(e){case 134283266:return"NumericLiteral";case 134283267:return"StringLiteral";case 86021:case 86022:return"BooleanLiteral";case 86023:return"NullLiteral";case 65540:return"RegularExpression";case 67174408:case 67174409:case 131:return"TemplateLiteral";default:return 143360&~e?4096&~e?"Punctuator":"Keyword":"Identifier"}}const f=["SingleLine","MultiLine","HTMLOpen","HTMLClose","HashbangComment"];function k(e,t,r,n,o,a){return 2&n&&e.report(0),h(e,t,r,o,a)}function h(e,t,r,o,a){const{index:i}=e;for(e.tokenIndex=e.index,e.tokenLine=e.line,e.tokenColumn=e.column;e.index'",49:"The left-hand side of the arrow can only be destructed through assignment",50:"The binding declaration is not destructible",51:"Async arrow can not be followed by new expression",52:"Classes may not have a static property named 'prototype'",53:"Class constructor may not be a %0",54:"Duplicate constructor method in class",55:"Invalid increment/decrement operand",56:"Invalid use of `new` keyword on an increment/decrement expression",57:"`=>` is an invalid assignment target",58:"Rest element may not have a trailing comma",59:"Missing initializer in %0 declaration",60:"'for-%0' loop head declarations can not have an initializer",61:"Invalid left-hand side in for-%0 loop: Must have a single binding",62:"Invalid shorthand property initializer",63:"Property name __proto__ appears more than once in object literal",64:"Let is disallowed as a lexically bound name",65:"Invalid use of '%0' inside new expression",66:"Illegal 'use strict' directive in function with non-simple parameter list",67:'Identifier "let" disallowed as left-hand side expression in strict mode',68:"Illegal continue statement",69:"Illegal break statement",70:"Cannot have `let[...]` as a var name in strict mode",71:"Invalid destructuring assignment target",72:"Rest parameter may not have a default initializer",73:"The rest argument must the be last parameter",74:"Invalid rest argument",76:"In strict mode code, functions can only be declared at top level or inside a block",77:"In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement",78:"Without web compatibility enabled functions can not be declared at top level, inside a block, or as the body of an if statement",79:"Class declaration can't appear in single-statement context",80:"Invalid left-hand side in for-%0",81:"Invalid assignment in for-%0",82:"for await (... of ...) is only valid in async functions and async generators",83:"The first token after the template expression should be a continuation of the template",85:"`let` declaration not allowed here and `let` cannot be a regular var name in strict mode",84:"`let \n [` is a restricted production at the start of a statement",86:"Catch clause requires exactly one parameter, not more (and no trailing comma)",87:"Catch clause parameter does not support default values",88:"Missing catch or finally after try",89:"More than one default clause in switch statement",90:"Illegal newline after throw",91:"Strict mode code may not include a with statement",92:"Illegal return statement",93:"The left hand side of the for-header binding declaration is not destructible",94:"new.target only allowed within functions or static blocks",96:"'#' not followed by identifier",102:"Invalid keyword",101:"Can not use 'let' as a class name",100:"'A lexical declaration can't define a 'let' binding",99:"Can not use `let` as variable name in strict mode",97:"'%0' may not be used as an identifier in this context",98:"Await is only valid in async functions",103:"The %0 keyword can only be used with the module goal",104:"Unicode codepoint must not be greater than 0x10FFFF",105:"%0 source must be string",106:"Only a identifier or string can be used to indicate alias",107:"Only '*' or '{...}' can be imported after default",108:"Trailing decorator may be followed by method",109:"Decorators can't be used with a constructor",110:"Can not use `await` as identifier in module or async func",111:"Can not use `await` as identifier in module",112:"HTML comments are only allowed with web compatibility (Annex B)",113:"The identifier 'let' must not be in expression position in strict mode",114:"Cannot assign to `eval` and `arguments` in strict mode",115:"The left-hand side of a for-of loop may not start with 'let'",116:"Block body arrows can not be immediately invoked without a group",117:"Block body arrows can not be immediately accessed without a group",118:"Unexpected strict mode reserved word",119:"Unexpected eval or arguments in strict mode",120:"Decorators must not be followed by a semicolon",121:"Calling delete on expression not allowed in strict mode",122:"Pattern can not have a tail",124:"Can not have a `yield` expression on the left side of a ternary",125:"An arrow function can not have a postfix update operator",126:"Invalid object literal key character after generator star",127:"Private fields can not be deleted",129:"Classes may not have a field called constructor",128:"Classes may not have a private element named constructor",130:"A class field initializer or static block may not contain arguments",131:"Generators can only be declared at the top level or inside a block",132:"Async methods are a restricted production and cannot have a newline following it",133:"Unexpected character after object literal property name",135:"Invalid key token",136:"Label '%0' has already been declared",137:"continue statement must be nested within an iteration statement",138:"Undefined label '%0'",139:"Trailing comma is disallowed inside import(...) arguments",140:"Invalid binding in JSON import",141:"import() requires exactly one argument",142:"Cannot use new with import(...)",143:"... is not allowed in import()",144:"Expected '=>'",145:"Duplicate binding '%0'",146:"Duplicate private identifier #%0",147:"Cannot export a duplicate name '%0'",150:"Duplicate %0 for-binding",148:"Exported binding '%0' needs to refer to a top-level declared variable",149:"Unexpected private field",153:"Numeric separators are not allowed at the end of numeric literals",152:"Only one underscore is allowed as numeric separator",154:"JSX value should be either an expression or a quoted JSX text",155:"Expected corresponding JSX closing tag for %0",156:"Adjacent JSX elements must be wrapped in an enclosing tag",157:"JSX attributes must only be assigned a non-empty 'expression'",158:"'%0' has already been declared",159:"'%0' shadowed a catch clause binding",160:"Dot property must be an identifier",161:"Encountered invalid input after spread/rest argument",162:"Catch without try",163:"Finally without try",164:"Expected corresponding closing tag for JSX fragment",165:"Coalescing and logical operators used together in the same expression must be disambiguated with parentheses",166:"Invalid tagged template on optional chain",167:"Invalid optional chain from super property",168:"Invalid optional chain from new expression",169:'Cannot use "import.meta" outside a module',170:"Leading decorators must be attached to a class declaration",171:"An export name cannot include a lone surrogate",172:"A string literal cannot be used as an exported binding without `from`",173:"Private fields can't be accessed on super",174:"The only valid meta property for import is 'import.meta'",175:"'import.meta' must not contain escaped characters",176:'cannot use "await" as identifier inside an async function',177:'cannot use "await" in static blocks'};class T extends SyntaxError{start;end;range;loc;description;constructor(e,t,r,...n){const o=b[r].replace(/%(\d+)/g,((e,t)=>n[t]));super("["+e.line+":"+e.column+"-"+t.line+":"+t.column+"]: "+o),this.start=e.index,this.end=t.index,this.range=[e.index,t.index],this.loc={start:{line:e.line,column:e.column},end:{line:t.line,column:t.column}},this.description=o}}function y(e,t){return Object.hasOwn(e,t)?e[t]:void 0}const x=["end of source","identifier","number","string","regular expression","false","true","null","template continuation","template tail","=>","(","{",".","...","}",")",";",",","[","]",":","?","'",'"',"++","--","=","<<=",">>=",">>>=","**=","+=","-=","*=","/=","%=","^=","|=","&=","||=","&&=","??=","typeof","delete","void","!","~","+","-","in","instanceof","*","%","/","**","&&","||","===","!==","==","!=","<=",">=","<",">","<<",">>",">>>","&","|","^","var","let","const","break","case","catch","class","continue","debugger","default","do","else","export","extends","finally","for","function","if","import","new","return","super","switch","this","throw","try","while","with","implements","interface","package","private","protected","public","static","yield","as","async","await","constructor","get","set","accessor","from","of","enum","eval","arguments","escaped keyword","escaped future reserved keyword","reserved if strict","#","BigIntLiteral","??","?.","WhiteSpace","Illegal","LineTerminator","PrivateField","Template","@","target","meta","LineFeed","Escaped","JSXText"],w={this:86111,function:86104,if:20569,return:20572,var:86088,else:20563,for:20567,new:86107,in:8673330,typeof:16863275,while:20578,case:20556,break:20555,try:20577,catch:20557,delete:16863276,throw:86112,switch:86110,continue:20559,default:20561,instanceof:8411187,do:20562,void:16863277,finally:20566,async:209005,await:209006,class:86094,const:86090,constructor:12399,debugger:20560,export:20564,extends:20565,false:86021,from:209011,get:209008,implements:36964,import:86106,interface:36965,let:241737,null:86023,of:471156,package:36966,private:36967,protected:36968,public:36969,set:209009,static:36970,super:86109,true:86022,with:20579,yield:241771,enum:86133,eval:537079926,as:77932,arguments:537079927,target:209029,meta:209030,accessor:12402};function S(e,t,r){for(;a[c(e)];);return e.tokenValue=e.source.slice(e.tokenIndex,e.index),92!==e.currentChar&&e.currentChar<=126?y(w,e.tokenValue)??208897:C(e,t,0,r)}function v(e,t){const r=E(e);return i(r)||e.report(5),e.tokenValue=String.fromCodePoint(r),C(e,t,1,4&n[r])}function C(e,t,r,o){let a=e.index;for(;e.index0)s(t)||e.report(20,String.fromCodePoint(t)),e.currentChar=t,e.index++,e.column++;else if(!s(e.currentChar))break;c(e)}e.index<=e.end&&(e.tokenValue+=e.source.slice(a,e.index));const{length:i}=e.tokenValue;if(o&&i>=2&&i<=11){const n=y(w,e.tokenValue);return void 0===n?208897|(r?-2147483648:0):r?209006===n?2050&t?-2147483528:-2147483648|n:1&t?36970===n?-2147483527:36864&~n?20480&~n?-2147274630:262144&t&&!(8&t)?-2147483648|n:-2147483528:-2147483527:!(262144&t)||8&t||20480&~n?241771===n?262144&t?-2147274630:1024&t?-2147483528:-2147483648|n:209005===n?-2147274630:36864&~n?-2147483528:12288|n|-2147483648:-2147483648|n:n}return 208897|(r?-2147483648:0)}function q(e){let t=c(e);if(92===t)return 130;const r=l(e);return r&&(t=r),i(t)||e.report(96),130}function E(e){return 117!==e.source.charCodeAt(e.index+1)&&e.report(5),e.currentChar=e.source.charCodeAt(e.index+=2),e.column+=2,function(e){let t=0;const r=e.currentChar;if(123===r){const r=e.index-2;for(;64&n[c(e)];)if(t=t<<4|d(e.currentChar),t>1114111)throw new T({index:r,line:e.line,column:e.column},e.currentLocation,104);if(125!==e.currentChar)throw new T({index:r,line:e.line,column:e.column},e.currentLocation,7);return c(e),t}64&n[r]||e.report(7);const o=e.source.charCodeAt(e.index+1);64&n[o]||e.report(7);const a=e.source.charCodeAt(e.index+2);64&n[a]||e.report(7);const i=e.source.charCodeAt(e.index+3);64&n[i]||e.report(7);return t=d(r)<<12|d(o)<<8|d(a)<<4|d(i),e.currentChar=e.source.charCodeAt(e.index+=4),e.column+=4,t}(e)}function N(e,t,r){let o=e.currentChar,a=0,s=9,l=64&r?0:1,u=0,p=0;if(64&r)a="."+L(e,o),o=e.currentChar,110===o&&e.report(12);else{if(48===o)if(o=c(e),120==(32|o)){for(r=136,o=c(e);4160&n[o];)95!==o?(p=1,a=16*a+d(o),u++,o=c(e)):(p||e.report(152),p=0,o=c(e));0!==u&&p||e.report(0===u?21:153)}else if(111==(32|o)){for(r=132,o=c(e);4128&n[o];)95!==o?(p=1,a=8*a+(o-48),u++,o=c(e)):(p||e.report(152),p=0,o=c(e));0!==u&&p||e.report(0===u?0:153)}else if(98==(32|o)){for(r=130,o=c(e);4224&n[o];)95!==o?(p=1,a=2*a+(o-48),u++,o=c(e)):(p||e.report(152),p=0,o=c(e));0!==u&&p||e.report(0===u?0:153)}else if(32&n[o])for(1&t&&e.report(1),r=1;16&n[o];){if(512&n[o]){r=32,l=0;break}a=8*a+(o-48),o=c(e)}else 512&n[o]?(1&t&&e.report(1),e.flags|=64,r=32):95===o&&e.report(0);if(48&r){if(l){for(;s>=0&&4112&n[o];)if(95!==o)p=0,a=10*a+(o-48),o=c(e),--s;else{if(o=c(e),95===o||32&r)throw new T(e.currentLocation,{index:e.index+1,line:e.line,column:e.column},152);p=1}if(p)throw new T(e.currentLocation,{index:e.index+1,line:e.line,column:e.column},153);if(s>=0&&!i(o)&&46!==o)return e.tokenValue=a,e.options.raw&&(e.tokenRaw=e.source.slice(e.tokenIndex,e.index)),134283266}a+=L(e,o),o=e.currentChar,46===o&&(95===c(e)&&e.report(0),r=64,a+="."+L(e,e.currentChar),o=e.currentChar)}}const g=e.index;let f=0;if(110===o&&128&r)f=1,o=c(e);else if(101==(32|o)){o=c(e),256&n[o]&&(o=c(e));const{index:t}=e;16&n[o]||e.report(11),a+=e.source.substring(g,t)+L(e,o),o=e.currentChar}return(e.index=e.source.length)return e.report(34)}const n=e.index-1;let o=I.Empty,a=e.currentChar;const{index:i}=e;for(;s(a);){switch(a){case 103:o&I.Global&&e.report(36,"g"),o|=I.Global;break;case 105:o&I.IgnoreCase&&e.report(36,"i"),o|=I.IgnoreCase;break;case 109:o&I.Multiline&&e.report(36,"m"),o|=I.Multiline;break;case 117:o&I.Unicode&&e.report(36,"u"),o&I.UnicodeSets&&e.report(36,"vu"),o|=I.Unicode;break;case 118:o&I.Unicode&&e.report(36,"uv"),o&I.UnicodeSets&&e.report(36,"v"),o|=I.UnicodeSets;break;case 121:o&I.Sticky&&e.report(36,"y"),o|=I.Sticky;break;case 115:o&I.DotAll&&e.report(36,"s"),o|=I.DotAll;break;case 100:o&I.Indices&&e.report(36,"d"),o|=I.Indices;break;default:e.report(35)}a=c(e)}const l=e.source.slice(i,e.index),u=e.source.slice(t,n);return e.tokenRegExp={pattern:u,flags:l},e.options.raw&&(e.tokenRaw=e.source.slice(e.tokenIndex,e.index)),e.tokenValue=function(e,t,r){try{return new RegExp(t,r)}catch{if(!e.options.validateRegex)return null;e.report(34)}}(e,u,l),65540}function D(e,t,r){const{index:o}=e;let a="",i=c(e),s=e.index;for(;!(8&n[i]);){if(i===r)return a+=e.source.slice(s,e.index),c(e),e.options.raw&&(e.tokenRaw=e.source.slice(o,e.index)),e.tokenValue=a,134283267;if(8&~i||92!==i)8232!==i&&8233!==i||(e.column=-1,e.line++);else{if(a+=e.source.slice(s,e.index),i=c(e),i<127||8232===i||8233===i){const r=R(e,t,i);r>=0?a+=String.fromCodePoint(r):B(e,r,0)}else a+=String.fromCodePoint(i);s=e.index+1}e.index>=e.end&&e.report(16),i=c(e)}e.report(16)}function R(e,t,r,o=0){switch(r){case 98:return 8;case 102:return 12;case 114:return 13;case 110:return 10;case 116:return 9;case 118:return 11;case 13:if(e.index1114111)return-5;return e.currentChar<1||125!==e.currentChar?-4:t}{if(!(64&n[t]))return-4;const r=e.source.charCodeAt(e.index+1);if(!(64&n[r]))return-4;const o=e.source.charCodeAt(e.index+2);if(!(64&n[o]))return-4;const a=e.source.charCodeAt(e.index+3);return 64&n[a]?(e.index+=3,e.column+=3,e.currentChar=e.source.charCodeAt(e.index),d(t)<<12|d(r)<<8|d(o)<<4|d(a)):-4}}case 56:case 57:if(o||!e.options.webcompat||1&t)return-3;e.flags|=4096;default:return r}}function B(e,t,r){switch(t){case-1:return;case-2:e.report(r?2:1);case-3:e.report(r?3:14);case-4:e.report(7);case-5:e.report(104)}}function U(e,t){const{index:r}=e;let n=67174409,o="",a=c(e);for(;96!==a;){if(36===a&&123===e.source.charCodeAt(e.index+1)){c(e),n=67174408;break}if(92===a)if(a=c(e),a>126)o+=String.fromCodePoint(a);else{const{index:r,line:i,column:s}=e,c=R(e,1|t,a,1);if(c>=0)o+=String.fromCodePoint(c);else{if(-1!==c&&64&t){e.index=r,e.line=i,e.column=s,o=null,a=P(e,a),a<0&&(n=67174408);break}B(e,c,1)}}else e.index=e.end&&e.report(17),a=c(e)}return c(e),e.tokenValue=o,e.tokenRaw=e.source.slice(r+1,e.index-(67174409===n?1:2)),n}function P(e,t){for(;96!==t;){switch(t){case 36:{const r=e.index+1;if(r=e.end&&e.report(17),t=c(e)}return t}function O(e,t){return e.index>=e.end&&e.report(0),e.index--,e.column--,U(e,t)}!function(e){e[e.Empty=0]="Empty",e[e.Escape=1]="Escape",e[e.Class=2]="Class"}(A||(A={})),function(e){e[e.Empty=0]="Empty",e[e.IgnoreCase=1]="IgnoreCase",e[e.Global=2]="Global",e[e.Multiline=4]="Multiline",e[e.Unicode=16]="Unicode",e[e.Sticky=8]="Sticky",e[e.DotAll=32]="DotAll",e[e.Indices=64]="Indices",e[e.UnicodeSets=128]="UnicodeSets"}(I||(I={}));const G=[128,128,128,128,128,128,128,128,128,127,135,127,127,129,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,127,16842798,134283267,130,208897,8391477,8390213,134283267,67174411,16,8391476,25233968,18,25233969,67108877,8457014,134283266,134283266,134283266,134283266,134283266,134283266,134283266,134283266,134283266,134283266,21,1074790417,8456256,1077936155,8390721,22,132,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,208897,69271571,136,20,8389959,208897,131,4096,4096,4096,4096,4096,4096,4096,208897,4096,208897,208897,4096,208897,4096,208897,4096,208897,4096,4096,4096,208897,4096,4096,208897,4096,4096,2162700,8389702,1074790415,16842799,128];function j(e,t){e.flags=1^(1|e.flags),e.startIndex=e.index,e.startColumn=e.column,e.startLine=e.line,e.setToken(F(e,t,0))}function F(e,t,n){const o=0===e.index,{source:a}=e;for(;e.index=e.end)return 8391476;const t=e.currentChar;return 61===t?(c(e),4194338):42!==t?8391476:61!==c(e)?8391735:(c(e),4194335)}case 8389959:return 61!==c(e)?8389959:(c(e),4194341);case 25233968:{c(e);const t=e.currentChar;return 43===t?(c(e),33619993):61===t?(c(e),4194336):25233968}case 25233969:{c(e);const r=e.currentChar;if(45===r){if(c(e),(1&n||o)&&62===e.currentChar){e.options.webcompat||e.report(112),c(e),n=k(e,a,n,t,3,e.tokenStart);continue}return 33619994}return 61===r?(c(e),4194337):25233969}case 8457014:if(c(e),e.index=48&&r<=57)return N(e,t,80);if(46===r){const t=e.index+1;if(t=48&&t<=57)))return c(e),67108990}return 22}}}else{if((8232^s)<=1){n=-5&n|1,p(e);continue}const o=l(e);if(o>0&&(s=o),r(s))return e.tokenValue="",C(e,t,0,0);if(160===(i=s)||65279===i||133===i||5760===i||i>=8192&&i<=8203||8239===i||8287===i||12288===i||8201===i||65519===i){c(e);continue}e.report(20,String.fromCodePoint(s))}}var i;return 1048576}function M(e,t){!(1&e.flags)&&1048576&~e.getToken()&&e.report(30,x[255&e.getToken()]),z(e,t,1074790417)||e.options.onInsertedSemicolon?.(e.startIndex)}function H(e,t,r,n){return t-r<13&&"use strict"===n&&(!(1048576&~e.getToken())||1&e.flags)?1:0}function J(e,t,r){return e.getToken()!==r?0:(j(e,t),1)}function z(e,t,r){return e.getToken()===r&&(j(e,t),!0)}function X(e,t,r){e.getToken()!==r&&e.report(25,x[255&r]),j(e,t)}function _(e,t){switch(t.type){case"ArrayExpression":{t.type="ArrayPattern";const{elements:r}=t;for(let t=0,n=r.length;t",Gamma:"Γ",Gammad:"Ϝ",Gbreve:"Ğ",Gcedil:"Ģ",Gcirc:"Ĝ",Gcy:"Г",Gdot:"Ġ",Gfr:"𝔊",Gg:"⋙",Gopf:"𝔾",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",Gscr:"𝒢",Gt:"≫",HARDcy:"Ъ",Hacek:"ˇ",Hat:"^",Hcirc:"Ĥ",Hfr:"ℌ",HilbertSpace:"ℋ",Hopf:"ℍ",HorizontalLine:"─",Hscr:"ℋ",Hstrok:"Ħ",HumpDownHump:"≎",HumpEqual:"≏",IEcy:"Е",IJlig:"IJ",IOcy:"Ё",Iacute:"Í",Icirc:"Î",Icy:"И",Idot:"İ",Ifr:"ℑ",Igrave:"Ì",Im:"ℑ",Imacr:"Ī",ImaginaryI:"ⅈ",Implies:"⇒",Int:"∬",Integral:"∫",Intersection:"⋂",InvisibleComma:"⁣",InvisibleTimes:"⁢",Iogon:"Į",Iopf:"𝕀",Iota:"Ι",Iscr:"ℐ",Itilde:"Ĩ",Iukcy:"І",Iuml:"Ï",Jcirc:"Ĵ",Jcy:"Й",Jfr:"𝔍",Jopf:"𝕁",Jscr:"𝒥",Jsercy:"Ј",Jukcy:"Є",KHcy:"Х",KJcy:"Ќ",Kappa:"Κ",Kcedil:"Ķ",Kcy:"К",Kfr:"𝔎",Kopf:"𝕂",Kscr:"𝒦",LJcy:"Љ",LT:"<",Lacute:"Ĺ",Lambda:"Λ",Lang:"⟪",Laplacetrf:"ℒ",Larr:"↞",Lcaron:"Ľ",Lcedil:"Ļ",Lcy:"Л",LeftAngleBracket:"⟨",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",LeftRightArrow:"↔",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",Leftarrow:"⇐",Leftrightarrow:"⇔",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",LessLess:"⪡",LessSlantEqual:"⩽",LessTilde:"≲",Lfr:"𝔏",Ll:"⋘",Lleftarrow:"⇚",Lmidot:"Ŀ",LongLeftArrow:"⟵",LongLeftRightArrow:"⟷",LongRightArrow:"⟶",Longleftarrow:"⟸",Longleftrightarrow:"⟺",Longrightarrow:"⟹",Lopf:"𝕃",LowerLeftArrow:"↙",LowerRightArrow:"↘",Lscr:"ℒ",Lsh:"↰",Lstrok:"Ł",Lt:"≪",Map:"⤅",Mcy:"М",MediumSpace:" ",Mellintrf:"ℳ",Mfr:"𝔐",MinusPlus:"∓",Mopf:"𝕄",Mscr:"ℳ",Mu:"Μ",NJcy:"Њ",Nacute:"Ń",Ncaron:"Ň",Ncedil:"Ņ",Ncy:"Н",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",Nfr:"𝔑",NoBreak:"⁠",NonBreakingSpace:" ",Nopf:"ℕ",Not:"⫬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",Nscr:"𝒩",Ntilde:"Ñ",Nu:"Ν",OElig:"Œ",Oacute:"Ó",Ocirc:"Ô",Ocy:"О",Odblac:"Ő",Ofr:"𝔒",Ograve:"Ò",Omacr:"Ō",Omega:"Ω",Omicron:"Ο",Oopf:"𝕆",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",Or:"⩔",Oscr:"𝒪",Oslash:"Ø",Otilde:"Õ",Otimes:"⨷",Ouml:"Ö",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",PartialD:"∂",Pcy:"П",Pfr:"𝔓",Phi:"Φ",Pi:"Π",PlusMinus:"±",Poincareplane:"ℌ",Popf:"ℙ",Pr:"⪻",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",Prime:"″",Product:"∏",Proportion:"∷",Proportional:"∝",Pscr:"𝒫",Psi:"Ψ",QUOT:'"',Qfr:"𝔔",Qopf:"ℚ",Qscr:"𝒬",RBarr:"⤐",REG:"®",Racute:"Ŕ",Rang:"⟫",Rarr:"↠",Rarrtl:"⤖",Rcaron:"Ř",Rcedil:"Ŗ",Rcy:"Р",Re:"ℜ",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",Rfr:"ℜ",Rho:"Ρ",RightAngleBracket:"⟩",RightArrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",Rightarrow:"⇒",Ropf:"ℝ",RoundImplies:"⥰",Rrightarrow:"⇛",Rscr:"ℛ",Rsh:"↱",RuleDelayed:"⧴",SHCHcy:"Щ",SHcy:"Ш",SOFTcy:"Ь",Sacute:"Ś",Sc:"⪼",Scaron:"Š",Scedil:"Ş",Scirc:"Ŝ",Scy:"С",Sfr:"𝔖",ShortDownArrow:"↓",ShortLeftArrow:"←",ShortRightArrow:"→",ShortUpArrow:"↑",Sigma:"Σ",SmallCircle:"∘",Sopf:"𝕊",Sqrt:"√",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",Sscr:"𝒮",Star:"⋆",Sub:"⋐",Subset:"⋐",SubsetEqual:"⊆",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",SuchThat:"∋",Sum:"∑",Sup:"⋑",Superset:"⊃",SupersetEqual:"⊇",Supset:"⋑",THORN:"Þ",TRADE:"™",TSHcy:"Ћ",TScy:"Ц",Tab:"\t",Tau:"Τ",Tcaron:"Ť",Tcedil:"Ţ",Tcy:"Т",Tfr:"𝔗",Therefore:"∴",Theta:"Θ",ThickSpace:"  ",ThinSpace:" ",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",Topf:"𝕋",TripleDot:"⃛",Tscr:"𝒯",Tstrok:"Ŧ",Uacute:"Ú",Uarr:"↟",Uarrocir:"⥉",Ubrcy:"Ў",Ubreve:"Ŭ",Ucirc:"Û",Ucy:"У",Udblac:"Ű",Ufr:"𝔘",Ugrave:"Ù",Umacr:"Ū",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",Uopf:"𝕌",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",UpDownArrow:"↕",UpEquilibrium:"⥮",UpTee:"⊥",UpTeeArrow:"↥",Uparrow:"⇑",Updownarrow:"⇕",UpperLeftArrow:"↖",UpperRightArrow:"↗",Upsi:"ϒ",Upsilon:"Υ",Uring:"Ů",Uscr:"𝒰",Utilde:"Ũ",Uuml:"Ü",VDash:"⊫",Vbar:"⫫",Vcy:"В",Vdash:"⊩",Vdashl:"⫦",Vee:"⋁",Verbar:"‖",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"𝔙",Vopf:"𝕍",Vscr:"𝒱",Vvdash:"⊪",Wcirc:"Ŵ",Wedge:"⋀",Wfr:"𝔚",Wopf:"𝕎",Wscr:"𝒲",Xfr:"𝔛",Xi:"Ξ",Xopf:"𝕏",Xscr:"𝒳",YAcy:"Я",YIcy:"Ї",YUcy:"Ю",Yacute:"Ý",Ycirc:"Ŷ",Ycy:"Ы",Yfr:"𝔜",Yopf:"𝕐",Yscr:"𝒴",Yuml:"Ÿ",ZHcy:"Ж",Zacute:"Ź",Zcaron:"Ž",Zcy:"З",Zdot:"Ż",ZeroWidthSpace:"​",Zeta:"Ζ",Zfr:"ℨ",Zopf:"ℤ",Zscr:"𝒵",aacute:"á",abreve:"ă",ac:"∾",acE:"∾̳",acd:"∿",acirc:"â",acute:"´",acy:"а",aelig:"æ",af:"⁡",afr:"𝔞",agrave:"à",alefsym:"ℵ",aleph:"ℵ",alpha:"α",amacr:"ā",amalg:"⨿",amp:"&",and:"∧",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",aogon:"ą",aopf:"𝕒",ap:"≈",apE:"⩰",apacir:"⩯",ape:"≊",apid:"≋",apos:"'",approx:"≈",approxeq:"≊",aring:"å",ascr:"𝒶",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ã",auml:"ä",awconint:"∳",awint:"⨑",bNot:"⫭",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",barvee:"⊽",barwed:"⌅",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",bcy:"б",bdquo:"„",becaus:"∵",because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",beta:"β",beth:"ℶ",between:"≬",bfr:"𝔟",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bnot:"⌐",bopf:"𝕓",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxDL:"╗",boxDR:"╔",boxDl:"╖",boxDr:"╓",boxH:"═",boxHD:"╦",boxHU:"╩",boxHd:"╤",boxHu:"╧",boxUL:"╝",boxUR:"╚",boxUl:"╜",boxUr:"╙",boxV:"║",boxVH:"╬",boxVL:"╣",boxVR:"╠",boxVh:"╫",boxVl:"╢",boxVr:"╟",boxbox:"⧉",boxdL:"╕",boxdR:"╒",boxdl:"┐",boxdr:"┌",boxh:"─",boxhD:"╥",boxhU:"╨",boxhd:"┬",boxhu:"┴",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxuL:"╛",boxuR:"╘",boxul:"┘",boxur:"└",boxv:"│",boxvH:"╪",boxvL:"╡",boxvR:"╞",boxvh:"┼",boxvl:"┤",boxvr:"├",bprime:"‵",breve:"˘",brvbar:"¦",bscr:"𝒷",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"≏",bumpeq:"≏",cacute:"ć",cap:"∩",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",caps:"∩︀",caret:"⁁",caron:"ˇ",ccaps:"⩍",ccaron:"č",ccedil:"ç",ccirc:"ĉ",ccups:"⩌",ccupssm:"⩐",cdot:"ċ",cedil:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",cfr:"𝔠",chcy:"ч",check:"✓",checkmark:"✓",chi:"χ",cir:"○",cirE:"⧃",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledR:"®",circledS:"Ⓢ",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",cire:"≗",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",clubs:"♣",clubsuit:"♣",colon:":",colone:"≔",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",conint:"∮",copf:"𝕔",coprod:"∐",copy:"©",copysr:"℗",crarr:"↵",cross:"✗",cscr:"𝒸",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",cup:"∪",cupbrcap:"⩈",cupcap:"⩆",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",dArr:"⇓",dHar:"⥥",dagger:"†",daleth:"ℸ",darr:"↓",dash:"‐",dashv:"⊣",dbkarow:"⤏",dblac:"˝",dcaron:"ď",dcy:"д",dd:"ⅆ",ddagger:"‡",ddarr:"⇊",ddotseq:"⩷",deg:"°",delta:"δ",demptyv:"⦱",dfisht:"⥿",dfr:"𝔡",dharl:"⇃",dharr:"⇂",diam:"⋄",diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",djcy:"ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",dot:"˙",doteq:"≐",doteqdot:"≑",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",downarrow:"↓",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",dscy:"ѕ",dsol:"⧶",dstrok:"đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",dzcy:"џ",dzigrarr:"⟿",eDDot:"⩷",eDot:"≑",eacute:"é",easter:"⩮",ecaron:"ě",ecir:"≖",ecirc:"ê",ecolon:"≕",ecy:"э",edot:"ė",ee:"ⅇ",efDot:"≒",efr:"𝔢",eg:"⪚",egrave:"è",egs:"⪖",egsdot:"⪘",el:"⪙",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",emacr:"ē",empty:"∅",emptyset:"∅",emptyv:"∅",emsp13:" ",emsp14:" ",emsp:" ",eng:"ŋ",ensp:" ",eogon:"ę",eopf:"𝕖",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",epsilon:"ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",equals:"=",equest:"≟",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erDot:"≓",erarr:"⥱",escr:"ℯ",esdot:"≐",esim:"≂",eta:"η",eth:"ð",euml:"ë",euro:"€",excl:"!",exist:"∃",expectation:"ℰ",exponentiale:"ⅇ",fallingdotseq:"≒",fcy:"ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",ffr:"𝔣",filig:"fi",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",fopf:"𝕗",forall:"∀",fork:"⋔",forkv:"⫙",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",fscr:"𝒻",gE:"≧",gEl:"⪌",gacute:"ǵ",gamma:"γ",gammad:"ϝ",gap:"⪆",gbreve:"ğ",gcirc:"ĝ",gcy:"г",gdot:"ġ",ge:"≥",gel:"⋛",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",gfr:"𝔤",gg:"≫",ggg:"⋙",gimel:"ℷ",gjcy:"ѓ",gl:"≷",glE:"⪒",gla:"⪥",glj:"⪤",gnE:"≩",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gneq:"⪈",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",grave:"`",gscr:"ℊ",gsim:"≳",gsime:"⪎",gsiml:"⪐",gt:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",hArr:"⇔",hairsp:" ",half:"½",hamilt:"ℋ",hardcy:"ъ",harr:"↔",harrcir:"⥈",harrw:"↭",hbar:"ℏ",hcirc:"ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"𝔥",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"𝕙",horbar:"―",hscr:"𝒽",hslash:"ℏ",hstrok:"ħ",hybull:"⁃",hyphen:"‐",iacute:"í",ic:"⁣",icirc:"î",icy:"и",iecy:"е",iexcl:"¡",iff:"⇔",ifr:"𝔦",igrave:"ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"ij",imacr:"ī",image:"ℑ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",int:"∫",intcal:"⊺",integers:"ℤ",intercal:"⊺",intlarhk:"⨗",intprod:"⨼",iocy:"ё",iogon:"į",iopf:"𝕚",iota:"ι",iprod:"⨼",iquest:"¿",iscr:"𝒾",isin:"∈",isinE:"⋹",isindot:"⋵",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",itilde:"ĩ",iukcy:"і",iuml:"ï",jcirc:"ĵ",jcy:"й",jfr:"𝔧",jmath:"ȷ",jopf:"𝕛",jscr:"𝒿",jsercy:"ј",jukcy:"є",kappa:"κ",kappav:"ϰ",kcedil:"ķ",kcy:"к",kfr:"𝔨",kgreen:"ĸ",khcy:"х",kjcy:"ќ",kopf:"𝕜",kscr:"𝓀",lAarr:"⇚",lArr:"⇐",lAtail:"⤛",lBarr:"⤎",lE:"≦",lEg:"⪋",lHar:"⥢",lacute:"ĺ",laemptyv:"⦴",lagran:"ℒ",lambda:"λ",lang:"⟨",langd:"⦑",langle:"⟨",lap:"⪅",laquo:"«",larr:"←",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",latail:"⤙",late:"⪭",lates:"⪭︀",lbarr:"⤌",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",lcaron:"ľ",lcedil:"ļ",lceil:"⌈",lcub:"{",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",leftarrow:"←",leftarrowtail:"↢",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",leftthreetimes:"⋋",leg:"⋚",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",lessgtr:"≶",lesssim:"≲",lfisht:"⥼",lfloor:"⌊",lfr:"𝔩",lg:"≶",lgE:"⪑",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",ljcy:"љ",ll:"≪",llarr:"⇇",llcorner:"⌞",llhard:"⥫",lltri:"◺",lmidot:"ŀ",lmoust:"⎰",lmoustache:"⎰",lnE:"≨",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",longleftrightarrow:"⟷",longmapsto:"⟼",longrightarrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",lopf:"𝕝",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",lscr:"𝓁",lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",lt:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltrPar:"⦖",ltri:"◃",ltrie:"⊴",ltrif:"◂",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",mDDot:"∺",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",mcy:"м",mdash:"—",measuredangle:"∡",mfr:"𝔪",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",mopf:"𝕞",mp:"∓",mscr:"𝓂",mstpos:"∾",mu:"μ",multimap:"⊸",mumap:"⊸",nGg:"⋙̸",nGt:"≫⃒",nGtv:"≫̸",nLeftarrow:"⇍",nLeftrightarrow:"⇎",nLl:"⋘̸",nLt:"≪⃒",nLtv:"≪̸",nRightarrow:"⇏",nVDash:"⊯",nVdash:"⊮",nabla:"∇",nacute:"ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",ncaron:"ň",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",ncy:"н",ndash:"–",ne:"≠",neArr:"⇗",nearhk:"⤤",nearr:"↗",nearrow:"↗",nedot:"≐̸",nequiv:"≢",nesear:"⤨",nesim:"≂̸",nexist:"∄",nexists:"∄",nfr:"𝔫",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",ngsim:"≵",ngt:"≯",ngtr:"≯",nhArr:"⇎",nharr:"↮",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",njcy:"њ",nlArr:"⇍",nlE:"≦̸",nlarr:"↚",nldr:"‥",nle:"≰",nleftarrow:"↚",nleftrightarrow:"↮",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nlsim:"≴",nlt:"≮",nltri:"⋪",nltrie:"⋬",nmid:"∤",nopf:"𝕟",not:"¬",notin:"∉",notinE:"⋹̸",notindot:"⋵̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrArr:"⇏",nrarr:"↛",nrarrc:"⤳̸",nrarrw:"↝̸",nrightarrow:"↛",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",nscr:"𝓃",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",nu:"ν",num:"#",numero:"№",numsp:" ",nvDash:"⊭",nvHarr:"⤄",nvap:"≍⃒",nvdash:"⊬",nvge:"≥⃒",nvgt:">⃒",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwArr:"⇖",nwarhk:"⤣",nwarr:"↖",nwarrow:"↖",nwnear:"⤧",oS:"Ⓢ",oacute:"ó",oast:"⊛",ocir:"⊚",ocirc:"ô",ocy:"о",odash:"⊝",odblac:"ő",odiv:"⨸",odot:"⊙",odsold:"⦼",oelig:"œ",ofcir:"⦿",ofr:"𝔬",ogon:"˛",ograve:"ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",omacr:"ō",omega:"ω",omicron:"ο",omid:"⦶",ominus:"⊖",oopf:"𝕠",opar:"⦷",operp:"⦹",oplus:"⊕",or:"∨",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oscr:"ℴ",oslash:"ø",osol:"⊘",otilde:"õ",otimes:"⊗",otimesas:"⨶",ouml:"ö",ovbar:"⌽",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",pfr:"𝔭",phi:"φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",pi:"π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",pointint:"⨕",popf:"𝕡",pound:"£",pr:"≺",prE:"⪳",prap:"⪷",prcue:"≼",pre:"⪯",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",prime:"′",primes:"ℙ",prnE:"⪵",prnap:"⪹",prnsim:"⋨",prod:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",psi:"ψ",puncsp:" ",qfr:"𝔮",qint:"⨌",qopf:"𝕢",qprime:"⁗",qscr:"𝓆",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',rAarr:"⇛",rArr:"⇒",rAtail:"⤜",rBarr:"⤏",rHar:"⥤",race:"∽̱",racute:"ŕ",radic:"√",raemptyv:"⦳",rang:"⟩",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarr:"→",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",rarrtl:"↣",rarrw:"↝",ratail:"⤚",ratio:"∶",rationals:"ℚ",rbarr:"⤍",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",rcaron:"ř",rcedil:"ŗ",rceil:"⌉",rcub:"}",rcy:"р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"®",rfisht:"⥽",rfloor:"⌋",rfr:"𝔯",rhard:"⇁",rharu:"⇀",rharul:"⥬",rho:"ρ",rhov:"ϱ",rightarrow:"→",rightarrowtail:"↣",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",rightthreetimes:"⋌",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"𝕣",roplus:"⨮",rotimes:"⨵",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",rsaquo:"›",rscr:"𝓇",rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",ruluhar:"⥨",rx:"℞",sacute:"ś",sbquo:"‚",sc:"≻",scE:"⪴",scap:"⪸",scaron:"š",sccue:"≽",sce:"⪰",scedil:"ş",scirc:"ŝ",scnE:"⪶",scnap:"⪺",scnsim:"⋩",scpolint:"⨓",scsim:"≿",scy:"с",sdot:"⋅",sdotb:"⊡",sdote:"⩦",seArr:"⇘",searhk:"⤥",searr:"↘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",sfr:"𝔰",sfrown:"⌢",sharp:"♯",shchcy:"щ",shcy:"ш",shortmid:"∣",shortparallel:"∥",shy:"­",sigma:"σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",softcy:"ь",sol:"/",solb:"⧄",solbar:"⌿",sopf:"𝕤",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",squarf:"▪",squf:"▪",srarr:"→",sscr:"𝓈",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",sub:"⊂",subE:"⫅",subdot:"⪽",sube:"⊆",subedot:"⫃",submult:"⫁",subnE:"⫋",subne:"⊊",subplus:"⪿",subrarr:"⥹",subset:"⊂",subseteq:"⊆",subseteqq:"⫅",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",sum:"∑",sung:"♪",sup1:"¹",sup2:"²",sup3:"³",sup:"⊃",supE:"⫆",supdot:"⪾",supdsub:"⫘",supe:"⊇",supedot:"⫄",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supnE:"⫌",supne:"⊋",supplus:"⫀",supset:"⊃",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swArr:"⇙",swarhk:"⤦",swarr:"↙",swarrow:"↙",swnwar:"⤪",szlig:"ß",target:"⌖",tau:"τ",tbrk:"⎴",tcaron:"ť",tcedil:"ţ",tcy:"т",tdot:"⃛",telrec:"⌕",tfr:"𝔱",there4:"∴",therefore:"∴",theta:"θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",thinsp:" ",thkap:"≈",thksim:"∼",thorn:"þ",tilde:"˜",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",topf:"𝕥",topfork:"⫚",tosa:"⤩",tprime:"‴",trade:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",tscr:"𝓉",tscy:"ц",tshcy:"ћ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uArr:"⇑",uHar:"⥣",uacute:"ú",uarr:"↑",ubrcy:"ў",ubreve:"ŭ",ucirc:"û",ucy:"у",udarr:"⇅",udblac:"ű",udhar:"⥮",ufisht:"⥾",ufr:"𝔲",ugrave:"ù",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ū",uml:"¨",uogon:"ų",uopf:"𝕦",uparrow:"↑",updownarrow:"↕",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",upsi:"υ",upsih:"ϒ",upsilon:"υ",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",urtri:"◹",uscr:"𝓊",utdot:"⋰",utilde:"ũ",utri:"▵",utrif:"▴",uuarr:"⇈",uuml:"ü",uwangle:"⦧",vArr:"⇕",vBar:"⫨",vBarv:"⫩",vDash:"⊨",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",varr:"↕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",vcy:"в",vdash:"⊢",vee:"∨",veebar:"⊻",veeeq:"≚",vellip:"⋮",verbar:"|",vert:"|",vfr:"𝔳",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",vprop:"∝",vrtri:"⊳",vscr:"𝓋",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",vzigzag:"⦚",wcirc:"ŵ",wedbar:"⩟",wedge:"∧",wedgeq:"≙",weierp:"℘",wfr:"𝔴",wopf:"𝕨",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",xfr:"𝔵",xhArr:"⟺",xharr:"⟷",xi:"ξ",xlArr:"⟸",xlarr:"⟵",xmap:"⟼",xnis:"⋻",xodot:"⨀",xopf:"𝕩",xoplus:"⨁",xotime:"⨂",xrArr:"⟹",xrarr:"⟶",xscr:"𝓍",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",yacute:"ý",yacy:"я",ycirc:"ŷ",ycy:"ы",yen:"¥",yfr:"𝔶",yicy:"ї",yopf:"𝕪",yscr:"𝓎",yucy:"ю",yuml:"ÿ",zacute:"ź",zcaron:"ž",zcy:"з",zdot:"ż",zeetrf:"ℨ",zeta:"ζ",zfr:"𝔷",zhcy:"ж",zigrarr:"⇝",zopf:"𝕫",zscr:"𝓏",zwj:"‍",zwnj:"‌"},re={0:65533,128:8364,130:8218,131:402,132:8222,133:8230,134:8224,135:8225,136:710,137:8240,138:352,139:8249,140:338,142:381,145:8216,146:8217,147:8220,148:8221,149:8226,150:8211,151:8212,152:732,153:8482,154:353,155:8250,156:339,158:382,159:376};function ne(e){return e.replace(/&(?:[a-zA-Z]+|#[xX][\da-fA-F]+|#\d+);/g,(e=>{if("#"===e.charAt(1)){const t=e.charAt(2);return function(e){if(e>=55296&&e<=57343||e>1114111)return"�";return String.fromCodePoint(y(re,e)??e)}("X"===t||"x"===t?parseInt(e.slice(3),16):parseInt(e.slice(2),10))}return y(te,e.slice(1,-1))??e}))}function oe(e,t){return e.startIndex=e.tokenIndex=e.index,e.startColumn=e.tokenColumn=e.column,e.startLine=e.tokenLine=e.line,e.setToken(8192&n[e.currentChar]?function(e){const t=e.currentChar;let r=c(e);const n=e.index;for(;r!==t;)e.index>=e.end&&e.report(16),r=c(e);r!==t&&e.report(16);e.tokenValue=e.source.slice(n,e.index),c(e),e.options.raw&&(e.tokenRaw=e.source.slice(e.tokenIndex,e.index));return 134283267}(e):F(e,t,0)),e.getToken()}function ae(e){if(e.startIndex=e.tokenIndex=e.index,e.startColumn=e.tokenColumn=e.column,e.startLine=e.tokenLine=e.line,e.index>=e.end)return void e.setToken(1048576);if(60===e.currentChar)return c(e),void e.setToken(8456256);if(123===e.currentChar)return c(e),void e.setToken(2162700);let t=0;for(;e.indexe.declareUnboundVariable(t)))):(c&&e.report(172),r&&(n.forEach((t=>e.declareUnboundVariable(t))),a.forEach((t=>e.addBindingToExports(t))))),M(e,32|t);break}case 132:case 86094:a=Tt(e,t,r,void 0,2);break;case 86104:a=nt(e,t,r,void 0,4,1,2,0,e.tokenStart);break;case 241737:a=we(e,t,r,void 0,8,64);break;case 86090:a=we(e,t,r,void 0,16,64);break;case 86088:a=Se(e,t,r,void 0,64);break;case 209005:{const{tokenStart:n}=e;if(j(e,t),!(1&e.flags)&&86104===e.getToken()){a=nt(e,t,r,void 0,4,1,2,1,n);break}}default:e.report(30,x[255&e.getToken()])}const c={type:"ExportNamedDeclaration",declaration:a,specifiers:o,source:i,attributes:s};return e.finishNode(c,n)}(e,t,r);break;case 86106:n=function(e,t,r){const n=e.tokenStart;j(e,t);let o=null;const{tokenStart:a}=e;let i=[];if(134283267===e.getToken())o=rt(e,t);else{if(143360&e.getToken()){const n=qe(e,t,r);if(i=[e.finishNode({type:"ImportDefaultSpecifier",local:n},a)],z(e,t,18))switch(e.getToken()){case 8391476:i.push(Ee(e,t,r));break;case 2162700:Ne(e,t,r,i);break;default:e.report(107)}}else switch(e.getToken()){case 8391476:i=[Ee(e,t,r)];break;case 2162700:Ne(e,t,r,i);break;case 67174411:return Ae(e,t,void 0,n);case 67108877:return Le(e,t,n);default:e.report(30,x[255&e.getToken()])}o=function(e,t){X(e,t,209011),134283267!==e.getToken()&&e.report(105,"Import");return rt(e,t)}(e,t)}const s=ze(e,t),c={type:"ImportDeclaration",specifiers:i,source:o,attributes:s};return M(e,32|t),e.finishNode(c,n)}(e,t,r);break;default:n=ge(e,t,r,void 0,4,{})}return e.leadingDecorators?.decorators.length&&e.report(170),n}function ge(e,t,r,n,o,a){const i=e.tokenStart;switch(e.getToken()){case 86104:return nt(e,t,r,n,o,1,0,0,i);case 132:case 86094:return Tt(e,t,r,n,0);case 86090:return we(e,t,r,n,16,0);case 241737:return function(e,t,r,n,o){const{tokenValue:a,tokenStart:i}=e,s=e.getToken();let c=tt(e,t);if(2240512&e.getToken()){const o=ve(e,t,r,n,8,0);return M(e,32|t),e.finishNode({type:"VariableDeclaration",kind:"let",declarations:o},i)}e.assignable=1,1&t&&e.report(85);if(21===e.getToken())return me(e,t,r,n,o,{},a,c,s,0,i);if(10===e.getToken()){let r;e.options.lexical&&(r=le(e,t,a)),e.flags=128^(128|e.flags),c=ft(e,t,r,n,[c],0,i)}else c=je(e,t,n,c,0,0,i),c=Re(e,t,n,0,0,i,c);18===e.getToken()&&(c=Ve(e,t,n,0,i,c));return he(e,t,c,i)}(e,t,r,n,o);case 20564:e.report(103,"export");case 86106:switch(j(e,t),e.getToken()){case 67174411:return Ae(e,t,n,i);case 67108877:return Le(e,t,i);default:e.report(103,"import")}case 209005:return be(e,t,r,n,o,a,1);default:return fe(e,t,r,n,o,a,1)}}function fe(e,t,r,n,o,a,i){switch(e.getToken()){case 86088:return Se(e,t,r,n,0);case 20572:return function(e,t,r){4096&t||e.report(92);const n=e.tokenStart;j(e,32|t);const o=1&e.flags||1048576&e.getToken()?null:De(e,t,r,0,1,e.tokenStart);return M(e,32|t),e.finishNode({type:"ReturnStatement",argument:o},n)}(e,t,n);case 20569:return function(e,t,r,n,o){const a=e.tokenStart;j(e,t),X(e,32|t,67174411),e.assignable=1;const i=De(e,t,n,0,1,e.tokenStart);X(e,32|t,16);const s=ye(e,t,r,n,o);let c=null;20563===e.getToken()&&(j(e,32|t),c=ye(e,t,r,n,o));return e.finishNode({type:"IfStatement",test:i,consequent:s,alternate:c},a)}(e,t,r,n,a);case 20567:return function(e,t,r,n,o){const a=e.tokenStart;j(e,t);const i=((2048&t)>0||(2&t)>0&&(8&t)>0)&&z(e,t,209006);X(e,32|t,67174411),r=r?.createChildScope(1);let s,c=null,l=null,u=0,p=null,d=86088===e.getToken()||241737===e.getToken()||86090===e.getToken();const{tokenStart:g}=e,f=e.getToken();if(d)241737===f?(p=tt(e,t),2240512&e.getToken()?(8673330===e.getToken()?1&t&&e.report(67):p=e.finishNode({type:"VariableDeclaration",kind:"let",declarations:ve(e,131072|t,r,n,8,32)},g),e.assignable=1):1&t?e.report(67):(d=!1,e.assignable=1,p=je(e,t,n,p,0,0,g),471156===e.getToken()&&e.report(115))):(j(e,t),p=e.finishNode(86088===f?{type:"VariableDeclaration",kind:"var",declarations:ve(e,131072|t,r,n,4,32)}:{type:"VariableDeclaration",kind:"const",declarations:ve(e,131072|t,r,n,16,32)},g),e.assignable=1);else if(1074790417===f)i&&e.report(82);else if(2097152&~f)p=Ge(e,131072|t,n,1,0,1);else{const r=e.tokenStart;p=2162700===f?lt(e,t,void 0,n,1,0,0,2,32):at(e,t,void 0,n,1,0,0,2,32),u=e.destructible,64&u&&e.report(63),e.assignable=16&u?2:1,p=je(e,131072|t,n,p,0,0,r)}if(!(262144&~e.getToken())){if(471156===e.getToken()){2&e.assignable&&e.report(80,i?"await":"of"),_(e,p),j(e,32|t),s=Ie(e,t,n,1,0,e.tokenStart),X(e,32|t,16);const c=xe(e,t,r,n,o);return e.finishNode({type:"ForOfStatement",left:p,right:s,body:c,await:i},a)}2&e.assignable&&e.report(80,"in"),_(e,p),j(e,32|t),i&&e.report(82),s=De(e,t,n,0,1,e.tokenStart),X(e,32|t,16);const c=xe(e,t,r,n,o);return e.finishNode({type:"ForInStatement",body:c,left:p,right:s},a)}i&&e.report(82);d||(8&u&&1077936155!==e.getToken()&&e.report(80,"loop"),p=Re(e,131072|t,n,0,0,g,p));18===e.getToken()&&(p=Ve(e,t,n,0,g,p));X(e,32|t,1074790417),1074790417!==e.getToken()&&(c=De(e,t,n,0,1,e.tokenStart));X(e,32|t,1074790417),16!==e.getToken()&&(l=De(e,t,n,0,1,e.tokenStart));X(e,32|t,16);const k=xe(e,t,r,n,o);return e.finishNode({type:"ForStatement",init:p,test:c,update:l,body:k},a)}(e,t,r,n,a);case 20562:return function(e,t,r,n,o){const a=e.tokenStart;j(e,32|t);const i=xe(e,t,r,n,o);X(e,t,20578),X(e,32|t,67174411);const s=De(e,t,n,0,1,e.tokenStart);return X(e,32|t,16),z(e,32|t,1074790417),e.finishNode({type:"DoWhileStatement",body:i,test:s},a)}(e,t,r,n,a);case 20578:return function(e,t,r,n,o){const a=e.tokenStart;j(e,t),X(e,32|t,67174411);const i=De(e,t,n,0,1,e.tokenStart);X(e,32|t,16);const s=xe(e,t,r,n,o);return e.finishNode({type:"WhileStatement",test:i,body:s},a)}(e,t,r,n,a);case 86110:return function(e,t,r,n,o){const a=e.tokenStart;j(e,t),X(e,32|t,67174411);const i=De(e,t,n,0,1,e.tokenStart);X(e,t,16),X(e,t,2162700);const s=[];let c=0;r=r?.createChildScope(8);for(;1074790415!==e.getToken();){const{tokenStart:a}=e;let i=null;const l=[];for(z(e,32|t,20556)?i=De(e,t,n,0,1,e.tokenStart):(X(e,32|t,20561),c&&e.report(89),c=1),X(e,32|t,21);20556!==e.getToken()&&1074790415!==e.getToken()&&20561!==e.getToken();)l.push(ge(e,4|t,r,n,2,{$:o}));s.push(e.finishNode({type:"SwitchCase",test:i,consequent:l},a))}return X(e,32|t,1074790415),e.finishNode({type:"SwitchStatement",discriminant:i,cases:s},a)}(e,t,r,n,a);case 1074790417:return function(e,t){const r=e.tokenStart;return j(e,32|t),e.finishNode({type:"EmptyStatement"},r)}(e,t);case 2162700:return ke(e,t,r?.createChildScope(),n,a,e.tokenStart);case 86112:return function(e,t,r){const n=e.tokenStart;j(e,32|t),1&e.flags&&e.report(90);const o=De(e,t,r,0,1,e.tokenStart);return M(e,32|t),e.finishNode({type:"ThrowStatement",argument:o},n)}(e,t,n);case 20555:return function(e,t,r){const n=e.tokenStart;j(e,32|t);let o=null;if(!(1&e.flags)&&143360&e.getToken()){const{tokenValue:n}=e;o=tt(e,32|t),Z(e,r,n,0)||e.report(138,n)}else 132&t||e.report(69);return M(e,32|t),e.finishNode({type:"BreakStatement",label:o},n)}(e,t,a);case 20559:return function(e,t,r){128&t||e.report(68);const n=e.tokenStart;j(e,t);let o=null;if(!(1&e.flags)&&143360&e.getToken()){const{tokenValue:n}=e;o=tt(e,32|t),Z(e,r,n,1)||e.report(138,n)}return M(e,32|t),e.finishNode({type:"ContinueStatement",label:o},n)}(e,t,a);case 20577:return function(e,t,r,n,o){const a=e.tokenStart;j(e,32|t);const i=r?.createChildScope(16),s=ke(e,t,i,n,{$:o}),{tokenStart:c}=e,l=z(e,32|t,20557)?function(e,t,r,n,o,a){let i=null,s=r;z(e,t,67174411)&&(r=r?.createChildScope(4),i=qt(e,t,r,n,2097152&~e.getToken()?512:256,0),18===e.getToken()?e.report(86):1077936155===e.getToken()&&e.report(87),X(e,32|t,16));s=r?.createChildScope(32);const c=ke(e,t,s,n,{$:o});return e.finishNode({type:"CatchClause",param:i,body:c},a)}(e,t,r,n,o,c):null;let u=null;if(20566===e.getToken()){j(e,32|t);const a=r?.createChildScope(4);u=ke(e,t,a,n,{$:o})}l||u||e.report(88);return e.finishNode({type:"TryStatement",block:s,handler:l,finalizer:u},a)}(e,t,r,n,a);case 20579:return function(e,t,r,n,o){const a=e.tokenStart;j(e,t),1&t&&e.report(91);X(e,32|t,67174411);const i=De(e,t,n,0,1,e.tokenStart);X(e,32|t,16);const s=fe(e,t,r,n,2,o,0);return e.finishNode({type:"WithStatement",object:i,body:s},a)}(e,t,r,n,a);case 20560:return function(e,t){const r=e.tokenStart;return j(e,32|t),M(e,32|t),e.finishNode({type:"DebuggerStatement"},r)}(e,t);case 209005:return be(e,t,r,n,o,a,0);case 20557:e.report(162);case 20566:e.report(163);case 86104:e.report(1&t?76:e.options.webcompat?77:78);case 86094:e.report(79);default:return function(e,t,r,n,o,a,i){const{tokenValue:s,tokenStart:c}=e,l=e.getToken();let u;if(241737===l)u=tt(e,t),1&t&&e.report(85),69271571===e.getToken()&&e.report(84);else u=Me(e,t,n,2,0,1,0,1,e.tokenStart);if(143360&l&&21===e.getToken())return me(e,t,r,n,o,a,s,u,l,i,c);u=je(e,t,n,u,0,0,c),u=Re(e,t,n,0,0,c,u),18===e.getToken()&&(u=Ve(e,t,n,0,c,u));return he(e,t,u,c)}(e,t,r,n,o,a,i)}}function ke(e,t,r,n,o,a=e.tokenStart,i="BlockStatement"){const s=[];for(X(e,32|t,2162700);1074790415!==e.getToken();)s.push(ge(e,t,r,n,2,{$:o}));return X(e,32|t,1074790415),e.finishNode({type:i,body:s},a)}function he(e,t,r,n){return M(e,32|t),e.finishNode({type:"ExpressionStatement",expression:r},n)}function me(e,t,r,n,o,a,i,s,c,l,u){$(e,t,0,c,1),function(e,t,r){let n=t;for(;n;)n["$"+r]&&e.report(136,r),n=n.$;t["$"+r]=1}(e,a,i),j(e,32|t);const p=!l||1&t||!e.options.webcompat||86104!==e.getToken()?fe(e,t,r,n,o,a,l):nt(e,t,r?.createChildScope(),n,o,0,0,0,e.tokenStart);return e.finishNode({type:"LabeledStatement",label:s,body:p},u)}function be(e,t,r,n,o,a,i){const{tokenValue:s,tokenStart:c}=e,l=e.getToken();let u=tt(e,t);if(21===e.getToken())return me(e,t,r,n,o,a,s,u,l,1,c);const p=1&e.flags;if(!p){if(86104===e.getToken())return i||e.report(123),nt(e,t,r,n,o,1,0,1,c);if(Q(t,e.getToken()))return u=mt(e,t,n,1,c),18===e.getToken()&&(u=Ve(e,t,n,0,c,u)),he(e,t,u,c)}return 67174411===e.getToken()?u=bt(e,t,n,u,1,1,0,p,c):(10===e.getToken()&&(ee(e,t,l),36864&~l||(e.flags|=256),u=dt(e,2048|t,n,e.tokenValue,u,0,1,0,c)),e.assignable=1),u=je(e,t,n,u,0,0,c),u=Re(e,t,n,0,0,c,u),e.assignable=1,18===e.getToken()&&(u=Ve(e,t,n,0,c,u)),he(e,t,u,c)}function Te(e,t,r,n,o){const a=e.startIndex;1074790417!==n&&(e.assignable=2,r=je(e,t,void 0,r,0,0,o),1074790417!==e.getToken()&&(r=Re(e,t,void 0,0,0,o,r),18===e.getToken()&&(r=Ve(e,t,void 0,0,o,r))),M(e,32|t));const i={type:"ExpressionStatement",expression:r};return"Literal"===r.type&&"string"==typeof r.value&&(i.directive=e.source.slice(o.index+1,a-1)),e.finishNode(i,o)}function ye(e,t,r,n,o){const{tokenStart:a}=e;return 1&t||!e.options.webcompat||86104!==e.getToken()?fe(e,t,r,n,0,{$:o},0):nt(e,t,r?.createChildScope(),n,0,0,0,0,a)}function xe(e,t,r,n,o){return fe(e,131072^(131072|t)|128,r,n,0,{loop:1,$:o},0)}function we(e,t,r,n,o,a){const i=e.tokenStart;j(e,t);const s=ve(e,t,r,n,o,a);return M(e,32|t),e.finishNode({type:"VariableDeclaration",kind:8&o?"let":"const",declarations:s},i)}function Se(e,t,r,n,o){const a=e.tokenStart;j(e,t);const i=ve(e,t,r,n,4,o);return M(e,32|t),e.finishNode({type:"VariableDeclaration",kind:"var",declarations:i},a)}function ve(e,t,r,n,o,a){let i=1;const s=[Ce(e,t,r,n,o,a)];for(;z(e,t,18);)i++,s.push(Ce(e,t,r,n,o,a));return i>1&&32&a&&262144&e.getToken()&&e.report(61,x[255&e.getToken()]),s}function Ce(e,t,r,n,o,a){const{tokenStart:i}=e,s=e.getToken();let c=null;const l=qt(e,t,r,n,o,a);if(1077936155===e.getToken()){if(j(e,32|t),c=Ie(e,t,n,1,0,e.tokenStart),(32&a||!(2097152&s))&&(471156===e.getToken()||8673330===e.getToken()&&(2097152&s||!(4&o)||1&t)))throw new T(i,e.currentLocation,60,471156===e.getToken()?"of":"in")}else(16&o||(2097152&s)>0)&&262144&~e.getToken()&&e.report(59,16&o?"const":"destructuring");return e.finishNode({type:"VariableDeclarator",id:l,init:c},i)}function qe(e,t,r){return Q(t,e.getToken())||e.report(118),537079808&~e.getToken()||e.report(119),r?.addBlockName(t,e.tokenValue,8,0),tt(e,t)}function Ee(e,t,r){const{tokenStart:n}=e;if(j(e,t),X(e,t,77932),!(134217728&~e.getToken()))throw new T(n,e.currentLocation,30,x[255&e.getToken()]);return e.finishNode({type:"ImportNamespaceSpecifier",local:qe(e,t,r)},n)}function Ne(e,t,r,n){for(j(e,t);143360&e.getToken()||134283267===e.getToken();){let{tokenValue:o,tokenStart:a}=e;const i=e.getToken(),s=$e(e,t);let c;z(e,t,77932)?(134217728&~e.getToken()&&18!==e.getToken()?$(e,t,16,e.getToken(),0):e.report(106),o=e.tokenValue,c=tt(e,t)):"Identifier"===s.type?($(e,t,16,i,0),c=e.cloneIdentifier(s)):e.report(25,x[108]),r?.addBlockName(t,o,8,0),n.push(e.finishNode({type:"ImportSpecifier",local:c,imported:s},a)),1074790415!==e.getToken()&&X(e,t,18)}return X(e,t,1074790415),n}function Le(e,t,r){let n=He(e,t,e.finishNode({type:"Identifier",name:"import"},r),r);return n=je(e,t,void 0,n,0,0,r),n=Re(e,t,void 0,0,0,r,n),18===e.getToken()&&(n=Ve(e,t,void 0,0,r,n)),he(e,t,n,r)}function Ae(e,t,r,n){let o=Je(e,t,r,0,n);return o=je(e,t,r,o,0,0,n),18===e.getToken()&&(o=Ve(e,t,r,0,n,o)),he(e,t,o,n)}function Ie(e,t,r,n,o,a){let i=Me(e,t,r,2,0,n,o,1,a);return i=je(e,t,r,i,o,0,a),Re(e,t,r,o,0,a,i)}function Ve(e,t,r,n,o,a){const i=[a];for(;z(e,32|t,18);)i.push(Ie(e,t,r,1,n,e.tokenStart));return e.finishNode({type:"SequenceExpression",expressions:i},o)}function De(e,t,r,n,o,a){const i=Ie(e,t,r,o,n,a);return 18===e.getToken()?Ve(e,t,r,n,a,i):i}function Re(e,t,r,n,o,a,i){const s=e.getToken();if(!(4194304&~s)){2&e.assignable&&e.report(26),(!o&&1077936155===s&&"ArrayExpression"===i.type||"ObjectExpression"===i.type)&&_(e,i),j(e,32|t);const c=Ie(e,t,r,1,n,e.tokenStart);return e.assignable=2,e.finishNode(o?{type:"AssignmentPattern",left:i,right:c}:{type:"AssignmentExpression",left:i,operator:x[255&s],right:c},a)}return 8388608&~s||(i=Pe(e,t,r,n,a,4,s,i)),z(e,32|t,22)&&(i=Ue(e,t,r,i,a)),i}function Be(e,t,r,n,o,a,i){const s=e.getToken();j(e,32|t);const c=Ie(e,t,r,1,n,e.tokenStart);return i=e.finishNode(o?{type:"AssignmentPattern",left:i,right:c}:{type:"AssignmentExpression",left:i,operator:x[255&s],right:c},a),e.assignable=2,i}function Ue(e,t,r,n,o){const a=Ie(e,131072^(131072|t),r,1,0,e.tokenStart);X(e,32|t,21),e.assignable=1;const i=Ie(e,t,r,1,0,e.tokenStart);return e.assignable=2,e.finishNode({type:"ConditionalExpression",test:n,consequent:a,alternate:i},o)}function Pe(e,t,r,n,o,a,i,s){const c=8673330&-((131072&t)>0);let l,u;for(e.assignable=2;8388608&e.getToken()&&(l=e.getToken(),u=3840&l,(524288&l&&268435456&i||524288&i&&268435456&l)&&e.report(165),!(u+((8391735===l)<<8)-((c===l)<<12)<=a));)j(e,32|t),s=e.finishNode({type:524288&l||268435456&l?"LogicalExpression":"BinaryExpression",left:s,right:Pe(e,t,r,n,e.tokenStart,u,l,Ge(e,t,r,0,n,1)),operator:x[255&l]},o);return 1077936155===e.getToken()&&e.report(26),s}function Oe(e,t,r,n,o,a,i){const{tokenStart:s}=e;X(e,32|t,2162700);const c=[];if(1074790415!==e.getToken()){for(;134283267===e.getToken();){const{index:r,tokenStart:n,tokenIndex:o,tokenValue:a}=e,s=e.getToken(),l=rt(e,t);if(H(e,r,o,a)){if(t|=1,128&e.flags)throw new T(n,e.currentLocation,66);if(64&e.flags)throw new T(n,e.currentLocation,9);if(4096&e.flags)throw new T(n,e.currentLocation,15);i?.reportScopeError()}c.push(Te(e,t,l,s,n))}1&t&&(a&&(537079808&~a||e.report(119),36864&~a||e.report(40)),512&e.flags&&e.report(119),256&e.flags&&e.report(118))}for(e.flags=4928^(4928|e.flags),e.destructible=256^(256|e.destructible);1074790415!==e.getToken();)c.push(ge(e,t,r,n,4,{}));return X(e,24&o?32|t:t,1074790415),e.flags&=-4289,1077936155===e.getToken()&&e.report(26),e.finishNode({type:"BlockStatement",body:c},s)}function Ge(e,t,r,n,o,a){const i=e.tokenStart;return je(e,t,r,Me(e,t,r,2,0,n,o,a,i),o,0,i)}function je(e,t,r,n,o,a,i){if(33619968&~e.getToken()||1&e.flags){if(!(67108864&~e.getToken())){switch(t=131072^(131072|t),e.getToken()){case 67108877:{j(e,8^(262152|t)),16&t&&130===e.getToken()&&"super"===e.tokenValue&&e.report(173),e.assignable=1;const o=Fe(e,64|t,r);n=e.finishNode({type:"MemberExpression",object:n,computed:!1,property:o,optional:!1},i);break}case 69271571:{let a=!1;2048&~e.flags||(a=!0,e.flags=2048^(2048|e.flags)),j(e,32|t);const{tokenStart:s}=e,c=De(e,t,r,o,1,s);X(e,t,20),e.assignable=1,n=e.finishNode({type:"MemberExpression",object:n,computed:!0,property:c,optional:!1},i),a&&(e.flags|=2048);break}case 67174411:{if(!(1024&~e.flags))return e.flags=1024^(1024|e.flags),n;let a=!1;2048&~e.flags||(a=!0,e.flags=2048^(2048|e.flags));const s=et(e,t,r,o);e.assignable=2,n=e.finishNode({type:"CallExpression",callee:n,arguments:s,optional:!1},i),a&&(e.flags|=2048);break}case 67108990:j(e,8^(262152|t)),e.flags|=2048,e.assignable=2,n=function(e,t,r,n,o){let a,i=!1;69271571!==e.getToken()&&67174411!==e.getToken()||2048&~e.flags||(i=!0,e.flags=2048^(2048|e.flags));if(69271571===e.getToken()){j(e,32|t);const{tokenStart:i}=e,s=De(e,t,r,0,1,i);X(e,t,20),e.assignable=2,a=e.finishNode({type:"MemberExpression",object:n,computed:!0,optional:!0,property:s},o)}else if(67174411===e.getToken()){const i=et(e,t,r,0);e.assignable=2,a=e.finishNode({type:"CallExpression",callee:n,arguments:i,optional:!0},o)}else{const i=Fe(e,t,r);e.assignable=2,a=e.finishNode({type:"MemberExpression",object:n,computed:!1,optional:!0,property:i},o)}i&&(e.flags|=2048);return a}(e,t,r,n,i);break;default:2048&~e.flags||e.report(166),e.assignable=2,n=e.finishNode({type:"TaggedTemplateExpression",tag:n,quasi:67174408===e.getToken()?Ze(e,64|t,r):We(e,t)},i)}n=je(e,t,r,n,0,1,i)}}else n=function(e,t,r,n){2&e.assignable&&e.report(55);const o=e.getToken();return j(e,t),e.assignable=2,e.finishNode({type:"UpdateExpression",argument:r,operator:x[255&o],prefix:!1},n)}(e,t,n,i);return 0!==a||2048&~e.flags||(e.flags=2048^(2048|e.flags),n=e.finishNode({type:"ChainExpression",expression:n},i)),n}function Fe(e,t,r){return 143360&e.getToken()||-2147483528===e.getToken()||-2147483527===e.getToken()||130===e.getToken()||e.report(160),130===e.getToken()?vt(e,t,r,0):tt(e,t)}function Me(e,t,r,n,o,a,i,s,c){if(!(143360&~e.getToken())){switch(e.getToken()){case 209006:return function(e,t,r,n,o,a){o&&(e.destructible|=128),524288&t&&e.report(177);const i=pt(e,t,r);if("ArrowFunctionExpression"===i.type||!(65536&e.getToken())){if(2048&t)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},176);if(2&t)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},110);if(8192&t&&2048&t)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},110);return i}if(8192&t)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},31);if(2048&t||2&t&&8&t){if(n)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},0);const o=Ge(e,t,r,0,0,1);return 8391735===e.getToken()&&e.report(33),e.assignable=2,e.finishNode({type:"AwaitExpression",argument:o},a)}if(2&t)throw new T(a,{index:e.startIndex,line:e.startLine,column:e.startColumn},98);return i}(e,t,r,o,i,c);case 241771:return function(e,t,r,n,o,a){if(n&&(e.destructible|=256),1024&t){j(e,32|t),8192&t&&e.report(32),o||e.report(26),22===e.getToken()&&e.report(124);let n=null,i=!1;return 1&e.flags?8391476===e.getToken()&&e.report(30,x[255&e.getToken()]):(i=z(e,32|t,8391476),(77824&e.getToken()||i)&&(n=Ie(e,t,r,1,0,e.tokenStart))),e.assignable=2,e.finishNode({type:"YieldExpression",argument:n,delegate:i},a)}return 1&t&&e.report(97,"yield"),pt(e,t,r)}(e,t,r,i,a,c);case 209005:return function(e,t,r,n,o,a,i,s){const c=e.getToken(),l=tt(e,t),{flags:u}=e;if(!(1&u)){if(86104===e.getToken())return ot(e,t,r,1,n,s);if(Q(t,e.getToken()))return o||e.report(0),36864&~e.getToken()||(e.flags|=256),mt(e,t,r,a,s)}return i||67174411!==e.getToken()?10===e.getToken()?(ee(e,t,c),i&&e.report(51),36864&~c||(e.flags|=256),dt(e,t,r,e.tokenValue,l,i,a,0,s)):(e.assignable=1,l):bt(e,t,r,l,a,1,0,u,s)}(e,t,r,i,s,a,o,c)}const{tokenValue:l}=e,u=e.getToken(),p=tt(e,64|t);return 10===e.getToken()?(s||e.report(0),ee(e,t,u),36864&~u||(e.flags|=256),dt(e,t,r,l,p,o,a,0,c)):(!(16&t)||32768&t||8192&t||"arguments"!==e.tokenValue||e.report(130),73==(255&u)&&(1&t&&e.report(113),24&n&&e.report(100)),e.assignable=1&t&&!(537079808&~u)?2:1,p)}if(!(134217728&~e.getToken()))return rt(e,t);switch(e.getToken()){case 33619993:case 33619994:return function(e,t,r,n,o,a){n&&e.report(56),o||e.report(0);const i=e.getToken();j(e,32|t);const s=Ge(e,t,r,0,0,1);return 2&e.assignable&&e.report(55),e.assignable=2,e.finishNode({type:"UpdateExpression",argument:s,operator:x[255&i],prefix:!0},a)}(e,t,r,o,s,c);case 16863276:case 16842798:case 16842799:case 25233968:case 25233969:case 16863275:case 16863277:return function(e,t,r,n,o){n||e.report(0);const{tokenStart:a}=e,i=e.getToken();j(e,32|t);const s=Ge(e,t,r,0,o,1);var c;return 8391735===e.getToken()&&e.report(33),1&t&&16863276===i&&("Identifier"===s.type?e.report(121):(c=s).property&&"PrivateIdentifier"===c.property.type&&e.report(127)),e.assignable=2,e.finishNode({type:"UnaryExpression",operator:x[255&i],argument:s,prefix:!0},a)}(e,t,r,s,i);case 86104:return ot(e,t,r,0,i,c);case 2162700:return function(e,t,r,n,o){const a=lt(e,t,void 0,r,n,o,0,2,0);64&e.destructible&&e.report(63);8&e.destructible&&e.report(62);return a}(e,t,r,a?0:1,i);case 69271571:return function(e,t,r,n,o){const a=at(e,t,void 0,r,n,o,0,2,0);64&e.destructible&&e.report(63);8&e.destructible&&e.report(62);return a}(e,t,r,a?0:1,i);case 67174411:return function(e,t,r,n,o,a,i){e.flags=128^(128|e.flags);const s=e.tokenStart;j(e,262176|t);const c=e.createScopeIfLexical()?.createChildScope(512);if(t=131072^(131072|t),z(e,t,16))return gt(e,t,c,r,[],n,0,i);let l,u=0;e.destructible&=-385;let p=[],d=0,g=0,f=0;const k=e.tokenStart;e.assignable=1;for(;16!==e.getToken();){const{tokenStart:n}=e,i=e.getToken();if(143360&i)c?.addBlockName(t,e.tokenValue,1,0),537079808&~i?36864&~i||(f=1):g=1,l=Me(e,t,r,o,0,1,1,1,n),16===e.getToken()||18===e.getToken()?2&e.assignable&&(u|=16,g=1):(1077936155===e.getToken()?g=1:u|=16,l=je(e,t,r,l,1,0,n),16!==e.getToken()&&18!==e.getToken()&&(l=Re(e,t,r,1,0,n,l)));else{if(2097152&~i){if(14===i){l=st(e,t,c,r,16,o,a,0,1,0),16&e.destructible&&e.report(74),g=1,!d||16!==e.getToken()&&18!==e.getToken()||p.push(l),u|=8;break}if(u|=16,l=Ie(e,t,r,1,1,n),!d||16!==e.getToken()&&18!==e.getToken()||p.push(l),18===e.getToken()&&(d||(d=1,p=[l])),d){for(;z(e,32|t,18);)p.push(Ie(e,t,r,1,1,e.tokenStart));e.assignable=2,l=e.finishNode({type:"SequenceExpression",expressions:p},k)}return X(e,t,16),e.destructible=u,e.options.preserveParens?e.finishNode({type:"ParenthesizedExpression",expression:l},s):l}l=2162700===i?lt(e,262144|t,c,r,0,1,0,o,a):at(e,262144|t,c,r,0,1,0,o,a),u|=e.destructible,g=1,e.assignable=2,16!==e.getToken()&&18!==e.getToken()&&(8&u&&e.report(122),l=je(e,t,r,l,0,0,n),u|=16,16!==e.getToken()&&18!==e.getToken()&&(l=Re(e,t,r,0,0,n,l)))}if(!d||16!==e.getToken()&&18!==e.getToken()||p.push(l),!z(e,32|t,18))break;if(d||(d=1,p=[l]),16===e.getToken()){u|=8;break}}d&&(e.assignable=2,l=e.finishNode({type:"SequenceExpression",expressions:p},k));X(e,t,16),16&u&&8&u&&e.report(151);if(u|=256&e.destructible?256:128&e.destructible?128:0,10===e.getToken())return 48&u&&e.report(49),2050&t&&128&u&&e.report(31),1025&t&&256&u&&e.report(32),g&&(e.flags|=128),f&&(e.flags|=256),gt(e,t,c,r,d?p:[l],n,0,i);64&u&&e.report(63);8&u&&e.report(144);return e.destructible=256^(256|e.destructible)|u,e.options.preserveParens?e.finishNode({type:"ParenthesizedExpression",expression:l},s):l}(e,64|t,r,a,1,0,c);case 86021:case 86022:case 86023:return function(e,t){const r=e.tokenStart,n=x[255&e.getToken()],o=86023===e.getToken()?null:"true"===n,a={type:"Literal",value:o};e.options.raw&&(a.raw=n);return j(e,t),e.assignable=2,e.finishNode(a,r)}(e,t);case 86111:return function(e,t){const{tokenStart:r}=e;return j(e,t),e.assignable=2,e.finishNode({type:"ThisExpression"},r)}(e,t);case 65540:return function(e,t){const{tokenRaw:r,tokenRegExp:n,tokenValue:o,tokenStart:a}=e;j(e,t),e.assignable=2;const i={type:"Literal",value:o,regex:n};e.options.raw&&(i.raw=r);return e.finishNode(i,a)}(e,t);case 132:case 86094:return function(e,t,r,n,o){let a=null,i=null;const s=yt(e,t,r);t=16384^(16385|t),j(e,t),4096&e.getToken()&&20565!==e.getToken()&&(W(e,t,e.getToken())&&e.report(118),537079808&~e.getToken()||e.report(119),a=tt(e,t));let c=t;z(e,32|t,20565)?(i=Ge(e,t,r,0,n,0),c|=512):c=512^(512|c);const l=wt(e,c,t,void 0,r,2,0,n);return e.assignable=2,e.finishNode({type:"ClassExpression",id:a,superClass:i,body:l,...e.options.next?{decorators:s}:null},o)}(e,t,r,i,c);case 86109:return function(e,t){const{tokenStart:r}=e;switch(j(e,t),e.getToken()){case 67108990:e.report(167);case 67174411:512&t||e.report(28),e.assignable=2;break;case 69271571:case 67108877:256&t||e.report(29),e.assignable=1;break;default:e.report(30,"super")}return e.finishNode({type:"Super"},r)}(e,t);case 67174409:return We(e,t);case 67174408:return Ze(e,t,r);case 86107:return function(e,t,r,n){const{tokenStart:o}=e,a=tt(e,32|t),{tokenStart:i}=e;if(z(e,t,67108877)){if(65536&t&&209029===e.getToken())return e.assignable=2,function(e,t,r,n){const o=tt(e,t);return e.finishNode({type:"MetaProperty",meta:r,property:o},n)}(e,t,a,o);e.report(94)}e.assignable=2,16842752&~e.getToken()||e.report(65,x[255&e.getToken()]);const s=Me(e,t,r,2,1,0,n,1,i);t=131072^(131072|t),67108990===e.getToken()&&e.report(168);const c=ht(e,t,r,s,n,i);return e.assignable=2,e.finishNode({type:"NewExpression",callee:c,arguments:67174411===e.getToken()?et(e,t,r,n):[]},o)}(e,t,r,i);case 134283388:return Ye(e,t);case 130:return vt(e,t,r,0);case 86106:return function(e,t,r,n,o,a){let i=tt(e,t);if(67108877===e.getToken())return He(e,t,i,a);n&&e.report(142);return i=Je(e,t,r,o,a),e.assignable=2,je(e,t,r,i,o,0,a)}(e,t,r,o,i,c);case 8456256:if(e.options.jsx)return Nt(e,t,r,0,e.tokenStart);default:if(Q(t,e.getToken()))return pt(e,t,r);e.report(30,x[255&e.getToken()])}}function He(e,t,r,n){2&t||e.report(169),j(e,t);const o=e.getToken();return 209030!==o&&"meta"!==e.tokenValue?e.report(174):-2147483648&o&&e.report(175),e.assignable=2,e.finishNode({type:"MetaProperty",meta:r,property:tt(e,t)},n)}function Je(e,t,r,n,o){X(e,32|t,67174411),14===e.getToken()&&e.report(143);const a=Ie(e,t,r,1,n,e.tokenStart);let i=null;if(18===e.getToken()){if(X(e,t,18),16!==e.getToken()){i=Ie(e,131072^(131072|t),r,1,n,e.tokenStart)}z(e,t,18)}const s={type:"ImportExpression",source:a,options:i};return X(e,t,16),e.finishNode(s,o)}function ze(e,t){if(!z(e,t,20579))return[];X(e,t,2162700);const r=[],n=new Set;for(;1074790415!==e.getToken();){const o=e.tokenStart,a=_e(e,t);X(e,t,21);const i=Xe(e,t),s="Literal"===a.type?a.value:a.name;n.has(s)&&e.report(145,`${s}`),n.add(s),r.push(e.finishNode({type:"ImportAttribute",key:a,value:i},o)),1074790415!==e.getToken()&&X(e,t,18)}return X(e,t,1074790415),r}function Xe(e,t){if(134283267===e.getToken())return rt(e,t);e.report(30,x[255&e.getToken()])}function _e(e,t){return 134283267===e.getToken()?rt(e,t):143360&e.getToken()?tt(e,t):void e.report(30,x[255&e.getToken()])}function $e(e,t){if(134283267===e.getToken()){return e.tokenValue.isWellFormed()||e.report(171),rt(e,t)}if(143360&e.getToken())return tt(e,t);e.report(30,x[255&e.getToken()])}function Ye(e,t){const{tokenRaw:r,tokenValue:n,tokenStart:o}=e;j(e,t),e.assignable=2;const a={type:"Literal",value:n,bigint:String(n)};return e.options.raw&&(a.raw=r),e.finishNode(a,o)}function We(e,t){e.assignable=2;const{tokenValue:r,tokenRaw:n,tokenStart:o}=e;X(e,t,67174409);const a=[Ke(e,r,n,o,!0)];return e.finishNode({type:"TemplateLiteral",expressions:[],quasis:a},o)}function Ze(e,t,r){t=131072^(131072|t);const{tokenValue:n,tokenRaw:o,tokenStart:a}=e;X(e,-65&t|32,67174408);const i=[Ke(e,n,o,a,!1)],s=[De(e,-65&t,r,0,1,e.tokenStart)];for(1074790415!==e.getToken()&&e.report(83);67174409!==e.setToken(O(e,t),!0);){const{tokenValue:n,tokenRaw:o,tokenStart:a}=e;X(e,-65&t|32,67174408),i.push(Ke(e,n,o,a,!1)),s.push(De(e,t,r,0,1,e.tokenStart)),1074790415!==e.getToken()&&e.report(83)}{const{tokenValue:r,tokenRaw:n,tokenStart:o}=e;X(e,t,67174409),i.push(Ke(e,r,n,o,!0))}return e.finishNode({type:"TemplateLiteral",expressions:s,quasis:i},a)}function Ke(e,t,r,n,o){const a=e.finishNode({type:"TemplateElement",value:{cooked:t,raw:r},tail:o},n),i=o?1:2;return e.options.ranges&&(a.start+=1,a.range[0]+=1,a.end-=i,a.range[1]-=i),e.options.loc&&(a.loc.start.column+=1,a.loc.end.column-=i),a}function Qe(e,t,r){const n=e.tokenStart;X(e,32|(t=131072^(131072|t)),14);const o=Ie(e,t,r,1,0,e.tokenStart);return e.assignable=1,e.finishNode({type:"SpreadElement",argument:o},n)}function et(e,t,r,n){j(e,32|t);const o=[];if(16===e.getToken())return j(e,64|t),o;for(;16!==e.getToken()&&(14===e.getToken()?o.push(Qe(e,t,r)):o.push(Ie(e,t,r,1,n,e.tokenStart)),18===e.getToken())&&(j(e,32|t),16!==e.getToken()););return X(e,64|t,16),o}function tt(e,t){const{tokenValue:r,tokenStart:n}=e,o="await"===r&&!(-2147483648&e.getToken());return j(e,t|(o?32:0)),e.finishNode({type:"Identifier",name:r},n)}function rt(e,t){const{tokenValue:r,tokenRaw:n,tokenStart:o}=e;if(134283388===e.getToken())return Ye(e,t);const a={type:"Literal",value:r};return e.options.raw&&(a.raw=n),j(e,t),e.assignable=2,e.finishNode(a,o)}function nt(e,t,r,n,o,a,i,s,c){j(e,32|t);const l=a?J(e,t,8391476):0;let u,p=null,d=r?e.createScope():void 0;if(67174411===e.getToken())1&i||e.report(39,"Function");else{const n=!(4&o)||8&t&&2&t?64|(s?1024:0)|(l?1024:0):4;Y(e,t,e.getToken()),r&&(4&n?r.addVarName(t,e.tokenValue,n):r.addBlockName(t,e.tokenValue,n,o),d=d?.createChildScope(128),i&&2&i&&e.declareUnboundVariable(e.tokenValue)),u=e.getToken(),143360&e.getToken()?p=tt(e,t):e.report(30,x[255&e.getToken()])}{const e=28416;t=(t|e)^e|65536|(s?2048:0)|(l?1024:0)|(l?0:262144)}d=d?.createChildScope(256);const g=kt(e,-524289&t|8192,d,n,0,1),f=524428,k=Oe(e,36864|(t|f)^f,d?.createChildScope(64),n,8,u,d);return e.finishNode({type:"FunctionDeclaration",id:p,params:g,body:k,async:1===s,generator:1===l},c)}function ot(e,t,r,n,o,a){j(e,32|t);const i=J(e,t,8391476),s=(n?2048:0)|(i?1024:0);let c,l=null,u=e.createScopeIfLexical();const p=552704;143360&e.getToken()&&(Y(e,(t|p)^p|s,e.getToken()),u=u?.createChildScope(128),c=e.getToken(),l=tt(e,t)),t=(t|p)^p|65536|s|(i?0:262144),u=u?.createChildScope(256);const d=kt(e,-524289&t|8192,u,r,o,1),g=Oe(e,36864|-131229&t,u?.createChildScope(64),r,0,c,u);return e.assignable=2,e.finishNode({type:"FunctionExpression",id:l,params:d,body:g,async:1===n,generator:1===i},a)}function at(e,t,r,n,o,a,i,s,c){const{tokenStart:l}=e;j(e,32|t);const u=[];let p=0;for(t=131072^(131072|t);20!==e.getToken();)if(z(e,32|t,18))u.push(null);else{let o;const{tokenStart:l,tokenValue:d}=e,g=e.getToken();if(143360&g)if(o=Me(e,t,n,s,0,1,a,1,l),1077936155===e.getToken()){2&e.assignable&&e.report(26),j(e,32|t),r?.addVarOrBlock(t,d,s,c);const u=Ie(e,t,n,1,a,e.tokenStart);o=e.finishNode(i?{type:"AssignmentPattern",left:o,right:u}:{type:"AssignmentExpression",operator:"=",left:o,right:u},l),p|=256&e.destructible?256:128&e.destructible?128:0}else 18===e.getToken()||20===e.getToken()?(2&e.assignable?p|=16:r?.addVarOrBlock(t,d,s,c),p|=256&e.destructible?256:128&e.destructible?128:0):(p|=1&s?32:2&s?0:16,o=je(e,t,n,o,a,0,l),18!==e.getToken()&&20!==e.getToken()?(1077936155!==e.getToken()&&(p|=16),o=Re(e,t,n,a,i,l,o)):1077936155!==e.getToken()&&(p|=2&e.assignable?16:32));else 2097152&g?(o=2162700===e.getToken()?lt(e,t,r,n,0,a,i,s,c):at(e,t,r,n,0,a,i,s,c),p|=e.destructible,e.assignable=16&e.destructible?2:1,18===e.getToken()||20===e.getToken()?2&e.assignable&&(p|=16):8&e.destructible?e.report(71):(o=je(e,t,n,o,a,0,l),p=2&e.assignable?16:0,18!==e.getToken()&&20!==e.getToken()?o=Re(e,t,n,a,i,l,o):1077936155!==e.getToken()&&(p|=2&e.assignable?16:32))):14===g?(o=st(e,t,r,n,20,s,c,0,a,i),p|=e.destructible,18!==e.getToken()&&20!==e.getToken()&&e.report(30,x[255&e.getToken()])):(o=Ge(e,t,n,1,0,1),18!==e.getToken()&&20!==e.getToken()?(o=Re(e,t,n,a,i,l,o),3&s||67174411!==g||(p|=16)):2&e.assignable?p|=16:67174411===g&&(p|=1&e.assignable&&3&s?32:16));if(u.push(o),!z(e,32|t,18))break;if(20===e.getToken())break}X(e,t,20);const d=e.finishNode({type:i?"ArrayPattern":"ArrayExpression",elements:u},l);return!o&&4194304&e.getToken()?it(e,t,n,p,a,i,l,d):(e.destructible=p,d)}function it(e,t,r,n,o,a,i,s){1077936155!==e.getToken()&&e.report(26),j(e,32|t),16&n&&e.report(26),a||_(e,s);const{tokenStart:c}=e,l=Ie(e,t,r,1,o,c);return e.destructible=72^(72|n)|(128&e.destructible?128:0)|(256&e.destructible?256:0),e.finishNode(a?{type:"AssignmentPattern",left:s,right:l}:{type:"AssignmentExpression",left:s,operator:"=",right:l},i)}function st(e,t,r,n,o,a,i,s,c,l){const{tokenStart:u}=e;j(e,32|t);let p=null,d=0;const{tokenValue:g,tokenStart:f}=e;let k=e.getToken();if(143360&k)e.assignable=1,p=Me(e,t,n,a,0,1,c,1,f),k=e.getToken(),p=je(e,t,n,p,c,0,f),18!==e.getToken()&&e.getToken()!==o&&(2&e.assignable&&1077936155===e.getToken()&&e.report(71),d|=16,p=Re(e,t,n,c,l,f,p)),2&e.assignable?d|=16:k===o||18===k?r?.addVarOrBlock(t,g,a,i):d|=32,d|=128&e.destructible?128:0;else if(k===o)e.report(41);else{if(!(2097152&k)){d|=32,p=Ge(e,t,n,1,c,1);const{tokenStart:r}=e,a=e.getToken();return 1077936155===a?(2&e.assignable&&e.report(26),p=Re(e,t,n,c,l,r,p),d|=16):(18===a?d|=16:a!==o&&(p=Re(e,t,n,c,l,r,p)),d|=1&e.assignable?32:16),e.destructible=d,e.getToken()!==o&&18!==e.getToken()&&e.report(161),e.finishNode({type:l?"RestElement":"SpreadElement",argument:p},u)}p=2162700===e.getToken()?lt(e,t,r,n,1,c,l,a,i):at(e,t,r,n,1,c,l,a,i),k=e.getToken(),1077936155!==k&&k!==o&&18!==k?(8&e.destructible&&e.report(71),p=je(e,t,n,p,c,0,f),d|=2&e.assignable?16:0,4194304&~e.getToken()?(8388608&~e.getToken()||(p=Pe(e,t,n,1,f,4,k,p)),z(e,32|t,22)&&(p=Ue(e,t,n,p,f)),d|=2&e.assignable?16:32):(1077936155!==e.getToken()&&(d|=16),p=Re(e,t,n,c,l,f,p))):d|=1074790415===o&&1077936155!==k?16:e.destructible}if(e.getToken()!==o)if(1&a&&(d|=s?16:32),z(e,32|t,1077936155)){16&d&&e.report(26),_(e,p);const r=Ie(e,t,n,1,c,e.tokenStart);p=e.finishNode(l?{type:"AssignmentPattern",left:p,right:r}:{type:"AssignmentExpression",left:p,operator:"=",right:r},f),d=16}else d|=16;return e.destructible=d,e.finishNode({type:l?"RestElement":"SpreadElement",argument:p},u)}function ct(e,t,r,n,o,a){const i=11264|(64&n?0:16896);t=98560|((t|i)^i|(8&n?1024:0)|(16&n?2048:0)|(64&n?16384:0));let s=e.createScopeIfLexical(256);const c=function(e,t,r,n,o,a,i){X(e,t,67174411);const s=[];if(e.flags=128^(128|e.flags),16===e.getToken())return 512&o&&e.report(37,"Setter","one",""),j(e,t),s;256&o&&e.report(37,"Getter","no","s");512&o&&14===e.getToken()&&e.report(38);t=131072^(131072|t);let c=0,l=0;for(;18!==e.getToken();){let u=null;const{tokenStart:p}=e;if(143360&e.getToken()?(1&t||(36864&~e.getToken()||(e.flags|=256),537079808&~e.getToken()||(e.flags|=512)),u=Et(e,t,r,1|o,0)):(2162700===e.getToken()?u=lt(e,t,r,n,1,i,1,a,0):69271571===e.getToken()?u=at(e,t,r,n,1,i,1,a,0):14===e.getToken()&&(u=st(e,t,r,n,16,a,0,0,i,1)),l=1,48&e.destructible&&e.report(50)),1077936155===e.getToken()){j(e,32|t),l=1;const r=Ie(e,t,n,1,0,e.tokenStart);u=e.finishNode({type:"AssignmentPattern",left:u,right:r},p)}if(c++,s.push(u),!z(e,t,18))break;if(16===e.getToken())break}512&o&&1!==c&&e.report(37,"Setter","one","");r?.reportScopeError(),l&&(e.flags|=128);return X(e,t,16),s}(e,-524289&t|8192,s,r,n,1,o);s=s?.createChildScope(64);const l=Oe(e,36864|-655373&t,s,r,0,void 0,s?.parent);return e.finishNode({type:"FunctionExpression",params:c,body:l,async:(16&n)>0,generator:(8&n)>0,id:null},a)}function lt(e,t,r,n,o,a,i,s,c){const{tokenStart:l}=e;j(e,t);const u=[];let p=0,d=0;for(t=131072^(131072|t);1074790415!==e.getToken();){const{tokenValue:o,tokenStart:l}=e,g=e.getToken();if(14===g)u.push(st(e,t,r,n,1074790415,s,c,0,a,i));else{let f,k=0,h=null;if(143360&e.getToken()||-2147483528===e.getToken()||-2147483527===e.getToken())if(-2147483527===e.getToken()&&(p|=16),h=tt(e,t),18===e.getToken()||1074790415===e.getToken()||1077936155===e.getToken())if(k|=4,1&t&&!(537079808&~g)?p|=16:$(e,t,s,g,0),r?.addVarOrBlock(t,o,s,c),z(e,32|t,1077936155)){p|=8;const r=Ie(e,t,n,1,a,e.tokenStart);p|=256&e.destructible?256:128&e.destructible?128:0,f=e.finishNode({type:"AssignmentPattern",left:e.cloneIdentifier(h),right:r},l)}else p|=(209006===g?128:0)|(-2147483528===g?16:0),f=e.cloneIdentifier(h);else if(z(e,32|t,21)){const{tokenStart:l}=e;if("__proto__"===o&&d++,143360&e.getToken()){const o=e.getToken(),u=e.tokenValue;f=Me(e,t,n,s,0,1,a,1,l);const d=e.getToken();f=je(e,t,n,f,a,0,l),18===e.getToken()||1074790415===e.getToken()?1077936155===d||1074790415===d||18===d?(p|=128&e.destructible?128:0,2&e.assignable?p|=16:143360&~o||r?.addVarOrBlock(t,u,s,c)):p|=1&e.assignable?32:16:4194304&~e.getToken()?(p|=16,8388608&~e.getToken()||(f=Pe(e,t,n,1,l,4,d,f)),z(e,32|t,22)&&(f=Ue(e,t,n,f,l))):(2&e.assignable?p|=16:1077936155!==d?p|=32:r?.addVarOrBlock(t,u,s,c),f=Re(e,t,n,a,i,l,f))}else 2097152&~e.getToken()?(f=Ge(e,t,n,1,a,1),p|=1&e.assignable?32:16,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):(f=je(e,t,n,f,a,0,l),p=2&e.assignable?16:0,18!==e.getToken()&&1074790415!==g&&(1077936155!==e.getToken()&&(p|=16),f=Re(e,t,n,a,i,l,f)))):(f=69271571===e.getToken()?at(e,t,r,n,0,a,i,s,c):lt(e,t,r,n,0,a,i,s,c),p=e.destructible,e.assignable=16&p?2:1,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):8&e.destructible?e.report(71):(f=je(e,t,n,f,a,0,l),p=2&e.assignable?16:0,4194304&~e.getToken()?(8388608&~e.getToken()||(f=Pe(e,t,n,1,l,4,g,f)),z(e,32|t,22)&&(f=Ue(e,t,n,f,l)),p|=2&e.assignable?16:32):f=Be(e,t,n,a,i,l,f)))}else 69271571===e.getToken()?(p|=16,209005===g&&(k|=16),k|=2|(209008===g?256:209009===g?512:1),h=ut(e,t,n,a),p|=e.assignable,f=ct(e,t,n,k,a,e.tokenStart)):143360&e.getToken()?(p|=16,-2147483528===g&&e.report(95),209005===g?(1&e.flags&&e.report(132),k|=17):209008===g?k|=256:209009===g?k|=512:e.report(0),h=tt(e,t),f=ct(e,t,n,k,a,e.tokenStart)):67174411===e.getToken()?(p|=16,k|=1,f=ct(e,t,n,k,a,e.tokenStart)):8391476===e.getToken()?(p|=16,209008===g?e.report(42):209009===g?e.report(43):209005!==g&&e.report(30,x[52]),j(e,t),k|=9|(209005===g?16:0),143360&e.getToken()?h=tt(e,t):134217728&~e.getToken()?69271571===e.getToken()?(k|=2,h=ut(e,t,n,a),p|=e.assignable):e.report(30,x[255&e.getToken()]):h=rt(e,t),f=ct(e,t,n,k,a,e.tokenStart)):134217728&~e.getToken()?e.report(133):(209005===g&&(k|=16),k|=209008===g?256:209009===g?512:1,p|=16,h=rt(e,t),f=ct(e,t,n,k,a,e.tokenStart));else if(134217728&~e.getToken())if(69271571===e.getToken())if(h=ut(e,t,n,a),p|=256&e.destructible?256:0,k|=2,21===e.getToken()){j(e,32|t);const{tokenStart:o,tokenValue:l}=e,u=e.getToken();if(143360&e.getToken()){f=Me(e,t,n,s,0,1,a,1,o);const d=e.getToken();f=je(e,t,n,f,a,0,o),4194304&~e.getToken()?18===e.getToken()||1074790415===e.getToken()?1077936155===d||1074790415===d||18===d?2&e.assignable?p|=16:143360&~u||r?.addVarOrBlock(t,l,s,c):p|=1&e.assignable?32:16:(p|=16,f=Re(e,t,n,a,i,o,f)):(p|=2&e.assignable?16:1077936155===d?0:32,f=Be(e,t,n,a,i,o,f))}else 2097152&~e.getToken()?(f=Ge(e,t,n,1,0,1),p|=1&e.assignable?32:16,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):(f=je(e,t,n,f,a,0,o),p=1&e.assignable?0:16,18!==e.getToken()&&1074790415!==e.getToken()&&(1077936155!==e.getToken()&&(p|=16),f=Re(e,t,n,a,i,o,f)))):(f=69271571===e.getToken()?at(e,t,r,n,0,a,i,s,c):lt(e,t,r,n,0,a,i,s,c),p=e.destructible,e.assignable=16&p?2:1,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):8&p?e.report(62):(f=je(e,t,n,f,a,0,o),p=2&e.assignable?16|p:0,4194304&~e.getToken()?(8388608&~e.getToken()||(f=Pe(e,t,n,1,o,4,g,f)),z(e,32|t,22)&&(f=Ue(e,t,n,f,o)),p|=2&e.assignable?16:32):(1077936155!==e.getToken()&&(p|=16),f=Be(e,t,n,a,i,o,f))))}else 67174411===e.getToken()?(k|=1,f=ct(e,t,n,k,a,e.tokenStart),p=16):e.report(44);else if(8391476===g)if(X(e,32|t,8391476),k|=8,143360&e.getToken()){const r=e.getToken();if(h=tt(e,t),k|=1,67174411!==e.getToken())throw new T(e.tokenStart,e.currentLocation,209005===r?46:209008===r||209009===e.getToken()?45:47,x[255&r]);p|=16,f=ct(e,t,n,k,a,e.tokenStart)}else 134217728&~e.getToken()?69271571===e.getToken()?(p|=16,k|=3,h=ut(e,t,n,a),f=ct(e,t,n,k,a,e.tokenStart)):e.report(126):(p|=16,h=rt(e,t),k|=1,f=ct(e,t,n,k,a,e.tokenStart));else e.report(30,x[255&g]);else if(h=rt(e,t),21===e.getToken()){X(e,32|t,21);const{tokenStart:l}=e;if("__proto__"===o&&d++,143360&e.getToken()){f=Me(e,t,n,s,0,1,a,1,l);const{tokenValue:o}=e,u=e.getToken();f=je(e,t,n,f,a,0,l),18===e.getToken()||1074790415===e.getToken()?1077936155===u||1074790415===u||18===u?2&e.assignable?p|=16:r?.addVarOrBlock(t,o,s,c):p|=1&e.assignable?32:16:1077936155===e.getToken()?(2&e.assignable&&(p|=16),f=Re(e,t,n,a,i,l,f)):(p|=16,f=Re(e,t,n,a,i,l,f))}else 2097152&~e.getToken()?(f=Ge(e,t,n,1,0,1),p|=1&e.assignable?32:16,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):(f=je(e,t,n,f,a,0,l),p=1&e.assignable?0:16,18!==e.getToken()&&1074790415!==e.getToken()&&(1077936155!==e.getToken()&&(p|=16),f=Re(e,t,n,a,i,l,f)))):(f=69271571===e.getToken()?at(e,t,r,n,0,a,i,s,c):lt(e,t,r,n,0,a,i,s,c),p=e.destructible,e.assignable=16&p?2:1,18===e.getToken()||1074790415===e.getToken()?2&e.assignable&&(p|=16):8&~e.destructible&&(f=je(e,t,n,f,a,0,l),p=2&e.assignable?16:0,4194304&~e.getToken()?(8388608&~e.getToken()||(f=Pe(e,t,n,1,l,4,g,f)),z(e,32|t,22)&&(f=Ue(e,t,n,f,l)),p|=2&e.assignable?16:32):f=Be(e,t,n,a,i,l,f)))}else 67174411===e.getToken()?(k|=1,f=ct(e,t,n,k,a,e.tokenStart),p=16|e.assignable):e.report(134);p|=128&e.destructible?128:0,e.destructible=p,u.push(e.finishNode({type:"Property",key:h,value:f,kind:768&k?512&k?"set":"get":"init",computed:(2&k)>0,method:(1&k)>0,shorthand:(4&k)>0},l))}if(p|=e.destructible,18!==e.getToken())break;j(e,t)}X(e,t,1074790415),d>1&&(p|=64);const g=e.finishNode({type:i?"ObjectPattern":"ObjectExpression",properties:u},l);return!o&&4194304&e.getToken()?it(e,t,n,p,a,i,l,g):(e.destructible=p,g)}function ut(e,t,r,n){j(e,32|t);const o=Ie(e,131072^(131072|t),r,1,n,e.tokenStart);return X(e,t,20),o}function pt(e,t,r){const{tokenStart:n}=e,{tokenValue:o}=e;let a=0,i=0;537079808&~e.getToken()?36864&~e.getToken()||(i=1):a=1;const s=tt(e,t);if(e.assignable=1,10===e.getToken()){const c=e.options.lexical?le(e,t,o):void 0;return a&&(e.flags|=128),i&&(e.flags|=256),ft(e,t,c,r,[s],0,n)}return s}function dt(e,t,r,n,o,a,i,s,c){i||e.report(57),a&&e.report(51),e.flags&=-129;return ft(e,t,e.options.lexical?le(e,t,n):void 0,r,[o],s,c)}function gt(e,t,r,n,o,a,i,s){a||e.report(57);for(let t=0;t0&&"constructor"===e.tokenValue&&e.report(109),1074790415===e.getToken()&&e.report(108),z(e,t,1074790417)?i.length>0&&e.report(120):d.push(St(e,t,n,l,r,a,i,0,s,i.length>0?o:e.tokenStart))}return X(e,8&i?32|t:t,1074790415),l?.validatePrivateIdentifierRefs(),e.flags=-33&e.flags|p,e.finishNode({type:"ClassBody",body:d},c)}function St(e,t,r,n,o,a,i,s,c,l){let u=s?32:0,p=null;const d=e.getToken();if(176128&d||-2147483528===d)switch(p=tt(e,t),d){case 36970:if(!s&&67174411!==e.getToken()&&1048576&~e.getToken()&&1077936155!==e.getToken())return St(e,t,r,n,o,a,i,1,c,l);break;case 209005:if(67174411!==e.getToken()&&!(1&e.flags)){if(!(1073741824&~e.getToken()))return Ct(e,t,n,p,u,i,l);u|=16|(J(e,t,8391476)?8:0)}break;case 209008:if(67174411!==e.getToken()){if(!(1073741824&~e.getToken()))return Ct(e,t,n,p,u,i,l);u|=256}break;case 209009:if(67174411!==e.getToken()){if(!(1073741824&~e.getToken()))return Ct(e,t,n,p,u,i,l);u|=512}break;case 12402:if(67174411!==e.getToken()&&!(1&e.flags)){if(!(1073741824&~e.getToken()))return Ct(e,t,n,p,u,i,l);e.options.next&&(u|=1024)}}else if(69271571===d)u|=2,p=ut(e,o,n,c);else if(134217728&~d)if(8391476===d)u|=8,j(e,t);else if(130===e.getToken())u|=8192,p=vt(e,16|t,n,768);else if(1073741824&~e.getToken()){if(s&&2162700===d)return function(e,t,r,n,o){return r=r?.createChildScope(),ke(e,t=592128|5764^(5764|t),r,n,{},o,"StaticBlock")}(e,16|t,r,n,l);-2147483527===d?(p=tt(e,t),67174411!==e.getToken()&&e.report(30,x[255&e.getToken()])):e.report(30,x[255&e.getToken()])}else u|=128;else p=rt(e,t);if(1816&u&&(143360&e.getToken()||-2147483528===e.getToken()||-2147483527===e.getToken()?p=tt(e,t):134217728&~e.getToken()?69271571===e.getToken()?(u|=2,p=ut(e,t,n,0)):130===e.getToken()?(u|=8192,p=vt(e,t,n,u)):e.report(135):p=rt(e,t)),2&u||("constructor"===e.tokenValue?(1073741824&~e.getToken()?32&u||67174411!==e.getToken()||(920&u?e.report(53,"accessor"):512&t||(32&e.flags?e.report(54):e.flags|=32)):e.report(129),u|=64):!(8192&u)&&32&u&&"prototype"===e.tokenValue&&e.report(52)),1024&u||67174411!==e.getToken()&&!(768&u))return Ct(e,t,n,p,u,i,l);const g=ct(e,16|t,n,u,c,e.tokenStart);return e.finishNode({type:"MethodDefinition",kind:!(32&u)&&64&u?"constructor":256&u?"get":512&u?"set":"method",static:(32&u)>0,computed:(2&u)>0,key:p,value:g,...e.options.next?{decorators:i}:null},l)}function vt(e,t,r,n){const{tokenStart:o}=e;j(e,t);const{tokenValue:a}=e;return"constructor"===a&&e.report(128),e.options.lexical&&(r||e.report(4,a),n?r.addPrivateIdentifier(a,n):r.addPrivateIdentifierRef(a)),j(e,t),e.finishNode({type:"PrivateIdentifier",name:a},o)}function Ct(e,t,r,n,o,a,i){let s=null;if(8&o&&e.report(0),1077936155===e.getToken()){j(e,32|t);const{tokenStart:n}=e;537079927===e.getToken()&&e.report(119);const a=11264|(64&o?0:16896);s=Me(e,16|(t=65792|((t|a)^a|(8&o?1024:0)|(16&o?2048:0)|(64&o?16384:0))),r,2,0,1,0,1,n),!(1073741824&~e.getToken())&&4194304&~e.getToken()||(s=je(e,16|t,r,s,0,0,n),s=Re(e,16|t,r,0,0,n,s))}return M(e,t),e.finishNode({type:1024&o?"AccessorProperty":"PropertyDefinition",key:n,value:s,static:(32&o)>0,computed:(2&o)>0,...e.options.next?{decorators:a}:null},i)}function qt(e,t,r,n,o,a){if(143360&e.getToken()||!(1&t)&&-2147483527===e.getToken())return Et(e,t,r,o,a);2097152&~e.getToken()&&e.report(30,x[255&e.getToken()]);const i=69271571===e.getToken()?at(e,t,r,n,1,0,1,o,a):lt(e,t,r,n,1,0,1,o,a);return 16&e.destructible&&e.report(50),32&e.destructible&&e.report(50),i}function Et(e,t,r,n,o){const a=e.getToken();1&t&&(537079808&~a?36864&~a&&-2147483527!==a||e.report(118):e.report(119)),20480&~a||e.report(102),241771===a&&(1024&t&&e.report(32),2&t&&e.report(111)),73==(255&a)&&24&n&&e.report(100),209006===a&&(2048&t&&e.report(176),2&t&&e.report(110));const{tokenValue:i,tokenStart:s}=e;return j(e,t),r?.addVarOrBlock(t,i,n,o),e.finishNode({type:"Identifier",name:i},s)}function Nt(e,t,r,n,o){if(n||X(e,t,8456256),8390721===e.getToken()){const a=function(e,t){return ae(e),e.finishNode({type:"JSXOpeningFragment"},t)}(e,o),[i,s]=function(e,t,r,n){const o=[];for(;;){const a=At(e,t,r,n);if("JSXClosingFragment"===a.type)return[o,a];o.push(a)}}(e,t,r,n);return e.finishNode({type:"JSXFragment",openingFragment:a,children:i,closingFragment:s},o)}8457014===e.getToken()&&e.report(30,x[255&e.getToken()]);let a=null,i=[];const s=function(e,t,r,n,o){143360&~e.getToken()&&4096&~e.getToken()&&e.report(0);const a=Vt(e,t),i=function(e,t,r){const n=[];for(;8457014!==e.getToken()&&8390721!==e.getToken()&&1048576!==e.getToken();)n.push(Rt(e,t,r));return n}(e,t,r),s=8457014===e.getToken();s&&X(e,t,8457014);8390721!==e.getToken()&&e.report(25,x[65]);n||!s?ae(e):j(e,t);return e.finishNode({type:"JSXOpeningElement",name:a,attributes:i,selfClosing:s},o)}(e,t,r,n,o);if(!s.selfClosing){[i,a]=function(e,t,r,n){const o=[];for(;;){const a=Lt(e,t,r,n);if("JSXClosingElement"===a.type)return[o,a];o.push(a)}}(e,t,r,n);const o=K(a.name);K(s.name)!==o&&e.report(155,o)}return e.finishNode({type:"JSXElement",children:i,openingElement:s,closingElement:a},o)}function Lt(e,t,r,n){if(137===e.getToken())return It(e,t);if(2162700===e.getToken())return Ut(e,t,r,1,0);if(8456256===e.getToken()){const{tokenStart:o}=e;return j(e,t),8457014===e.getToken()?function(e,t,r,n){X(e,t,8457014);const o=Vt(e,t);return 8390721!==e.getToken()&&e.report(25,x[65]),r?ae(e):j(e,t),e.finishNode({type:"JSXClosingElement",name:o},n)}(e,t,n,o):Nt(e,t,r,1,o)}e.report(0)}function At(e,t,r,n){if(137===e.getToken())return It(e,t);if(2162700===e.getToken())return Ut(e,t,r,1,0);if(8456256===e.getToken()){const{tokenStart:o}=e;return j(e,t),8457014===e.getToken()?function(e,t,r,n){return X(e,t,8457014),8390721!==e.getToken()&&e.report(25,x[65]),r?ae(e):j(e,t),e.finishNode({type:"JSXClosingFragment"},n)}(e,t,n,o):Nt(e,t,r,1,o)}e.report(0)}function It(e,t){const r=e.tokenStart;j(e,t);const n={type:"JSXText",value:e.tokenValue};return e.options.raw&&(n.raw=e.tokenRaw),e.finishNode(n,r)}function Vt(e,t){const{tokenStart:r}=e;ie(e);let n=Pt(e,t);if(21===e.getToken())return Bt(e,t,n,r);for(;z(e,t,67108877);)ie(e),n=Dt(e,t,n,r);return n}function Dt(e,t,r,n){const o=Pt(e,t);return e.finishNode({type:"JSXMemberExpression",object:r,property:o},n)}function Rt(e,t,r){const{tokenStart:n}=e;if(2162700===e.getToken())return function(e,t,r){const n=e.tokenStart;j(e,t),X(e,t,14);const o=Ie(e,t,r,1,0,e.tokenStart);return X(e,t,1074790415),e.finishNode({type:"JSXSpreadAttribute",argument:o},n)}(e,t,r);ie(e);let o=null,a=Pt(e,t);if(21===e.getToken()&&(a=Bt(e,t,a,n)),1077936155===e.getToken()){switch(oe(e,t)){case 134283267:o=rt(e,t);break;case 8456256:o=Nt(e,t,r,0,e.tokenStart);break;case 2162700:o=Ut(e,t,r,0,1);break;default:e.report(154)}}return e.finishNode({type:"JSXAttribute",value:o,name:a},n)}function Bt(e,t,r,n){X(e,t,21);const o=Pt(e,t);return e.finishNode({type:"JSXNamespacedName",namespace:r,name:o},n)}function Ut(e,t,r,n,o){const{tokenStart:a}=e;j(e,32|t);const{tokenStart:i}=e;if(14===e.getToken())return function(e,t,r,n){X(e,t,14);const o=Ie(e,t,r,1,0,e.tokenStart);return X(e,t,1074790415),e.finishNode({type:"JSXSpreadChild",expression:o},n)}(e,t,r,a);let s=null;return 1074790415===e.getToken()?(o&&e.report(157),s=function(e,t){return e.finishNode({type:"JSXEmptyExpression"},t,e.tokenStart)}(e,{index:e.startIndex,line:e.startLine,column:e.startColumn})):s=Ie(e,t,r,1,0,i),1074790415!==e.getToken()&&e.report(25,x[15]),n?ae(e):j(e,t),e.finishNode({type:"JSXExpressionContainer",expression:s},a)}function Pt(e,t){const r=e.tokenStart;143360&e.getToken()||e.report(30,x[255&e.getToken()]);const{tokenValue:n}=e;return j(e,t),e.finishNode({type:"JSXIdentifier",name:n},r)}e.parse=function(e,t){return pe(e,t)},e.parseModule=function(e,t){return pe(e,{...t,sourceType:"module"})},e.parseScript=function(e,t){return pe(e,{...t,sourceType:"script"})},e.version="7.0.0"})); diff --git a/codex-rs/core/src/tools/js_repl/mod.rs b/codex-rs/core/src/tools/js_repl/mod.rs deleted file mode 100644 index 2f494adc35a2..000000000000 --- a/codex-rs/core/src/tools/js_repl/mod.rs +++ /dev/null @@ -1,2055 +0,0 @@ -use std::collections::HashMap; -use std::collections::VecDeque; -use std::fmt; -#[cfg(unix)] -use std::os::unix::process::ExitStatusExt; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; - -use codex_protocol::ThreadId; -use codex_protocol::models::ContentItem; -use codex_protocol::models::DEFAULT_IMAGE_DETAIL; -use codex_protocol::models::FunctionCallOutputContentItem; -use codex_protocol::models::FunctionCallOutputPayload; -use codex_protocol::models::ImageDetail; -use codex_protocol::models::ResponseInputItem; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value as JsonValue; -use tokio::io::AsyncBufReadExt; -use tokio::io::AsyncWriteExt; -use tokio::io::BufReader; -use tokio::process::Child; -use tokio::process::ChildStdin; -use tokio::sync::Mutex; -use tokio::sync::Notify; -use tokio::sync::OnceCell; -use tokio_util::sync::CancellationToken; -use tracing::info; -use tracing::trace; -use tracing::warn; -use uuid::Uuid; - -use crate::exec::ExecCapturePolicy; -use crate::exec::ExecExpiration; -use crate::exec_env::create_env; -use crate::function_tool::FunctionCallError; -use crate::original_image_detail::normalize_output_image_detail; -use crate::sandboxing::ExecOptions; -use crate::session::session::Session; -use crate::session::turn_context::TurnContext; -use crate::tools::ToolRouter; -use crate::tools::context::SharedTurnDiffTracker; -use codex_sandboxing::SandboxCommand; -use codex_sandboxing::SandboxManager; -use codex_sandboxing::SandboxTransformRequest; -use codex_sandboxing::SandboxablePreference; -use codex_tools::ResponsesApiNamespaceTool; -use codex_tools::ToolName; -use codex_tools::ToolSpec; -use codex_utils_output_truncation::TruncationPolicy; -use codex_utils_output_truncation::truncate_text; - -pub(crate) const JS_REPL_PRAGMA_PREFIX: &str = "// codex-js-repl:"; -const KERNEL_SOURCE: &str = include_str!("kernel.js"); -const MERIYAH_UMD: &str = include_str!("meriyah.umd.min.js"); -const JS_REPL_MIN_NODE_VERSION: &str = include_str!("../../../../node-version.txt"); -const JS_REPL_STDERR_TAIL_LINE_LIMIT: usize = 20; -const JS_REPL_STDERR_TAIL_LINE_MAX_BYTES: usize = 512; -const JS_REPL_STDERR_TAIL_MAX_BYTES: usize = 4_096; -const JS_REPL_STDERR_TAIL_SEPARATOR: &str = " | "; -const JS_REPL_EXEC_ID_LOG_LIMIT: usize = 8; -const JS_REPL_MODEL_DIAG_STDERR_MAX_BYTES: usize = 1_024; -const JS_REPL_MODEL_DIAG_ERROR_MAX_BYTES: usize = 256; -const JS_REPL_TOOL_RESPONSE_TEXT_PREVIEW_MAX_BYTES: usize = 512; - -/// Per-task js_repl handle stored on the turn context. -pub(crate) struct JsReplHandle { - node_path: Option, - node_module_dirs: Vec, - cell: OnceCell>, -} - -impl fmt::Debug for JsReplHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("JsReplHandle").finish_non_exhaustive() - } -} - -impl JsReplHandle { - pub(crate) fn with_node_path( - node_path: Option, - node_module_dirs: Vec, - ) -> Self { - Self { - node_path, - node_module_dirs, - cell: OnceCell::new(), - } - } - - pub(crate) async fn manager(&self) -> Result, FunctionCallError> { - self.cell - .get_or_try_init(|| async { - JsReplManager::new(self.node_path.clone(), self.node_module_dirs.clone()).await - }) - .await - .cloned() - } - - pub(crate) fn manager_if_initialized(&self) -> Option> { - self.cell.get().cloned() - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct JsReplArgs { - pub code: String, - #[serde(default)] - pub timeout_ms: Option, -} - -#[derive(Clone, Debug)] -pub struct JsExecResult { - pub output: String, - pub content_items: Vec, -} - -struct KernelState { - child: Arc>, - recent_stderr: Arc>>, - stdin: Arc>, - pending_execs: Arc>>>, - exec_contexts: Arc>>, - top_level_exec_state: TopLevelExecState, - shutdown: CancellationToken, -} - -#[derive(Clone)] -struct ExecContext { - session: Arc, - turn: Arc, - cancellation_token: CancellationToken, - tracker: SharedTurnDiffTracker, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -enum TopLevelExecState { - #[default] - Idle, - FreshKernel { - turn_id: String, - exec_id: Option, - }, - ReusedKernelPending { - turn_id: String, - exec_id: String, - }, - Submitted { - turn_id: String, - exec_id: String, - }, -} - -impl TopLevelExecState { - fn registered_exec_id(&self) -> Option<&str> { - match self { - Self::Idle => None, - Self::FreshKernel { - exec_id: Some(exec_id), - .. - } - | Self::ReusedKernelPending { exec_id, .. } - | Self::Submitted { exec_id, .. } => Some(exec_id.as_str()), - Self::FreshKernel { exec_id: None, .. } => None, - } - } - - fn should_reset_for_interrupt(&self, turn_id: &str) -> bool { - match self { - Self::Idle => false, - Self::FreshKernel { - turn_id: active_turn_id, - .. - } - | Self::Submitted { - turn_id: active_turn_id, - .. - } => active_turn_id == turn_id, - Self::ReusedKernelPending { .. } => false, - } - } -} - -#[derive(Default)] -struct ExecToolCalls { - in_flight: usize, - content_items: Vec, - notify: Arc, - cancel: CancellationToken, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(clippy::enum_variant_names)] -enum JsReplToolCallPayloadKind { - MessageContent, - FunctionText, - FunctionContentItems, - CustomText, - CustomContentItems, - McpResult, - McpErrorResult, - Error, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -struct JsReplToolCallResponseSummary { - response_type: Option, - payload_kind: Option, - payload_text_preview: Option, - payload_text_length: Option, - payload_item_count: Option, - text_item_count: Option, - image_item_count: Option, - structured_content_present: Option, - result_is_error: Option, -} - -enum KernelStreamEnd { - Shutdown, - StdoutEof, - StdoutReadError(String), -} - -impl KernelStreamEnd { - fn reason(&self) -> &'static str { - match self { - Self::Shutdown => "shutdown", - Self::StdoutEof => "stdout_eof", - Self::StdoutReadError(_) => "stdout_read_error", - } - } - - fn error(&self) -> Option<&str> { - match self { - Self::StdoutReadError(err) => Some(err), - _ => None, - } - } -} - -struct KernelDebugSnapshot { - pid: Option, - status: String, - stderr_tail: String, -} - -fn format_exit_status(status: std::process::ExitStatus) -> String { - if let Some(code) = status.code() { - return format!("code={code}"); - } - #[cfg(unix)] - if let Some(signal) = status.signal() { - return format!("signal={signal}"); - } - "unknown".to_string() -} - -fn format_stderr_tail(lines: &VecDeque) -> String { - if lines.is_empty() { - return "".to_string(); - } - lines - .iter() - .cloned() - .collect::>() - .join(JS_REPL_STDERR_TAIL_SEPARATOR) -} - -fn truncate_utf8_prefix_by_bytes(input: &str, max_bytes: usize) -> String { - if input.len() <= max_bytes { - return input.to_string(); - } - if max_bytes == 0 { - return String::new(); - } - let mut end = max_bytes; - while end > 0 && !input.is_char_boundary(end) { - end -= 1; - } - input[..end].to_string() -} - -fn stderr_tail_formatted_bytes(lines: &VecDeque) -> usize { - if lines.is_empty() { - return 0; - } - let payload_bytes: usize = lines.iter().map(String::len).sum(); - let separator_bytes = JS_REPL_STDERR_TAIL_SEPARATOR.len() * (lines.len() - 1); - payload_bytes + separator_bytes -} - -fn stderr_tail_bytes_with_candidate(lines: &VecDeque, line: &str) -> usize { - if lines.is_empty() { - return line.len(); - } - stderr_tail_formatted_bytes(lines) + JS_REPL_STDERR_TAIL_SEPARATOR.len() + line.len() -} - -fn push_stderr_tail_line(lines: &mut VecDeque, line: &str) -> String { - let max_line_bytes = JS_REPL_STDERR_TAIL_LINE_MAX_BYTES.min(JS_REPL_STDERR_TAIL_MAX_BYTES); - let bounded_line = truncate_utf8_prefix_by_bytes(line, max_line_bytes); - if bounded_line.is_empty() { - return bounded_line; - } - - while !lines.is_empty() - && (lines.len() >= JS_REPL_STDERR_TAIL_LINE_LIMIT - || stderr_tail_bytes_with_candidate(lines, &bounded_line) - > JS_REPL_STDERR_TAIL_MAX_BYTES) - { - lines.pop_front(); - } - - lines.push_back(bounded_line.clone()); - bounded_line -} - -fn is_kernel_status_exited(status: &str) -> bool { - status.starts_with("exited(") -} - -fn should_include_model_diagnostics_for_write_error( - err_message: &str, - snapshot: &KernelDebugSnapshot, -) -> bool { - is_kernel_status_exited(&snapshot.status) - || err_message.to_ascii_lowercase().contains("broken pipe") -} - -fn format_model_kernel_failure_details( - reason: &str, - stream_error: Option<&str>, - snapshot: &KernelDebugSnapshot, -) -> String { - let payload = serde_json::json!({ - "reason": reason, - "stream_error": stream_error - .map(|err| truncate_utf8_prefix_by_bytes(err, JS_REPL_MODEL_DIAG_ERROR_MAX_BYTES)), - "kernel_pid": snapshot.pid, - "kernel_status": snapshot.status, - "kernel_stderr_tail": truncate_utf8_prefix_by_bytes( - &snapshot.stderr_tail, - JS_REPL_MODEL_DIAG_STDERR_MAX_BYTES, - ), - }); - let encoded = serde_json::to_string(&payload) - .unwrap_or_else(|err| format!(r#"{{"reason":"serialization_error","error":"{err}"}}"#)); - format!("js_repl diagnostics: {encoded}") -} - -fn with_model_kernel_failure_message( - base_message: &str, - reason: &str, - stream_error: Option<&str>, - snapshot: &KernelDebugSnapshot, -) -> String { - format!( - "{base_message}\n\n{}", - format_model_kernel_failure_details(reason, stream_error, snapshot) - ) -} - -pub struct JsReplManager { - node_path: Option, - node_module_dirs: Vec, - tmp_dir: tempfile::TempDir, - kernel: Arc>>, - exec_lock: Arc, - exec_tool_calls: Arc>>, -} - -impl JsReplManager { - async fn new( - node_path: Option, - node_module_dirs: Vec, - ) -> Result, FunctionCallError> { - let tmp_dir = tempfile::tempdir().map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to create js_repl temp dir: {err}")) - })?; - - let manager = Arc::new(Self { - node_path, - node_module_dirs, - tmp_dir, - kernel: Arc::new(Mutex::new(None)), - exec_lock: Arc::new(tokio::sync::Semaphore::new(1)), - exec_tool_calls: Arc::new(Mutex::new(HashMap::new())), - }); - - Ok(manager) - } - - async fn register_exec_tool_calls(&self, exec_id: &str) { - self.exec_tool_calls - .lock() - .await - .insert(exec_id.to_string(), ExecToolCalls::default()); - } - - async fn clear_exec_tool_calls(&self, exec_id: &str) { - if let Some(state) = self.exec_tool_calls.lock().await.remove(exec_id) { - state.cancel.cancel(); - state.notify.notify_waiters(); - } - } - - async fn wait_for_exec_tool_calls(&self, exec_id: &str) { - loop { - let notified = { - let calls = self.exec_tool_calls.lock().await; - calls - .get(exec_id) - .filter(|state| state.in_flight > 0) - .map(|state| Arc::clone(&state.notify).notified_owned()) - }; - match notified { - Some(notified) => notified.await, - None => return, - } - } - } - - async fn begin_exec_tool_call( - exec_tool_calls: &Arc>>, - exec_id: &str, - ) -> Option { - let mut calls = exec_tool_calls.lock().await; - let state = calls.get_mut(exec_id)?; - state.in_flight += 1; - Some(state.cancel.clone()) - } - - async fn record_exec_content_item( - exec_tool_calls: &Arc>>, - exec_id: &str, - content_item: FunctionCallOutputContentItem, - ) { - let mut calls = exec_tool_calls.lock().await; - if let Some(state) = calls.get_mut(exec_id) { - state.content_items.push(content_item); - } - } - - async fn finish_exec_tool_call( - exec_tool_calls: &Arc>>, - exec_id: &str, - ) { - let notify = { - let mut calls = exec_tool_calls.lock().await; - let Some(state) = calls.get_mut(exec_id) else { - return; - }; - if state.in_flight == 0 { - return; - } - state.in_flight -= 1; - if state.in_flight == 0 { - Some(Arc::clone(&state.notify)) - } else { - None - } - }; - if let Some(notify) = notify { - notify.notify_waiters(); - } - } - - async fn wait_for_exec_tool_calls_map( - exec_tool_calls: &Arc>>, - exec_id: &str, - ) { - loop { - let notified = { - let calls = exec_tool_calls.lock().await; - calls - .get(exec_id) - .filter(|state| state.in_flight > 0) - .map(|state| Arc::clone(&state.notify).notified_owned()) - }; - match notified { - Some(notified) => notified.await, - None => return, - } - } - } - - async fn clear_exec_tool_calls_map( - exec_tool_calls: &Arc>>, - exec_id: &str, - ) { - if let Some(state) = exec_tool_calls.lock().await.remove(exec_id) { - state.cancel.cancel(); - state.notify.notify_waiters(); - } - } - - async fn clear_all_exec_tool_calls_map( - exec_tool_calls: &Arc>>, - ) { - let states = { - let mut calls = exec_tool_calls.lock().await; - calls.drain().map(|(_, state)| state).collect::>() - }; - for state in states { - state.cancel.cancel(); - state.notify.notify_waiters(); - } - } - - async fn register_top_level_exec(&self, exec_id: String, turn_id: String) { - let mut kernel = self.kernel.lock().await; - let Some(state) = kernel.as_mut() else { - return; - }; - state.top_level_exec_state = match &state.top_level_exec_state { - TopLevelExecState::FreshKernel { - turn_id: active_turn_id, - .. - } if active_turn_id == &turn_id => TopLevelExecState::FreshKernel { - turn_id, - exec_id: Some(exec_id), - }, - TopLevelExecState::Idle - | TopLevelExecState::ReusedKernelPending { .. } - | TopLevelExecState::Submitted { .. } - | TopLevelExecState::FreshKernel { .. } => { - TopLevelExecState::ReusedKernelPending { turn_id, exec_id } - } - }; - } - - async fn mark_top_level_exec_submitted(&self, exec_id: &str) { - let mut kernel = self.kernel.lock().await; - let Some(state) = kernel.as_mut() else { - return; - }; - let next_state = match &state.top_level_exec_state { - TopLevelExecState::FreshKernel { - turn_id, - exec_id: Some(active_exec_id), - } - | TopLevelExecState::ReusedKernelPending { - turn_id, - exec_id: active_exec_id, - } if active_exec_id == exec_id => Some(TopLevelExecState::Submitted { - turn_id: turn_id.clone(), - exec_id: active_exec_id.clone(), - }), - TopLevelExecState::Idle - | TopLevelExecState::FreshKernel { .. } - | TopLevelExecState::ReusedKernelPending { .. } - | TopLevelExecState::Submitted { .. } => None, - }; - if let Some(next_state) = next_state { - state.top_level_exec_state = next_state; - } - } - - async fn clear_top_level_exec_if_matches(&self, exec_id: &str) { - Self::clear_top_level_exec_if_matches_map(&self.kernel, exec_id).await; - } - - async fn clear_top_level_exec_if_matches_map( - kernel: &Arc>>, - exec_id: &str, - ) { - let mut kernel = kernel.lock().await; - if let Some(state) = kernel.as_mut() - && state.top_level_exec_state.registered_exec_id() == Some(exec_id) - { - state.top_level_exec_state = TopLevelExecState::Idle; - } - } - - async fn clear_top_level_exec_if_matches_any_map( - kernel: &Arc>>, - exec_ids: &[String], - ) { - let mut kernel = kernel.lock().await; - if let Some(state) = kernel.as_mut() - && state - .top_level_exec_state - .registered_exec_id() - .is_some_and(|exec_id| exec_ids.iter().any(|pending_id| pending_id == exec_id)) - { - state.top_level_exec_state = TopLevelExecState::Idle; - } - } - - async fn turn_interrupt_requires_reset(&self, turn_id: &str) -> bool { - self.kernel.lock().await.as_ref().is_some_and(|state| { - state - .top_level_exec_state - .should_reset_for_interrupt(turn_id) - }) - } - - fn log_tool_call_response( - req: &RunToolRequest, - ok: bool, - summary: &JsReplToolCallResponseSummary, - response: Option<&JsonValue>, - error: Option<&str>, - ) { - info!( - exec_id = %req.exec_id, - tool_call_id = %req.id, - tool_name = %req.tool_name, - ok, - summary = ?summary, - "js_repl nested tool call completed" - ); - if let Some(response) = response { - trace!( - exec_id = %req.exec_id, - tool_call_id = %req.id, - tool_name = %req.tool_name, - response_json = %response, - "js_repl nested tool call raw response" - ); - } - if let Some(error) = error { - trace!( - exec_id = %req.exec_id, - tool_call_id = %req.id, - tool_name = %req.tool_name, - error = %error, - "js_repl nested tool call raw error" - ); - } - } - - fn summarize_text_payload( - response_type: Option<&str>, - payload_kind: JsReplToolCallPayloadKind, - text: &str, - ) -> JsReplToolCallResponseSummary { - JsReplToolCallResponseSummary { - response_type: response_type.map(str::to_owned), - payload_kind: Some(payload_kind), - payload_text_preview: (!text.is_empty()).then(|| { - truncate_text( - text, - TruncationPolicy::Bytes(JS_REPL_TOOL_RESPONSE_TEXT_PREVIEW_MAX_BYTES), - ) - }), - payload_text_length: Some(text.len()), - ..Default::default() - } - } - - fn summarize_function_output_payload( - response_type: &str, - payload_kind: JsReplToolCallPayloadKind, - output: &FunctionCallOutputPayload, - ) -> JsReplToolCallResponseSummary { - let (payload_item_count, text_item_count, image_item_count) = - if let Some(items) = output.content_items() { - let text_item_count = items - .iter() - .filter(|item| matches!(item, FunctionCallOutputContentItem::InputText { .. })) - .count(); - let image_item_count = items.len().saturating_sub(text_item_count); - ( - Some(items.len()), - Some(text_item_count), - Some(image_item_count), - ) - } else { - (None, None, None) - }; - let payload_text = output.body.to_text(); - JsReplToolCallResponseSummary { - response_type: Some(response_type.to_string()), - payload_kind: Some(payload_kind), - payload_text_preview: payload_text.as_deref().and_then(|text| { - (!text.is_empty()).then(|| { - truncate_text( - text, - TruncationPolicy::Bytes(JS_REPL_TOOL_RESPONSE_TEXT_PREVIEW_MAX_BYTES), - ) - }) - }), - payload_text_length: payload_text.as_ref().map(String::len), - payload_item_count, - text_item_count, - image_item_count, - ..Default::default() - } - } - - fn summarize_message_payload(content: &[ContentItem]) -> JsReplToolCallResponseSummary { - let text_item_count = content - .iter() - .filter(|item| { - matches!( - item, - ContentItem::InputText { .. } | ContentItem::OutputText { .. } - ) - }) - .count(); - let image_item_count = content.len().saturating_sub(text_item_count); - let payload_text = content - .iter() - .filter_map(|item| match item { - ContentItem::InputText { text } | ContentItem::OutputText { text } - if !text.trim().is_empty() => - { - Some(text.as_str()) - } - ContentItem::InputText { .. } - | ContentItem::InputImage { .. } - | ContentItem::OutputText { .. } => None, - }) - .collect::>(); - let payload_text = if payload_text.is_empty() { - None - } else { - Some(payload_text.join("\n")) - }; - JsReplToolCallResponseSummary { - response_type: Some("message".to_string()), - payload_kind: Some(JsReplToolCallPayloadKind::MessageContent), - payload_text_preview: payload_text.as_deref().and_then(|text| { - (!text.is_empty()).then(|| { - truncate_text( - text, - TruncationPolicy::Bytes(JS_REPL_TOOL_RESPONSE_TEXT_PREVIEW_MAX_BYTES), - ) - }) - }), - payload_text_length: payload_text.as_ref().map(String::len), - payload_item_count: Some(content.len()), - text_item_count: Some(text_item_count), - image_item_count: Some(image_item_count), - ..Default::default() - } - } - - fn summarize_tool_call_response(response: &ResponseInputItem) -> JsReplToolCallResponseSummary { - match response { - ResponseInputItem::Message { content, .. } => Self::summarize_message_payload(content), - ResponseInputItem::FunctionCallOutput { output, .. } => { - let payload_kind = if output.content_items().is_some() { - JsReplToolCallPayloadKind::FunctionContentItems - } else { - JsReplToolCallPayloadKind::FunctionText - }; - Self::summarize_function_output_payload( - "function_call_output", - payload_kind, - output, - ) - } - ResponseInputItem::CustomToolCallOutput { output, .. } => { - let payload_kind = if output.content_items().is_some() { - JsReplToolCallPayloadKind::CustomContentItems - } else { - JsReplToolCallPayloadKind::CustomText - }; - Self::summarize_function_output_payload( - "custom_tool_call_output", - payload_kind, - output, - ) - } - ResponseInputItem::McpToolCallOutput { output, .. } => { - let function_output = output.as_function_call_output_payload(); - let payload_kind = if output.success() { - JsReplToolCallPayloadKind::McpResult - } else { - JsReplToolCallPayloadKind::McpErrorResult - }; - let mut summary = Self::summarize_function_output_payload( - "mcp_tool_call_output", - payload_kind, - &function_output, - ); - summary.payload_item_count = Some(output.content.len()); - summary.structured_content_present = Some(output.structured_content.is_some()); - summary.result_is_error = Some(!output.success()); - summary - } - ResponseInputItem::ToolSearchOutput { tools, .. } => JsReplToolCallResponseSummary { - response_type: Some("tool_search_output".to_string()), - payload_kind: Some(JsReplToolCallPayloadKind::FunctionText), - payload_text_preview: Some(serde_json::Value::Array(tools.clone()).to_string()), - payload_text_length: Some( - serde_json::Value::Array(tools.clone()).to_string().len(), - ), - payload_item_count: Some(tools.len()), - ..Default::default() - }, - } - } - - fn summarize_tool_call_error(error: &str) -> JsReplToolCallResponseSummary { - Self::summarize_text_payload( - /*response_type*/ None, - JsReplToolCallPayloadKind::Error, - error, - ) - } - - pub async fn reset(&self) -> Result<(), FunctionCallError> { - let _permit = self.exec_lock.clone().acquire_owned().await.map_err(|_| { - FunctionCallError::RespondToModel("js_repl execution unavailable".to_string()) - })?; - self.reset_kernel().await; - Self::clear_all_exec_tool_calls_map(&self.exec_tool_calls).await; - Ok(()) - } - - pub async fn interrupt_turn_exec(&self, turn_id: &str) -> Result { - let _permit = self.exec_lock.clone().acquire_owned().await.map_err(|_| { - FunctionCallError::RespondToModel("js_repl execution unavailable".to_string()) - })?; - if !self.turn_interrupt_requires_reset(turn_id).await { - return Ok(false); - } - self.reset_kernel().await; - Self::clear_all_exec_tool_calls_map(&self.exec_tool_calls).await; - Ok(true) - } - - async fn reset_kernel(&self) { - let state = { - let mut guard = self.kernel.lock().await; - guard.take() - }; - if let Some(state) = state { - state.shutdown.cancel(); - Self::kill_kernel_child(&state.child, "reset").await; - } - } - - #[cfg(test)] - pub async fn execute( - &self, - session: Arc, - turn: Arc, - tracker: SharedTurnDiffTracker, - args: JsReplArgs, - ) -> Result { - self.execute_with_cancellation(session, turn, CancellationToken::new(), tracker, args) - .await - } - - #[expect( - clippy::await_holding_invalid_type, - reason = "js_repl kernel initialization must be serialized with kernel state" - )] - pub async fn execute_with_cancellation( - &self, - session: Arc, - turn: Arc, - cancellation_token: CancellationToken, - tracker: SharedTurnDiffTracker, - args: JsReplArgs, - ) -> Result { - let _permit = self.exec_lock.clone().acquire_owned().await.map_err(|_| { - FunctionCallError::RespondToModel("js_repl execution unavailable".to_string()) - })?; - - let (stdin, pending_execs, exec_contexts, child, recent_stderr) = { - let mut kernel = self.kernel.lock().await; - if kernel.is_none() { - let dependency_env = session.dependency_env().await; - let mut state = self - .start_kernel( - Arc::clone(&turn), - &dependency_env, - Some(session.conversation_id), - ) - .await - .map_err(FunctionCallError::RespondToModel)?; - state.top_level_exec_state = TopLevelExecState::FreshKernel { - turn_id: turn.sub_id.clone(), - exec_id: None, - }; - *kernel = Some(state); - } - - let state = match kernel.as_ref() { - Some(state) => state, - None => { - return Err(FunctionCallError::RespondToModel( - "js_repl kernel unavailable".to_string(), - )); - } - }; - ( - Arc::clone(&state.stdin), - Arc::clone(&state.pending_execs), - Arc::clone(&state.exec_contexts), - Arc::clone(&state.child), - Arc::clone(&state.recent_stderr), - ) - }; - - let (req_id, rx) = { - let req_id = Uuid::new_v4().to_string(); - let (tx, rx) = tokio::sync::oneshot::channel(); - pending_execs.lock().await.insert(req_id.clone(), tx); - exec_contexts.lock().await.insert( - req_id.clone(), - ExecContext { - session: Arc::clone(&session), - turn: Arc::clone(&turn), - cancellation_token, - tracker, - }, - ); - (req_id, rx) - }; - self.register_top_level_exec(req_id.clone(), turn.sub_id.clone()) - .await; - self.register_exec_tool_calls(&req_id).await; - - let payload = HostToKernel::Exec { - id: req_id.clone(), - code: args.code, - timeout_ms: args.timeout_ms, - }; - - let write_result = { - // Treat the exec as submitted before the async pipe writes begin: once we start - // awaiting `write_all`, the kernel may already observe runnable JS even if the turn is - // aborted before control returns here. - self.mark_top_level_exec_submitted(&req_id).await; - let write_result = Self::write_message(&stdin, &payload).await; - match write_result { - Ok(()) => Ok(()), - Err(err) => { - self.clear_top_level_exec_if_matches(&req_id).await; - Err(err) - } - } - }; - - if let Err(err) = write_result { - if pending_execs.lock().await.remove(&req_id).is_some() { - self.clear_top_level_exec_if_matches(&req_id).await; - } - exec_contexts.lock().await.remove(&req_id); - self.clear_exec_tool_calls(&req_id).await; - let snapshot = Self::kernel_debug_snapshot(&child, &recent_stderr).await; - let err_message = err.to_string(); - warn!( - exec_id = %req_id, - error = %err_message, - kernel_pid = ?snapshot.pid, - kernel_status = %snapshot.status, - kernel_stderr_tail = %snapshot.stderr_tail, - "failed to submit js_repl exec request to kernel" - ); - let message = - if should_include_model_diagnostics_for_write_error(&err_message, &snapshot) { - with_model_kernel_failure_message( - &err_message, - "write_failed", - Some(&err_message), - &snapshot, - ) - } else { - err_message - }; - return Err(FunctionCallError::RespondToModel(message)); - } - - let timeout_ms = args.timeout_ms.unwrap_or(30_000); - let response = match tokio::time::timeout(Duration::from_millis(timeout_ms), rx).await { - Ok(Ok(msg)) => msg, - Ok(Err(_)) => { - let removed = pending_execs.lock().await.remove(&req_id).is_some(); - if removed { - self.clear_top_level_exec_if_matches(&req_id).await; - } - exec_contexts.lock().await.remove(&req_id); - self.wait_for_exec_tool_calls(&req_id).await; - self.clear_exec_tool_calls(&req_id).await; - let snapshot = Self::kernel_debug_snapshot(&child, &recent_stderr).await; - let message = if is_kernel_status_exited(&snapshot.status) { - with_model_kernel_failure_message( - "js_repl kernel closed unexpectedly", - "response_channel_closed", - /*stream_error*/ None, - &snapshot, - ) - } else { - "js_repl kernel closed unexpectedly".to_string() - }; - return Err(FunctionCallError::RespondToModel(message)); - } - Err(_) => { - self.reset_kernel().await; - self.wait_for_exec_tool_calls(&req_id).await; - self.exec_tool_calls.lock().await.clear(); - self.clear_top_level_exec_if_matches(&req_id).await; - return Err(FunctionCallError::RespondToModel( - "js_repl execution timed out; kernel reset, rerun your request".to_string(), - )); - } - }; - - match response { - ExecResultMessage::Ok { content_items } => { - let (output, content_items) = split_exec_result_content_items(content_items); - Ok(JsExecResult { - output, - content_items, - }) - } - ExecResultMessage::Err { message } => Err(FunctionCallError::RespondToModel(message)), - } - } - - async fn start_kernel( - &self, - turn: Arc, - dependency_env: &HashMap, - thread_id: Option, - ) -> Result { - let node_path = resolve_compatible_node(self.node_path.as_deref()).await?; - - let kernel_path = self - .write_kernel_script() - .await - .map_err(|err| err.to_string())?; - - let mut env = create_env(&turn.shell_environment_policy, thread_id); - if !dependency_env.is_empty() { - env.extend(dependency_env.clone()); - } - env.insert( - "CODEX_JS_TMP_DIR".to_string(), - self.tmp_dir.path().to_string_lossy().to_string(), - ); - let node_module_dirs_key = "CODEX_JS_REPL_NODE_MODULE_DIRS"; - if !self.node_module_dirs.is_empty() && !env.contains_key(node_module_dirs_key) { - let joined = std::env::join_paths(&self.node_module_dirs) - .map_err(|err| format!("failed to join js_repl_node_module_dirs: {err}"))?; - env.insert( - node_module_dirs_key.to_string(), - joined.to_string_lossy().to_string(), - ); - } - - let sandbox = SandboxManager::new(); - let managed_network_active = turn.network.is_some(); - let sandbox_type = sandbox.select_initial( - &turn.file_system_sandbox_policy, - turn.network_sandbox_policy, - SandboxablePreference::Auto, - turn.windows_sandbox_level, - managed_network_active, - ); - let command = SandboxCommand { - program: node_path.into_os_string(), - args: vec![ - "--experimental-vm-modules".to_string(), - kernel_path.to_string_lossy().to_string(), - ], - cwd: turn.cwd.clone(), - env, - additional_permissions: None, - }; - let options = ExecOptions { - expiration: ExecExpiration::DefaultTimeout, - capture_policy: ExecCapturePolicy::ShellTool, - }; - let exec_env = sandbox - .transform(SandboxTransformRequest { - command, - policy: &turn.sandbox_policy, - file_system_policy: &turn.file_system_sandbox_policy, - network_policy: turn.network_sandbox_policy, - sandbox: sandbox_type, - enforce_managed_network: managed_network_active, - network: None, - sandbox_policy_cwd: &turn.cwd, - codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_deref(), - use_legacy_landlock: turn.features.use_legacy_landlock(), - windows_sandbox_level: turn.windows_sandbox_level, - windows_sandbox_private_desktop: turn - .config - .permissions - .windows_sandbox_private_desktop, - }) - .map(|request| { - crate::sandboxing::ExecRequest::from_sandbox_exec_request( - request, - options, - turn.cwd.clone(), - ) - }) - .map_err(|err| format!("failed to configure sandbox for js_repl: {err}"))?; - - let mut cmd = - tokio::process::Command::new(exec_env.command.first().cloned().unwrap_or_default()); - if exec_env.command.len() > 1 { - cmd.args(&exec_env.command[1..]); - } - #[cfg(unix)] - cmd.arg0( - exec_env - .arg0 - .clone() - .unwrap_or_else(|| exec_env.command.first().cloned().unwrap_or_default()), - ); - cmd.current_dir(&exec_env.cwd); - cmd.env_clear(); - cmd.envs(exec_env.env); - cmd.stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .kill_on_drop(true); - - let mut child = cmd - .spawn() - .map_err(|err| format!("failed to start Node runtime: {err}"))?; - let stdout = child - .stdout - .take() - .ok_or_else(|| "js_repl kernel missing stdout".to_string())?; - let stderr = child.stderr.take(); - let stdin = child - .stdin - .take() - .ok_or_else(|| "js_repl kernel missing stdin".to_string())?; - - let shutdown = CancellationToken::new(); - let pending_execs: Arc< - Mutex>>, - > = Arc::new(Mutex::new(HashMap::new())); - let exec_contexts: Arc>> = - Arc::new(Mutex::new(HashMap::new())); - let stdin_arc = Arc::new(Mutex::new(stdin)); - let child = Arc::new(Mutex::new(child)); - let recent_stderr = Arc::new(Mutex::new(VecDeque::with_capacity( - JS_REPL_STDERR_TAIL_LINE_LIMIT, - ))); - - tokio::spawn(Self::read_stdout( - stdout, - Arc::clone(&child), - Arc::clone(&self.kernel), - Arc::clone(&recent_stderr), - Arc::clone(&pending_execs), - Arc::clone(&exec_contexts), - Arc::clone(&self.exec_tool_calls), - Arc::clone(&stdin_arc), - shutdown.clone(), - )); - if let Some(stderr) = stderr { - tokio::spawn(Self::read_stderr( - stderr, - Arc::clone(&recent_stderr), - shutdown.clone(), - )); - } else { - warn!("js_repl kernel missing stderr"); - } - - Ok(KernelState { - child, - recent_stderr, - stdin: stdin_arc, - pending_execs, - exec_contexts, - top_level_exec_state: TopLevelExecState::Idle, - shutdown, - }) - } - - async fn write_kernel_script(&self) -> Result { - let dir = self.tmp_dir.path(); - let kernel_path = dir.join("js_repl_kernel.js"); - let meriyah_path = dir.join("meriyah.umd.min.js"); - tokio::fs::write(&kernel_path, KERNEL_SOURCE).await?; - tokio::fs::write(&meriyah_path, MERIYAH_UMD).await?; - Ok(kernel_path) - } - - #[expect( - clippy::await_holding_invalid_type, - reason = "js_repl stdin writes must be serialized per kernel" - )] - async fn write_message( - stdin: &Arc>, - msg: &HostToKernel, - ) -> Result<(), FunctionCallError> { - let encoded = serde_json::to_string(msg).map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to serialize kernel message: {err}")) - })?; - let mut guard = stdin.lock().await; - guard.write_all(encoded.as_bytes()).await.map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to write to kernel: {err}")) - })?; - guard.write_all(b"\n").await.map_err(|err| { - FunctionCallError::RespondToModel(format!("failed to flush kernel message: {err}")) - })?; - Ok(()) - } - - async fn kernel_stderr_tail_snapshot(recent_stderr: &Arc>>) -> String { - let tail = recent_stderr.lock().await; - format_stderr_tail(&tail) - } - - async fn kernel_debug_snapshot( - child: &Arc>, - recent_stderr: &Arc>>, - ) -> KernelDebugSnapshot { - let (pid, status) = { - let mut guard = child.lock().await; - let pid = guard.id(); - let status = match guard.try_wait() { - Ok(Some(status)) => format!("exited({})", format_exit_status(status)), - Ok(None) => "running".to_string(), - Err(err) => format!("unknown ({err})"), - }; - (pid, status) - }; - let stderr_tail = { - let tail = recent_stderr.lock().await; - format_stderr_tail(&tail) - }; - KernelDebugSnapshot { - pid, - status, - stderr_tail, - } - } - - #[expect( - clippy::await_holding_invalid_type, - reason = "js_repl child shutdown must serialize process inspection and termination" - )] - async fn kill_kernel_child(child: &Arc>, reason: &'static str) { - let mut guard = child.lock().await; - let pid = guard.id(); - match guard.try_wait() { - Ok(Some(_)) => return, - Ok(None) => {} - Err(err) => { - warn!( - kernel_pid = ?pid, - kill_reason = reason, - error = %err, - "failed to inspect js_repl kernel before kill" - ); - } - } - - if let Err(err) = guard.start_kill() { - warn!( - kernel_pid = ?pid, - kill_reason = reason, - error = %err, - "failed to send kill signal to js_repl kernel" - ); - return; - } - - match tokio::time::timeout(Duration::from_secs(2), guard.wait()).await { - Ok(Ok(_status)) => {} - Ok(Err(err)) => { - warn!( - kernel_pid = ?pid, - kill_reason = reason, - error = %err, - "failed while waiting for js_repl kernel exit" - ); - } - Err(_) => { - warn!( - kernel_pid = ?pid, - kill_reason = reason, - "timed out waiting for js_repl kernel to exit after kill" - ); - } - } - } - - fn truncate_id_list(ids: &[String]) -> Vec { - if ids.len() <= JS_REPL_EXEC_ID_LOG_LIMIT { - return ids.to_vec(); - } - let mut output = ids[..JS_REPL_EXEC_ID_LOG_LIMIT].to_vec(); - output.push(format!("...+{}", ids.len() - JS_REPL_EXEC_ID_LOG_LIMIT)); - output - } - - #[allow(clippy::too_many_arguments)] - async fn read_stdout( - stdout: tokio::process::ChildStdout, - child: Arc>, - manager_kernel: Arc>>, - recent_stderr: Arc>>, - pending_execs: Arc>>>, - exec_contexts: Arc>>, - exec_tool_calls: Arc>>, - stdin: Arc>, - shutdown: CancellationToken, - ) { - let mut reader = BufReader::new(stdout).lines(); - let end_reason = loop { - let line = tokio::select! { - _ = shutdown.cancelled() => break KernelStreamEnd::Shutdown, - res = reader.next_line() => match res { - Ok(Some(line)) => line, - Ok(None) => break KernelStreamEnd::StdoutEof, - Err(err) => break KernelStreamEnd::StdoutReadError(err.to_string()), - }, - }; - - let parsed: Result = serde_json::from_str(&line); - let msg = match parsed { - Ok(m) => m, - Err(err) => { - warn!("js_repl kernel sent invalid json: {err} (line: {line})"); - continue; - } - }; - - match msg { - KernelToHost::ExecResult { - id, - ok, - output, - error, - } => { - JsReplManager::wait_for_exec_tool_calls_map(&exec_tool_calls, &id).await; - let content_items = { - let calls = exec_tool_calls.lock().await; - calls - .get(&id) - .map(|state| state.content_items.clone()) - .unwrap_or_default() - }; - let tx = { - let mut pending = pending_execs.lock().await; - pending.remove(&id) - }; - if let Some(tx) = tx { - Self::clear_top_level_exec_if_matches_map(&manager_kernel, &id).await; - let payload = if ok { - ExecResultMessage::Ok { - content_items: build_exec_result_content_items( - output, - content_items, - ), - } - } else { - ExecResultMessage::Err { - message: error - .unwrap_or_else(|| "js_repl execution failed".to_string()), - } - }; - let _ = tx.send(payload); - } - exec_contexts.lock().await.remove(&id); - JsReplManager::clear_exec_tool_calls_map(&exec_tool_calls, &id).await; - } - KernelToHost::EmitImage(req) => { - let exec_id = req.exec_id.clone(); - let emit_id = req.id.clone(); - let context = exec_contexts.lock().await.get(&exec_id).cloned(); - let response = if let Some(ctx) = context { - match validate_emitted_image_url(&req.image_url) { - Ok(()) => { - let content_item = emitted_image_content_item( - ctx.turn.as_ref(), - req.image_url, - req.detail, - ); - JsReplManager::record_exec_content_item( - &exec_tool_calls, - &exec_id, - content_item, - ) - .await; - HostToKernel::EmitImageResult(EmitImageResult { - id: emit_id, - ok: true, - error: None, - }) - } - Err(error) => HostToKernel::EmitImageResult(EmitImageResult { - id: emit_id, - ok: false, - error: Some(error), - }), - } - } else { - HostToKernel::EmitImageResult(EmitImageResult { - id: emit_id, - ok: false, - error: Some("js_repl exec context not found".to_string()), - }) - }; - - if let Err(err) = JsReplManager::write_message(&stdin, &response).await { - let snapshot = - JsReplManager::kernel_debug_snapshot(&child, &recent_stderr).await; - warn!( - exec_id = %exec_id, - emit_id = %req.id, - error = %err, - kernel_pid = ?snapshot.pid, - kernel_status = %snapshot.status, - kernel_stderr_tail = %snapshot.stderr_tail, - "failed to reply to kernel emit_image request" - ); - } - } - KernelToHost::RunTool(req) => { - let Some(reset_cancel) = - JsReplManager::begin_exec_tool_call(&exec_tool_calls, &req.exec_id).await - else { - let exec_id = req.exec_id.clone(); - let tool_call_id = req.id.clone(); - let payload = HostToKernel::RunToolResult(RunToolResult { - id: req.id, - ok: false, - response: None, - error: Some("js_repl exec context not found".to_string()), - }); - if let Err(err) = JsReplManager::write_message(&stdin, &payload).await { - let snapshot = - JsReplManager::kernel_debug_snapshot(&child, &recent_stderr).await; - warn!( - exec_id = %exec_id, - tool_call_id = %tool_call_id, - error = %err, - kernel_pid = ?snapshot.pid, - kernel_status = %snapshot.status, - kernel_stderr_tail = %snapshot.stderr_tail, - "failed to reply to kernel run_tool request" - ); - } - continue; - }; - let stdin_clone = Arc::clone(&stdin); - let exec_contexts = Arc::clone(&exec_contexts); - let exec_tool_calls_for_task = Arc::clone(&exec_tool_calls); - let recent_stderr = Arc::clone(&recent_stderr); - tokio::spawn(async move { - let exec_id = req.exec_id.clone(); - let tool_call_id = req.id.clone(); - let tool_name = req.tool_name.clone(); - let context = exec_contexts.lock().await.get(&exec_id).cloned(); - let result = match context { - Some(ctx) => { - tokio::select! { - _ = reset_cancel.cancelled() => RunToolResult { - id: tool_call_id.clone(), - ok: false, - response: None, - error: Some("js_repl execution reset".to_string()), - }, - result = JsReplManager::run_tool_request(ctx, req) => result, - } - } - None => RunToolResult { - id: tool_call_id.clone(), - ok: false, - response: None, - error: Some("js_repl exec context not found".to_string()), - }, - }; - JsReplManager::finish_exec_tool_call(&exec_tool_calls_for_task, &exec_id) - .await; - let payload = HostToKernel::RunToolResult(result); - if let Err(err) = JsReplManager::write_message(&stdin_clone, &payload).await - { - let stderr_tail = - JsReplManager::kernel_stderr_tail_snapshot(&recent_stderr).await; - warn!( - exec_id = %exec_id, - tool_call_id = %tool_call_id, - tool_name = %tool_name, - error = %err, - kernel_stderr_tail = %stderr_tail, - "failed to reply to kernel run_tool request" - ); - } - }); - } - } - }; - - let exec_ids = { - let mut contexts = exec_contexts.lock().await; - let ids = contexts.keys().cloned().collect::>(); - contexts.clear(); - ids - }; - for exec_id in exec_ids { - JsReplManager::wait_for_exec_tool_calls_map(&exec_tool_calls, &exec_id).await; - JsReplManager::clear_exec_tool_calls_map(&exec_tool_calls, &exec_id).await; - } - let unexpected_snapshot = if matches!(end_reason, KernelStreamEnd::Shutdown) { - None - } else { - Some(Self::kernel_debug_snapshot(&child, &recent_stderr).await) - }; - let kernel_failure_message = unexpected_snapshot.as_ref().map(|snapshot| { - with_model_kernel_failure_message( - "js_repl kernel exited unexpectedly", - end_reason.reason(), - end_reason.error(), - snapshot, - ) - }); - let kernel_exit_message = kernel_failure_message - .clone() - .unwrap_or_else(|| "js_repl kernel exited unexpectedly".to_string()); - - { - let mut kernel = manager_kernel.lock().await; - let should_clear = kernel - .as_ref() - .is_some_and(|state| Arc::ptr_eq(&state.child, &child)); - if should_clear { - kernel.take(); - } - } - - let pending_execs_to_notify = { - let mut pending = pending_execs.lock().await; - pending.drain().collect::>() - }; - let mut pending_exec_ids = Vec::with_capacity(pending_execs_to_notify.len()); - for (id, tx) in pending_execs_to_notify { - pending_exec_ids.push(id); - let _ = tx.send(ExecResultMessage::Err { - message: kernel_exit_message.clone(), - }); - } - if !pending_exec_ids.is_empty() { - Self::clear_top_level_exec_if_matches_any_map(&manager_kernel, &pending_exec_ids).await; - } - - if !matches!(end_reason, KernelStreamEnd::Shutdown) { - let mut pending_exec_ids = pending_exec_ids; - pending_exec_ids.sort_unstable(); - let snapshot = Self::kernel_debug_snapshot(&child, &recent_stderr).await; - warn!( - reason = %end_reason.reason(), - stream_error = %end_reason.error().unwrap_or(""), - kernel_pid = ?snapshot.pid, - kernel_status = %snapshot.status, - pending_exec_count = pending_exec_ids.len(), - pending_exec_ids = ?Self::truncate_id_list(&pending_exec_ids), - kernel_stderr_tail = %snapshot.stderr_tail, - "js_repl kernel terminated unexpectedly" - ); - } - } - - #[expect( - clippy::await_holding_invalid_type, - reason = "nested js_repl tool routing reads through the session-owned manager guard" - )] - async fn run_tool_request(exec: ExecContext, req: RunToolRequest) -> RunToolResult { - if is_js_repl_internal_tool(&req.tool_name) { - let error = "js_repl cannot invoke itself".to_string(); - let summary = Self::summarize_tool_call_error(&error); - Self::log_tool_call_response( - &req, - /*ok*/ false, - &summary, - /*response*/ None, - Some(&error), - ); - return RunToolResult { - id: req.id, - ok: false, - response: None, - error: Some(error), - }; - } - - let mcp_tools = exec - .session - .services - .mcp_connection_manager - .read() - .await - .list_all_tools() - .await; - let router = ToolRouter::from_config( - &exec.turn.tools_config, - crate::tools::router::ToolRouterParams { - deferred_mcp_tools: None, - mcp_tools: Some(mcp_tools), - unavailable_called_tools: Vec::new(), - // JS REPL dispatches nested tool calls directly, not through - // `ToolCallRuntime`'s parallel scheduling lock. - parallel_mcp_server_names: std::collections::HashSet::new(), - discoverable_tools: None, - dynamic_tools: exec.turn.dynamic_tools.as_slice(), - }, - ); - - let specs = router.specs(); - let requested_tool_name = specs - .iter() - .find_map(|spec| match spec { - ToolSpec::Function(tool) if tool.name == req.tool_name => { - Some(ToolName::plain(req.tool_name.clone())) - } - ToolSpec::Freeform(tool) if tool.name == req.tool_name => { - Some(ToolName::plain(req.tool_name.clone())) - } - ToolSpec::Namespace(namespace) => { - namespace.tools.iter().find_map(|tool| match tool { - ResponsesApiNamespaceTool::Function(tool) => { - let tool_name = - ToolName::namespaced(namespace.name.clone(), tool.name.clone()); - let code_mode_name = - codex_tools::code_mode_name_for_tool_name(&tool_name); - (code_mode_name == req.tool_name - || tool_name.display() == req.tool_name) - .then_some(tool_name) - } - }) - } - ToolSpec::LocalShell {} - | ToolSpec::ImageGeneration { .. } - | ToolSpec::ToolSearch { .. } - | ToolSpec::WebSearch { .. } - | ToolSpec::Function(_) - | ToolSpec::Freeform(_) => None, - }) - .unwrap_or_else(|| ToolName::plain(req.tool_name.clone())); - let (tool_call_name, payload) = if let Some(tool_info) = exec - .session - .resolve_mcp_tool_info(&requested_tool_name) - .await - { - ( - tool_info.canonical_tool_name(), - crate::tools::context::ToolPayload::Mcp { - server: tool_info.server_name, - tool: tool_info.tool.name.to_string(), - raw_arguments: req.arguments.clone(), - }, - ) - } else if matches!( - router.find_spec(&requested_tool_name), - Some(ToolSpec::Freeform(_)) - ) { - ( - requested_tool_name, - crate::tools::context::ToolPayload::Custom { - input: req.arguments.clone(), - }, - ) - } else { - ( - requested_tool_name, - crate::tools::context::ToolPayload::Function { - arguments: req.arguments.clone(), - }, - ) - }; - - let call = crate::tools::router::ToolCall { - tool_name: tool_call_name, - call_id: req.id.clone(), - payload, - }; - - let session = Arc::clone(&exec.session); - let turn = Arc::clone(&exec.turn); - let cancellation_token = exec.cancellation_token.clone(); - let tracker = Arc::clone(&exec.tracker); - - match router - .dispatch_tool_call_with_code_mode_result( - session, - turn, - cancellation_token, - tracker, - call, - crate::tools::router::ToolCallSource::JsRepl, - ) - .await - { - Ok(result) => { - let response = result.into_response(); - let summary = Self::summarize_tool_call_response(&response); - match serde_json::to_value(response) { - Ok(value) => { - Self::log_tool_call_response( - &req, - /*ok*/ true, - &summary, - Some(&value), - /*error*/ None, - ); - RunToolResult { - id: req.id, - ok: true, - response: Some(value), - error: None, - } - } - Err(err) => { - let error = format!("failed to serialize tool output: {err}"); - let summary = Self::summarize_tool_call_error(&error); - Self::log_tool_call_response( - &req, - /*ok*/ false, - &summary, - /*response*/ None, - Some(&error), - ); - RunToolResult { - id: req.id, - ok: false, - response: None, - error: Some(error), - } - } - } - } - Err(err) => { - let error = err.to_string(); - let summary = Self::summarize_tool_call_error(&error); - Self::log_tool_call_response( - &req, - /*ok*/ false, - &summary, - /*response*/ None, - Some(&error), - ); - RunToolResult { - id: req.id, - ok: false, - response: None, - error: Some(error), - } - } - } - } - - async fn read_stderr( - stderr: tokio::process::ChildStderr, - recent_stderr: Arc>>, - shutdown: CancellationToken, - ) { - let mut reader = BufReader::new(stderr).lines(); - - loop { - let line = tokio::select! { - _ = shutdown.cancelled() => break, - res = reader.next_line() => match res { - Ok(Some(line)) => line, - Ok(None) => break, - Err(err) => { - warn!("js_repl kernel stderr ended: {err}"); - break; - } - }, - }; - let trimmed = line.trim(); - if !trimmed.is_empty() { - let bounded_line = { - let mut tail = recent_stderr.lock().await; - push_stderr_tail_line(&mut tail, trimmed) - }; - if bounded_line.is_empty() { - continue; - } - warn!("js_repl stderr: {bounded_line}"); - } - } - } -} - -fn emitted_image_content_item( - turn: &TurnContext, - image_url: String, - detail: Option, -) -> FunctionCallOutputContentItem { - FunctionCallOutputContentItem::InputImage { - image_url, - detail: normalize_output_image_detail(&turn.model_info, detail) - .or(Some(DEFAULT_IMAGE_DETAIL)), - } -} - -fn validate_emitted_image_url(image_url: &str) -> Result<(), String> { - if !image_url - .get(..5) - .is_some_and(|scheme| scheme.eq_ignore_ascii_case("data:")) - { - return Err("codex.emitImage only accepts data URLs".to_string()); - } - - let media_type = image_url - .split_once(',') - .and_then(|(header, _)| header.get(5..)) - .and_then(|header| header.split(';').next()) - .filter(|media_type| !media_type.is_empty()) - .ok_or_else(|| "codex.emitImage expected a valid image data URL".to_string())?; - - if matches!( - media_type.to_ascii_lowercase().as_str(), - "image/png" | "image/jpeg" | "image/webp" | "image/gif" - ) { - Ok(()) - } else { - Err( - "codex.emitImage only supports image/png, image/jpeg, image/webp, or image/gif" - .to_string(), - ) - } -} - -fn build_exec_result_content_items( - output: String, - content_items: Vec, -) -> Vec { - let mut all_content_items = Vec::with_capacity(content_items.len() + 1); - all_content_items.push(FunctionCallOutputContentItem::InputText { text: output }); - all_content_items.extend(content_items); - all_content_items -} - -fn split_exec_result_content_items( - mut content_items: Vec, -) -> (String, Vec) { - match content_items.first() { - Some(FunctionCallOutputContentItem::InputText { .. }) => { - let FunctionCallOutputContentItem::InputText { text } = content_items.remove(0) else { - unreachable!("first content item should be input_text"); - }; - (text, content_items) - } - Some(FunctionCallOutputContentItem::InputImage { .. }) | None => { - (String::new(), content_items) - } - } -} - -fn is_js_repl_internal_tool(name: &str) -> bool { - matches!(name, "js_repl" | "js_repl_reset") -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum KernelToHost { - ExecResult { - id: String, - ok: bool, - output: String, - #[serde(default)] - error: Option, - }, - RunTool(RunToolRequest), - EmitImage(EmitImageRequest), -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum HostToKernel { - Exec { - id: String, - code: String, - #[serde(default)] - timeout_ms: Option, - }, - RunToolResult(RunToolResult), - EmitImageResult(EmitImageResult), -} - -#[derive(Clone, Debug, Deserialize)] -struct RunToolRequest { - id: String, - exec_id: String, - tool_name: String, - arguments: String, -} - -#[derive(Clone, Debug, Serialize)] -struct RunToolResult { - id: String, - ok: bool, - #[serde(default)] - response: Option, - #[serde(default)] - error: Option, -} - -#[derive(Clone, Debug, Deserialize)] -struct EmitImageRequest { - id: String, - exec_id: String, - image_url: String, - #[serde(default)] - detail: Option, -} - -#[derive(Clone, Debug, Serialize)] -struct EmitImageResult { - id: String, - ok: bool, - #[serde(default)] - error: Option, -} - -#[derive(Debug)] -enum ExecResultMessage { - Ok { - content_items: Vec, - }, - Err { - message: String, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct NodeVersion { - major: u64, - minor: u64, - patch: u64, -} - -impl fmt::Display for NodeVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) - } -} - -impl NodeVersion { - fn parse(input: &str) -> Result { - let trimmed = input.trim().trim_start_matches('v'); - let mut parts = trimmed.split(['.', '-', '+']); - let major = parts - .next() - .ok_or_else(|| "missing major version".to_string())? - .parse::() - .map_err(|err| format!("invalid major version: {err}"))?; - let minor = parts - .next() - .ok_or_else(|| "missing minor version".to_string())? - .parse::() - .map_err(|err| format!("invalid minor version: {err}"))?; - let patch = parts - .next() - .ok_or_else(|| "missing patch version".to_string())? - .parse::() - .map_err(|err| format!("invalid patch version: {err}"))?; - Ok(Self { - major, - minor, - patch, - }) - } -} - -fn required_node_version() -> Result { - NodeVersion::parse(JS_REPL_MIN_NODE_VERSION) -} - -async fn read_node_version(node_path: &Path) -> Result { - let output = tokio::process::Command::new(node_path) - .arg("--version") - .output() - .await - .map_err(|err| format!("failed to execute Node: {err}"))?; - - if !output.status.success() { - let mut details = String::new(); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = stdout.trim(); - let stderr = stderr.trim(); - if !stdout.is_empty() { - details.push_str(" stdout: "); - details.push_str(stdout); - } - if !stderr.is_empty() { - details.push_str(" stderr: "); - details.push_str(stderr); - } - let details = if details.is_empty() { - String::new() - } else { - format!(" ({details})") - }; - return Err(format!( - "failed to read Node version (status {status}){details}", - status = output.status - )); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - let stdout = stdout.trim(); - NodeVersion::parse(stdout) - .map_err(|err| format!("failed to parse Node version output `{stdout}`: {err}")) -} - -async fn ensure_node_version(node_path: &Path) -> Result<(), String> { - let required = required_node_version()?; - let found = read_node_version(node_path).await?; - if found < required { - return Err(format!( - "Node runtime too old for js_repl (resolved {node_path}): found v{found}, requires >= v{required}. Install/update Node or set js_repl_node_path to a newer runtime.", - node_path = node_path.display() - )); - } - Ok(()) -} - -pub(crate) async fn resolve_compatible_node(config_path: Option<&Path>) -> Result { - let node_path = resolve_node(config_path).ok_or_else(|| { - "Node runtime not found; install Node or set CODEX_JS_REPL_NODE_PATH".to_string() - })?; - ensure_node_version(&node_path).await?; - Ok(node_path) -} - -pub(crate) fn resolve_node(config_path: Option<&Path>) -> Option { - if let Some(path) = std::env::var_os("CODEX_JS_REPL_NODE_PATH") { - let p = PathBuf::from(path); - if p.exists() { - return Some(p); - } - } - - if let Some(path) = config_path - && path.exists() - { - return Some(path.to_path_buf()); - } - - if let Ok(path) = which::which("node") { - return Some(path); - } - - None -} - -#[cfg(test)] -#[path = "mod_tests.rs"] -mod tests; diff --git a/codex-rs/core/src/tools/js_repl/mod_tests.rs b/codex-rs/core/src/tools/js_repl/mod_tests.rs deleted file mode 100644 index 38bd71e1a33e..000000000000 --- a/codex-rs/core/src/tools/js_repl/mod_tests.rs +++ /dev/null @@ -1,2912 +0,0 @@ -use super::*; -use crate::session::tests::make_session_and_context; -use crate::session::tests::make_session_and_context_with_dynamic_tools_and_rx; -use crate::turn_diff_tracker::TurnDiffTracker; -use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem; -use codex_protocol::dynamic_tools::DynamicToolResponse; -use codex_protocol::dynamic_tools::DynamicToolSpec; -use codex_protocol::models::DEFAULT_IMAGE_DETAIL; -use codex_protocol::models::FunctionCallOutputContentItem; -use codex_protocol::models::FunctionCallOutputPayload; -use codex_protocol::models::ImageDetail; -use codex_protocol::models::ResponseInputItem; -use codex_protocol::openai_models::InputModality; -use codex_protocol::permissions::FileSystemSandboxPolicy; -use codex_protocol::permissions::NetworkSandboxPolicy; -use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::EventMsg; -use codex_protocol::protocol::SandboxPolicy; -use core_test_support::PathBufExt; -use core_test_support::TempDirExt; -use pretty_assertions::assert_eq; -use std::fs; -use std::path::Path; -use tempfile::tempdir; - -fn set_danger_full_access(turn: &mut crate::session::turn_context::TurnContext) { - turn.sandbox_policy - .set(SandboxPolicy::DangerFullAccess) - .expect("test setup should allow updating sandbox policy"); - turn.file_system_sandbox_policy = FileSystemSandboxPolicy::from(turn.sandbox_policy.get()); - turn.network_sandbox_policy = NetworkSandboxPolicy::from(turn.sandbox_policy.get()); -} - -#[test] -fn node_version_parses_v_prefix_and_suffix() { - let version = NodeVersion::parse("v25.1.0-nightly.2024").unwrap(); - assert_eq!( - version, - NodeVersion { - major: 25, - minor: 1, - patch: 0, - } - ); -} - -#[test] -fn truncate_utf8_prefix_by_bytes_preserves_character_boundaries() { - let input = "aé🙂z"; - assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 0), ""); - assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 1), "a"); - assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 2), "a"); - assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 3), "aé"); - assert_eq!(truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 6), "aé"); - assert_eq!( - truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 7), - "aé🙂" - ); - assert_eq!( - truncate_utf8_prefix_by_bytes(input, /*max_bytes*/ 8), - "aé🙂z" - ); -} - -#[test] -fn stderr_tail_applies_line_and_byte_limits() { - let mut lines = VecDeque::new(); - let per_line_cap = JS_REPL_STDERR_TAIL_LINE_MAX_BYTES.min(JS_REPL_STDERR_TAIL_MAX_BYTES); - let long = "x".repeat(per_line_cap + 128); - let bounded = push_stderr_tail_line(&mut lines, &long); - assert_eq!(bounded.len(), per_line_cap); - - for i in 0..50 { - let line = format!("line-{i}-{}", "y".repeat(200)); - push_stderr_tail_line(&mut lines, &line); - } - - assert!(lines.len() <= JS_REPL_STDERR_TAIL_LINE_LIMIT); - assert!(lines.iter().all(|line| line.len() <= per_line_cap)); - assert!(stderr_tail_formatted_bytes(&lines) <= JS_REPL_STDERR_TAIL_MAX_BYTES); - assert_eq!( - format_stderr_tail(&lines).len(), - stderr_tail_formatted_bytes(&lines) - ); -} - -#[test] -fn model_kernel_failure_details_are_structured_and_truncated() { - let snapshot = KernelDebugSnapshot { - pid: Some(42), - status: "exited(code=1)".to_string(), - stderr_tail: "s".repeat(JS_REPL_MODEL_DIAG_STDERR_MAX_BYTES + 400), - }; - let stream_error = "e".repeat(JS_REPL_MODEL_DIAG_ERROR_MAX_BYTES + 200); - let message = with_model_kernel_failure_message( - "js_repl kernel exited unexpectedly", - "stdout_eof", - Some(&stream_error), - &snapshot, - ); - assert!(message.starts_with("js_repl kernel exited unexpectedly\n\njs_repl diagnostics: ")); - let (_prefix, encoded) = message - .split_once("js_repl diagnostics: ") - .expect("diagnostics suffix should be present"); - let parsed: serde_json::Value = - serde_json::from_str(encoded).expect("diagnostics should be valid json"); - assert_eq!( - parsed.get("reason").and_then(|v| v.as_str()), - Some("stdout_eof") - ); - assert_eq!( - parsed.get("kernel_pid").and_then(serde_json::Value::as_u64), - Some(42) - ); - assert_eq!( - parsed.get("kernel_status").and_then(|v| v.as_str()), - Some("exited(code=1)") - ); - assert!( - parsed - .get("kernel_stderr_tail") - .and_then(|v| v.as_str()) - .expect("kernel_stderr_tail should be present") - .len() - <= JS_REPL_MODEL_DIAG_STDERR_MAX_BYTES - ); - assert!( - parsed - .get("stream_error") - .and_then(|v| v.as_str()) - .expect("stream_error should be present") - .len() - <= JS_REPL_MODEL_DIAG_ERROR_MAX_BYTES - ); -} - -#[test] -fn write_error_diagnostics_only_attach_for_likely_kernel_failures() { - let running = KernelDebugSnapshot { - pid: Some(7), - status: "running".to_string(), - stderr_tail: "".to_string(), - }; - let exited = KernelDebugSnapshot { - pid: Some(7), - status: "exited(code=1)".to_string(), - stderr_tail: "".to_string(), - }; - assert!(!should_include_model_diagnostics_for_write_error( - "failed to flush kernel message: other io error", - &running - )); - assert!(should_include_model_diagnostics_for_write_error( - "failed to write to kernel: Broken pipe (os error 32)", - &running - )); - assert!(should_include_model_diagnostics_for_write_error( - "failed to write to kernel: some other io error", - &exited - )); -} - -#[test] -fn js_repl_internal_tool_guard_matches_expected_names() { - assert!(is_js_repl_internal_tool("js_repl")); - assert!(is_js_repl_internal_tool("js_repl_reset")); - assert!(!is_js_repl_internal_tool("shell_command")); - assert!(!is_js_repl_internal_tool("list_mcp_resources")); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn wait_for_exec_tool_calls_map_drains_inflight_calls_without_hanging() { - let exec_tool_calls = Arc::new(Mutex::new(HashMap::new())); - - for _ in 0..128 { - let exec_id = Uuid::new_v4().to_string(); - exec_tool_calls - .lock() - .await - .insert(exec_id.clone(), ExecToolCalls::default()); - assert!( - JsReplManager::begin_exec_tool_call(&exec_tool_calls, &exec_id) - .await - .is_some() - ); - - let wait_map = Arc::clone(&exec_tool_calls); - let wait_exec_id = exec_id.clone(); - let waiter = tokio::spawn(async move { - JsReplManager::wait_for_exec_tool_calls_map(&wait_map, &wait_exec_id).await; - }); - - let finish_map = Arc::clone(&exec_tool_calls); - let finish_exec_id = exec_id.clone(); - let finisher = tokio::spawn(async move { - tokio::task::yield_now().await; - JsReplManager::finish_exec_tool_call(&finish_map, &finish_exec_id).await; - }); - - tokio::time::timeout(Duration::from_secs(1), waiter) - .await - .expect("wait_for_exec_tool_calls_map should not hang") - .expect("wait task should not panic"); - finisher.await.expect("finish task should not panic"); - - JsReplManager::clear_exec_tool_calls_map(&exec_tool_calls, &exec_id).await; - } -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn reset_waits_for_exec_lock_before_clearing_exec_tool_calls() { - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let permit = manager - .exec_lock - .clone() - .acquire_owned() - .await - .expect("lock should be acquirable"); - let exec_id = Uuid::new_v4().to_string(); - manager.register_exec_tool_calls(&exec_id).await; - - let reset_manager = Arc::clone(&manager); - let mut reset_task = tokio::spawn(async move { reset_manager.reset().await }); - tokio::time::sleep(Duration::from_millis(50)).await; - - assert!( - !reset_task.is_finished(), - "reset should wait until execute lock is released" - ); - assert!( - manager.exec_tool_calls.lock().await.contains_key(&exec_id), - "reset must not clear tool-call contexts while execute lock is held" - ); - - drop(permit); - - tokio::time::timeout(Duration::from_secs(1), &mut reset_task) - .await - .expect("reset should complete after execute lock release") - .expect("reset task should not panic") - .expect("reset should succeed"); - assert!( - !manager.exec_tool_calls.lock().await.contains_key(&exec_id), - "reset should clear tool-call contexts after lock acquisition" - ); -} - -#[test] -fn summarize_tool_call_response_for_multimodal_function_output() { - let response = ResponseInputItem::FunctionCallOutput { - call_id: "call-1".to_string(), - output: FunctionCallOutputPayload::from_content_items(vec![ - FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,abcd".to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - ]), - }; - - let actual = JsReplManager::summarize_tool_call_response(&response); - - assert_eq!( - actual, - JsReplToolCallResponseSummary { - response_type: Some("function_call_output".to_string()), - payload_kind: Some(JsReplToolCallPayloadKind::FunctionContentItems), - payload_text_preview: None, - payload_text_length: None, - payload_item_count: Some(1), - text_item_count: Some(0), - image_item_count: Some(1), - structured_content_present: None, - result_is_error: None, - } - ); -} - -#[tokio::test] -async fn emitted_image_content_item_preserves_explicit_non_original_detail() { - let (_session, turn) = make_session_and_context().await; - let content_item = emitted_image_content_item( - &turn, - "data:image/png;base64,AAA".to_string(), - Some(ImageDetail::Low), - ); - assert_eq!( - content_item, - FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,AAA".to_string(), - detail: Some(ImageDetail::Low), - } - ); -} - -#[tokio::test] -async fn emitted_image_content_item_allows_explicit_original_detail_when_supported() { - let (_session, mut turn) = make_session_and_context().await; - turn.model_info.supports_image_detail_original = true; - - let content_item = emitted_image_content_item( - &turn, - "data:image/png;base64,AAA".to_string(), - Some(ImageDetail::Original), - ); - - assert_eq!( - content_item, - FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,AAA".to_string(), - detail: Some(ImageDetail::Original), - } - ); -} - -#[tokio::test] -async fn emitted_image_content_item_defaults_to_high_for_unsupported_original_detail() { - let (_session, turn) = make_session_and_context().await; - - let content_item = emitted_image_content_item( - &turn, - "data:image/png;base64,AAA".to_string(), - Some(ImageDetail::Original), - ); - - assert_eq!( - content_item, - FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,AAA".to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - } - ); -} - -#[test] -fn validate_emitted_image_url_accepts_case_insensitive_data_scheme() { - assert_eq!( - validate_emitted_image_url("DATA:image/png;base64,AAA"), - Ok(()) - ); -} - -#[test] -fn validate_emitted_image_url_rejects_non_data_scheme() { - assert_eq!( - validate_emitted_image_url("https://example.com/image.png"), - Err("codex.emitImage only accepts data URLs".to_string()) - ); -} - -#[test] -fn summarize_tool_call_response_for_multimodal_custom_output() { - let response = ResponseInputItem::CustomToolCallOutput { - call_id: "call-1".to_string(), - name: None, - output: FunctionCallOutputPayload::from_content_items(vec![ - FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,abcd".to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - ]), - }; - - let actual = JsReplManager::summarize_tool_call_response(&response); - - assert_eq!( - actual, - JsReplToolCallResponseSummary { - response_type: Some("custom_tool_call_output".to_string()), - payload_kind: Some(JsReplToolCallPayloadKind::CustomContentItems), - payload_text_preview: None, - payload_text_length: None, - payload_item_count: Some(1), - text_item_count: Some(0), - image_item_count: Some(1), - structured_content_present: None, - result_is_error: None, - } - ); -} - -#[test] -fn summarize_tool_call_error_marks_error_payload() { - let actual = JsReplManager::summarize_tool_call_error("tool failed"); - - assert_eq!( - actual, - JsReplToolCallResponseSummary { - response_type: None, - payload_kind: Some(JsReplToolCallPayloadKind::Error), - payload_text_preview: Some("tool failed".to_string()), - payload_text_length: Some("tool failed".len()), - payload_item_count: None, - text_item_count: None, - image_item_count: None, - structured_content_present: None, - result_is_error: None, - } - ); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn reset_clears_inflight_exec_tool_calls_without_waiting() { - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let exec_id = Uuid::new_v4().to_string(); - manager.register_exec_tool_calls(&exec_id).await; - assert!( - JsReplManager::begin_exec_tool_call(&manager.exec_tool_calls, &exec_id) - .await - .is_some() - ); - - let wait_manager = Arc::clone(&manager); - let wait_exec_id = exec_id.clone(); - let waiter = tokio::spawn(async move { - wait_manager.wait_for_exec_tool_calls(&wait_exec_id).await; - }); - tokio::task::yield_now().await; - - tokio::time::timeout(Duration::from_secs(1), manager.reset()) - .await - .expect("reset should not hang") - .expect("reset should succeed"); - - tokio::time::timeout(Duration::from_secs(1), waiter) - .await - .expect("waiter should be released") - .expect("wait task should not panic"); - - assert!(manager.exec_tool_calls.lock().await.is_empty()); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn reset_aborts_inflight_exec_tool_tasks() { - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let exec_id = Uuid::new_v4().to_string(); - manager.register_exec_tool_calls(&exec_id).await; - let reset_cancel = JsReplManager::begin_exec_tool_call(&manager.exec_tool_calls, &exec_id) - .await - .expect("exec should be registered"); - - let task = tokio::spawn(async move { - tokio::select! { - _ = reset_cancel.cancelled() => "cancelled", - _ = tokio::time::sleep(Duration::from_secs(60)) => "timed_out", - } - }); - - tokio::time::timeout(Duration::from_secs(1), manager.reset()) - .await - .expect("reset should not hang") - .expect("reset should succeed"); - - let outcome = tokio::time::timeout(Duration::from_secs(1), task) - .await - .expect("cancelled task should resolve promptly") - .expect("task should not panic"); - assert_eq!(outcome, "cancelled"); -} - -async fn can_run_js_repl_runtime_tests() -> bool { - // These white-box runtime tests are required on macOS. Linux relies on - // the codex-linux-sandbox arg0 dispatch path, which is exercised in - // integration tests instead. - cfg!(target_os = "macos") -} -fn write_js_repl_test_package_source(base: &Path, name: &str, source: &str) -> anyhow::Result<()> { - let pkg_dir = base.join("node_modules").join(name); - fs::create_dir_all(&pkg_dir)?; - fs::write( - pkg_dir.join("package.json"), - format!( - "{{\n \"name\": \"{name}\",\n \"version\": \"1.0.0\",\n \"type\": \"module\",\n \"exports\": {{\n \"import\": \"./index.js\"\n }}\n}}\n" - ), - )?; - fs::write(pkg_dir.join("index.js"), source)?; - Ok(()) -} - -fn write_js_repl_test_package(base: &Path, name: &str, value: &str) -> anyhow::Result<()> { - write_js_repl_test_package_source(base, name, &format!("export const value = \"{value}\";\n"))?; - Ok(()) -} - -fn write_js_repl_test_module(base: &Path, relative: &str, contents: &str) -> anyhow::Result<()> { - let module_path = base.join(relative); - if let Some(parent) = module_path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(module_path, contents)?; - Ok(()) -} - -#[tokio::test] -async fn js_repl_timeout_does_not_deadlock() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = tokio::time::timeout( - Duration::from_secs(3), - manager.execute( - session, - turn, - tracker, - JsReplArgs { - code: "while (true) {}".to_string(), - timeout_ms: Some(50), - }, - ), - ) - .await - .expect("execute should return, not deadlock") - .expect_err("expected timeout error"); - - assert_eq!( - result.to_string(), - "js_repl execution timed out; kernel reset, rerun your request" - ); - Ok(()) -} - -#[tokio::test] -async fn js_repl_timeout_kills_kernel_process() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "console.log('warmup');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - - let child = { - let guard = manager.kernel.lock().await; - let state = guard.as_ref().expect("kernel should exist after warmup"); - Arc::clone(&state.child) - }; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "while (true) {}".to_string(), - timeout_ms: Some(50), - }, - ) - .await - .expect_err("expected timeout error"); - - assert_eq!( - result.to_string(), - "js_repl execution timed out; kernel reset, rerun your request" - ); - - let exit_state = { - let mut child = child.lock().await; - child.try_wait()? - }; - assert!( - exit_state.is_some(), - "timed out js_repl execution should kill previous kernel process" - ); - Ok(()) -} - -#[tokio::test] -async fn interrupt_turn_exec_clears_matching_submitted_exec() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let (_session, turn) = make_session_and_context().await; - let turn = Arc::new(turn); - let dependency_env = HashMap::new(); - let mut state = manager - .start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None) - .await - .map_err(anyhow::Error::msg)?; - let child = Arc::clone(&state.child); - state.top_level_exec_state = TopLevelExecState::Submitted { - turn_id: turn.sub_id.clone(), - exec_id: "exec-1".to_string(), - }; - *manager.kernel.lock().await = Some(state); - manager.register_exec_tool_calls("exec-1").await; - - assert!(manager.interrupt_turn_exec(&turn.sub_id).await?); - assert!(manager.kernel.lock().await.is_none()); - assert!(manager.exec_tool_calls.lock().await.is_empty()); - - tokio::time::timeout(Duration::from_secs(3), async { - loop { - let exited = { - let mut child = child.lock().await; - child.try_wait()?.is_some() - }; - if exited { - return Ok::<(), anyhow::Error>(()); - } - tokio::time::sleep(Duration::from_millis(25)).await; - } - }) - .await - .expect("kernel should exit after interrupt cleanup")?; - - Ok(()) -} - -#[tokio::test] -async fn interrupt_turn_exec_resets_matching_pending_kernel_start() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let (_session, turn) = make_session_and_context().await; - let turn = Arc::new(turn); - let dependency_env = HashMap::new(); - let mut state = manager - .start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None) - .await - .map_err(anyhow::Error::msg)?; - state.top_level_exec_state = TopLevelExecState::FreshKernel { - turn_id: turn.sub_id.clone(), - exec_id: None, - }; - let child = Arc::clone(&state.child); - *manager.kernel.lock().await = Some(state); - - assert!(manager.interrupt_turn_exec(&turn.sub_id).await?); - assert!(manager.kernel.lock().await.is_none()); - - tokio::time::timeout(Duration::from_secs(3), async { - loop { - let exited = { - let mut child = child.lock().await; - child.try_wait()?.is_some() - }; - if exited { - return Ok::<(), anyhow::Error>(()); - } - tokio::time::sleep(Duration::from_millis(25)).await; - } - }) - .await - .expect("kernel should exit after interrupt cleanup")?; - - Ok(()) -} - -#[tokio::test] -async fn interrupt_turn_exec_does_not_reset_reused_kernel_before_submit() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let manager = JsReplManager::new(/*node_path*/ None, Vec::new()) - .await - .expect("manager should initialize"); - let (_session, turn) = make_session_and_context().await; - let turn = Arc::new(turn); - let dependency_env = HashMap::new(); - let mut state = manager - .start_kernel(Arc::clone(&turn), &dependency_env, /*thread_id*/ None) - .await - .map_err(anyhow::Error::msg)?; - state.top_level_exec_state = TopLevelExecState::ReusedKernelPending { - turn_id: turn.sub_id.clone(), - exec_id: "exec-1".to_string(), - }; - *manager.kernel.lock().await = Some(state); - - assert!(!manager.interrupt_turn_exec(&turn.sub_id).await?); - assert!(manager.kernel.lock().await.is_some()); - - manager.reset().await.map_err(anyhow::Error::msg) -} - -#[tokio::test] -async fn interrupt_active_exec_stops_aborted_kernel_before_later_exec() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let dir = tempdir()?; - let (session, mut turn) = make_session_and_context().await; - turn.cwd = dir.abs(); - set_danger_full_access(&mut turn); - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let first_file = dir.path().join("1.txt"); - let second_file = dir.path().join("2.txt"); - let first_file_js = serde_json::to_string(&first_file.to_string_lossy().to_string())?; - let second_file_js = serde_json::to_string(&second_file.to_string_lossy().to_string())?; - let code = format!( - r#" -const {{ promises: fs }} = await import("fs"); - -const paths = [{first_file_js}, {second_file_js}]; -for (let i = 0; i < paths.length; i++) {{ - await fs.writeFile(paths[i], `${{i + 1}}`); - if (i + 1 < paths.length) {{ - await new Promise((resolve) => setTimeout(resolve, 1000)); - }} -}} -"# - ); - - let handle = tokio::spawn({ - let manager = Arc::clone(&manager); - let session = Arc::clone(&session); - let turn = Arc::clone(&turn); - let tracker = Arc::clone(&tracker); - async move { - manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code, - timeout_ms: Some(15_000), - }, - ) - .await - } - }); - - tokio::time::timeout(Duration::from_secs(3), async { - while !first_file.exists() { - tokio::time::sleep(Duration::from_millis(25)).await; - } - }) - .await - .expect("first file should be written before interrupt"); - - let child = { - let guard = manager.kernel.lock().await; - let state = guard - .as_ref() - .expect("kernel should exist while exec is running"); - Arc::clone(&state.child) - }; - - handle.abort(); - assert!(manager.interrupt_turn_exec(&turn.sub_id).await?); - - tokio::time::timeout(Duration::from_secs(3), async { - loop { - let exited = { - let mut child = child.lock().await; - child.try_wait()?.is_some() - }; - if exited { - return Ok::<(), anyhow::Error>(()); - } - tokio::time::sleep(Duration::from_millis(25)).await; - } - }) - .await - .expect("kernel should exit after interrupt")?; - - tokio::time::sleep(Duration::from_millis(1500)).await; - assert!(first_file.exists()); - assert!(!second_file.exists()); - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "console.log('after interrupt');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("after interrupt")); - - Ok(()) -} - -#[tokio::test] -async fn js_repl_forced_kernel_exit_recovers_on_next_exec() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "console.log('warmup');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - - let child = { - let guard = manager.kernel.lock().await; - let state = guard.as_ref().expect("kernel should exist after warmup"); - Arc::clone(&state.child) - }; - JsReplManager::kill_kernel_child(&child, "test_crash").await; - tokio::time::timeout(Duration::from_secs(1), async { - loop { - let cleared = { - let guard = manager.kernel.lock().await; - guard - .as_ref() - .is_none_or(|state| !Arc::ptr_eq(&state.child, &child)) - }; - if cleared { - return; - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("host should clear dead kernel state promptly"); - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "console.log('after-kill');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("after-kill")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_uncaught_exception_returns_exec_error_and_recovers() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = crate::session::tests::make_session_and_context().await; - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "console.log('warmup');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - - let child = { - let guard = manager.kernel.lock().await; - let state = guard.as_ref().expect("kernel should exist after warmup"); - Arc::clone(&state.child) - }; - - let err = tokio::time::timeout( - Duration::from_secs(3), - manager.execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "setTimeout(() => { throw new Error('boom'); }, 0);\nawait new Promise(() => {});".to_string(), - timeout_ms: Some(10_000), - }, - ), - ) - .await - .expect("uncaught exception should fail promptly") - .expect_err("expected uncaught exception to fail the exec"); - - let message = err.to_string(); - assert!(message.contains("js_repl kernel uncaught exception: boom")); - assert!(message.contains("kernel reset.")); - assert!(message.contains("Catch or handle async errors")); - assert!(!message.contains("js_repl kernel exited unexpectedly")); - - tokio::time::timeout(Duration::from_secs(1), async { - loop { - let exited = { - let mut child = child.lock().await; - child.try_wait()?.is_some() - }; - if exited { - return Ok::<(), anyhow::Error>(()); - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("uncaught exception should terminate the previous kernel process")?; - - tokio::time::timeout(Duration::from_secs(1), async { - loop { - let cleared = { - let guard = manager.kernel.lock().await; - guard - .as_ref() - .is_none_or(|state| !Arc::ptr_eq(&state.child, &child)) - }; - if cleared { - return; - } - tokio::time::sleep(Duration::from_millis(10)).await; - } - }) - .await - .expect("host should clear dead kernel state promptly"); - - let next = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "console.log('after reset');".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(next.output.contains("after reset")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_waits_for_unawaited_tool_calls_before_completion() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, mut turn) = make_session_and_context().await; - turn.approval_policy - .set(AskForApproval::Never) - .expect("test setup should allow updating approval policy"); - set_danger_full_access(&mut turn); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let marker = turn - .cwd - .join(format!("js-repl-unawaited-marker-{}.txt", Uuid::new_v4())); - let marker_json = serde_json::to_string(&marker.to_string_lossy().to_string())?; - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: format!( - r#" -const marker = {marker_json}; -void codex.tool("shell_command", {{ command: `sleep 0.35; printf js_repl_unawaited_done > "${{marker}}"` }}); -console.log("cell-complete"); -"# - ), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("cell-complete")); - let marker_contents = tokio::fs::read_to_string(&marker).await?; - assert_eq!(marker_contents, "js_repl_unawaited_done"); - let _ = tokio::fs::remove_file(&marker).await; - Ok(()) -} - -#[tokio::test] -async fn js_repl_persisted_tool_helpers_work_across_cells() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, mut turn) = make_session_and_context().await; - turn.approval_policy - .set(AskForApproval::Never) - .expect("test setup should allow updating approval policy"); - set_danger_full_access(&mut turn); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let global_marker = turn - .cwd - .join(format!("js-repl-global-helper-{}.txt", Uuid::new_v4())); - let lexical_marker = turn - .cwd - .join(format!("js-repl-lexical-helper-{}.txt", Uuid::new_v4())); - let global_marker_json = serde_json::to_string(&global_marker.to_string_lossy().to_string())?; - let lexical_marker_json = serde_json::to_string(&lexical_marker.to_string_lossy().to_string())?; - - manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: format!( - r#" -const globalMarker = {global_marker_json}; -const lexicalMarker = {lexical_marker_json}; -const savedTool = codex.tool; -globalThis.globalToolHelper = {{ - run: () => savedTool("shell_command", {{ command: `printf global_helper > "${{globalMarker}}"` }}), -}}; -const lexicalToolHelper = {{ - run: () => savedTool("shell_command", {{ command: `printf lexical_helper > "${{lexicalMarker}}"` }}), -}}; -"# - ), - timeout_ms: Some(10_000), - }, - ) - .await?; - - let next = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - tracker, - JsReplArgs { - code: r#" -await globalToolHelper.run(); -await lexicalToolHelper.run(); -console.log("helpers-ran"); -"# - .to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - - assert!(next.output.contains("helpers-ran")); - assert_eq!( - tokio::fs::read_to_string(&global_marker).await?, - "global_helper" - ); - assert_eq!( - tokio::fs::read_to_string(&lexical_marker).await?, - "lexical_helper" - ); - let _ = tokio::fs::remove_file(&global_marker).await; - let _ = tokio::fs::remove_file(&lexical_marker).await; - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_does_not_auto_attach_image_via_view_image_tool() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, mut turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - turn.approval_policy - .set(AskForApproval::Never) - .expect("test setup should allow updating approval policy"); - set_danger_full_access(&mut turn); - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const fs = await import("node:fs/promises"); -const path = await import("node:path"); -const imagePath = path.join(codex.tmpDir, "js-repl-view-image.png"); -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await fs.writeFile(imagePath, png); -const out = await codex.tool("view_image", { path: imagePath }); -console.log(out.type); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert!(result.output.contains("function_call_output")); - assert!(result.content_items.is_empty()); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_emit_image_via_view_image_tool() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, mut turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - turn.approval_policy - .set(AskForApproval::Never) - .expect("test setup should allow updating approval policy"); - set_danger_full_access(&mut turn); - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const fs = await import("node:fs/promises"); -const path = await import("node:path"); -const imagePath = path.join(codex.tmpDir, "js-repl-view-image-explicit.png"); -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await fs.writeFile(imagePath, png); -const out = await codex.tool("view_image", { path: imagePath }); -await codex.emitImage(out); -console.log(out.type); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert!(result.output.contains("function_call_output")); - assert_eq!( - result.content_items.as_slice(), - [FunctionCallOutputContentItem::InputImage { - image_url: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" - .to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_emit_image_from_bytes_and_mime_type() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await codex.emitImage({ bytes: png, mimeType: "image/png" }); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert_eq!( - result.content_items.as_slice(), - [FunctionCallOutputContentItem::InputImage { - image_url: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" - .to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_emit_multiple_images_in_one_cell() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -await codex.emitImage( - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" -); -await codex.emitImage( - "data:image/gif;base64,R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=" -); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert_eq!( - result.content_items.as_slice(), - [ - FunctionCallOutputContentItem::InputImage { - image_url: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" - .to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - FunctionCallOutputContentItem::InputImage { - image_url: - "data:image/gif;base64,R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=" - .to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - ] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_waits_for_unawaited_emit_image_before_completion() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -void codex.emitImage( - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" -); -console.log("cell-complete"); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert!(result.output.contains("cell-complete")); - assert_eq!( - result.content_items.as_slice(), - [FunctionCallOutputContentItem::InputImage { - image_url: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==" - .to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_persisted_emit_image_helpers_work_across_cells() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let data_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="; - - manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: format!( - r#" -const dataUrl = "{data_url}"; -const savedEmitImage = codex.emitImage; -globalThis.globalEmitHelper = {{ - run: () => savedEmitImage(dataUrl), -}}; -const lexicalEmitHelper = {{ - run: () => savedEmitImage(dataUrl), -}}; -"# - ), - timeout_ms: Some(15_000), - }, - ) - .await?; - - let next = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - tracker, - JsReplArgs { - code: r#" -await globalEmitHelper.run(); -await lexicalEmitHelper.run(); -console.log("helpers-ran"); -"# - .to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - - assert!(next.output.contains("helpers-ran")); - assert_eq!( - next.content_items, - vec![ - FunctionCallOutputContentItem::InputImage { - image_url: data_url.to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - FunctionCallOutputContentItem::InputImage { - image_url: data_url.to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }, - ] - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_unawaited_emit_image_errors_fail_cell() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -void codex.emitImage({ bytes: new Uint8Array(), mimeType: "image/png" }); -console.log("cell-complete"); -"#; - - let err = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await - .expect_err("unawaited invalid emitImage should fail"); - assert!(err.to_string().contains("expected non-empty bytes")); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_caught_emit_image_error_does_not_fail_cell() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -try { - await codex.emitImage({ bytes: new Uint8Array(), mimeType: "image/png" }); -} catch (error) { - console.log(error.message); -} -console.log("cell-complete"); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert!(result.output.contains("expected non-empty bytes")); - assert!(result.output.contains("cell-complete")); - assert!(result.content_items.is_empty()); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_requires_explicit_mime_type_for_bytes() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await codex.emitImage({ bytes: png }); -"#; - - let err = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await - .expect_err("missing mimeType should fail"); - assert!(err.to_string().contains("expected a non-empty mimeType")); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_rejects_unsupported_byte_mime_type() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -await codex.emitImage({ - bytes: Buffer.from([255, 0, 0, 255]), - mimeType: "image/rgba", -}); -"#; - - let err = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await - .expect_err("unsupported byte MIME type should fail"); - assert!( - err.to_string() - .contains("only supports image/png, image/jpeg, image/webp, or image/gif") - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_rejects_non_data_url() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -await codex.emitImage("https://example.com/image.png"); -"#; - - let err = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await - .expect_err("non-data URLs should fail"); - assert!(err.to_string().contains("only accepts data URLs")); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[test] -fn validate_emitted_image_url_rejects_unsupported_mime_type() { - assert_eq!( - validate_emitted_image_url("data:image/rgba;base64,AAAA").expect_err("unsupported MIME"), - "codex.emitImage only supports image/png, image/jpeg, image/webp, or image/gif" - ); -} - -#[test] -fn validate_emitted_image_url_accepts_supported_mime_type_case_insensitive() { - assert!(validate_emitted_image_url("DATA:image/PNG;base64,AAAA").is_ok()); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_accepts_case_insensitive_data_url() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -await codex.emitImage("DATA:image/png;base64,AAA"); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert_eq!( - result.content_items.as_slice(), - [FunctionCallOutputContentItem::InputImage { - image_url: "DATA:image/png;base64,AAA".to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_rejects_invalid_detail() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await codex.emitImage({ bytes: png, mimeType: "image/png", detail: "ultra" }); -"#; - - let err = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await - .expect_err("invalid detail should fail"); - assert!(err.to_string().contains("expected detail to be one of")); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_treats_null_detail_as_omitted() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - let session = Arc::new(session); - let turn = Arc::new(turn); - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await codex.emitImage({ bytes: png, mimeType: "image/png", detail: null }); -"#; - - let result = manager - .execute( - Arc::clone(&session), - turn, - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ) - .await?; - assert_eq!( - result.content_items.as_slice(), - [FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==".to_string(), - detail: Some(DEFAULT_IMAGE_DETAIL), - }] - .as_slice() - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_rejects_mixed_content() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn, rx_event) = - make_session_and_context_with_dynamic_tools_and_rx(vec![DynamicToolSpec { - namespace: None, - name: "inline_image".to_string(), - description: "Returns inline text and image content.".to_string(), - input_schema: serde_json::json!({ - "type": "object", - "properties": {}, - "additionalProperties": false - }), - defer_loading: false, - }]) - .await; - if !turn - .model_info - .input_modalities - .contains(&InputModality::Image) - { - return Ok(()); - } - - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const out = await codex.tool("inline_image", {}); -await codex.emitImage(out); -"#; - let image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg=="; - - let session_for_response = Arc::clone(&session); - let response_watcher = async move { - loop { - let event = tokio::time::timeout(Duration::from_secs(2), rx_event.recv()).await??; - if let EventMsg::DynamicToolCallRequest(request) = event.msg { - session_for_response - .notify_dynamic_tool_response( - &request.call_id, - DynamicToolResponse { - content_items: vec![ - DynamicToolCallOutputContentItem::InputText { - text: "inline image note".to_string(), - }, - DynamicToolCallOutputContentItem::InputImage { - image_url: image_url.to_string(), - }, - ], - success: true, - }, - ) - .await; - return Ok::<(), anyhow::Error>(()); - } - } - }; - - let (result, response_watcher_result) = tokio::join!( - manager.execute( - Arc::clone(&session), - Arc::clone(&turn), - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ), - response_watcher, - ); - response_watcher_result?; - let err = result.expect_err("mixed content should fail"); - assert!( - err.to_string() - .contains("does not accept mixed text and image content") - ); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_dynamic_tool_response_preserves_js_line_separator_text() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - for (tool_name, description, expected_text, literal) in [ - ( - "line_separator_tool", - "Returns text containing U+2028.", - "alpha\u{2028}omega".to_string(), - r#""alpha\u2028omega""#, - ), - ( - "paragraph_separator_tool", - "Returns text containing U+2029.", - "alpha\u{2029}omega".to_string(), - r#""alpha\u2029omega""#, - ), - ] { - let (session, turn, rx_event) = - make_session_and_context_with_dynamic_tools_and_rx(vec![DynamicToolSpec { - namespace: None, - name: tool_name.to_string(), - description: description.to_string(), - input_schema: serde_json::json!({ - "type": "object", - "properties": {}, - "additionalProperties": false - }), - defer_loading: false, - }]) - .await; - - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = format!( - r#" -const out = await codex.tool("{tool_name}", {{}}); -const text = typeof out === "string" ? out : out?.output; -console.log(text === {literal}); -console.log(text); -"# - ); - - let session_for_response = Arc::clone(&session); - let expected_text_for_response = expected_text.clone(); - let response_watcher = async move { - loop { - let event = tokio::time::timeout(Duration::from_secs(2), rx_event.recv()).await??; - if let EventMsg::DynamicToolCallRequest(request) = event.msg { - session_for_response - .notify_dynamic_tool_response( - &request.call_id, - DynamicToolResponse { - content_items: vec![DynamicToolCallOutputContentItem::InputText { - text: expected_text_for_response.clone(), - }], - success: true, - }, - ) - .await; - return Ok::<(), anyhow::Error>(()); - } - } - }; - - let (result, response_watcher_result) = tokio::join!( - manager.execute( - Arc::clone(&session), - Arc::clone(&turn), - tracker, - JsReplArgs { - code, - timeout_ms: Some(15_000), - }, - ), - response_watcher, - ); - response_watcher_result?; - - let result = result?; - assert_eq!(result.output, format!("true\n{expected_text}")); - } - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_call_hidden_dynamic_tools() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn, rx_event) = - make_session_and_context_with_dynamic_tools_and_rx(vec![DynamicToolSpec { - namespace: Some("codex_app".to_string()), - name: "hidden_dynamic_tool".to_string(), - description: "A hidden dynamic tool.".to_string(), - input_schema: serde_json::json!({ - "type": "object", - "properties": { - "city": { "type": "string" } - }, - "required": ["city"], - "additionalProperties": false - }), - defer_loading: true, - }]) - .await; - - *session.active_turn.lock().await = Some(crate::state::ActiveTurn::default()); - - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - let code = r#" -const out = await codex.tool("codex_app_hidden_dynamic_tool", { city: "Paris" }); -console.log(JSON.stringify(out)); -"#; - - let session_for_response = Arc::clone(&session); - let response_watcher = async move { - loop { - let event = tokio::time::timeout(Duration::from_secs(2), rx_event.recv()).await??; - if let EventMsg::DynamicToolCallRequest(request) = event.msg { - session_for_response - .notify_dynamic_tool_response( - &request.call_id, - DynamicToolResponse { - content_items: vec![DynamicToolCallOutputContentItem::InputText { - text: "hidden-ok".to_string(), - }], - success: true, - }, - ) - .await; - return Ok::<(), anyhow::Error>(()); - } - } - }; - - let (result, response_watcher_result) = tokio::join!( - manager.execute( - Arc::clone(&session), - Arc::clone(&turn), - tracker, - JsReplArgs { - code: code.to_string(), - timeout_ms: Some(15_000), - }, - ), - response_watcher, - ); - - let result = result?; - response_watcher_result?; - assert!(result.output.contains("hidden-ok")); - assert!(session.get_pending_input().await.is_empty()); - - Ok(()) -} - -#[tokio::test] -async fn js_repl_prefers_env_node_module_dirs_over_config() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let env_base = tempdir()?; - write_js_repl_test_package(env_base.path(), "repl_probe", "env")?; - - let config_base = tempdir()?; - let cwd_dir = tempdir()?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy.r#set.insert( - "CODEX_JS_REPL_NODE_MODULE_DIRS".to_string(), - env_base.path().to_string_lossy().to_string(), - ); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - vec![config_base.path().to_path_buf()], - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "const mod = await import(\"repl_probe\"); console.log(mod.value);" - .to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("env")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_resolves_from_first_config_dir() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let first_base = tempdir()?; - let second_base = tempdir()?; - write_js_repl_test_package(first_base.path(), "repl_probe", "first")?; - write_js_repl_test_package(second_base.path(), "repl_probe", "second")?; - - let cwd_dir = tempdir()?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - vec![ - first_base.path().to_path_buf(), - second_base.path().to_path_buf(), - ], - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "const mod = await import(\"repl_probe\"); console.log(mod.value);" - .to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("first")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_falls_back_to_cwd_node_modules() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let config_base = tempdir()?; - let cwd_dir = tempdir()?; - write_js_repl_test_package(cwd_dir.path(), "repl_probe", "cwd")?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - vec![config_base.path().to_path_buf()], - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "const mod = await import(\"repl_probe\"); console.log(mod.value);" - .to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("cwd")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_accepts_node_modules_dir_entries() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let base_dir = tempdir()?; - let cwd_dir = tempdir()?; - write_js_repl_test_package(base_dir.path(), "repl_probe", "normalized")?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - vec![base_dir.path().join("node_modules")], - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "const mod = await import(\"repl_probe\"); console.log(mod.value);" - .to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("normalized")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_supports_relative_file_imports() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - write_js_repl_test_module( - cwd_dir.path(), - "child.js", - "export const value = \"child\";\n", - )?; - write_js_repl_test_module( - cwd_dir.path(), - "parent.js", - "import { value as childValue } from \"./child.js\";\nexport const value = `${childValue}-parent`;\n", - )?; - write_js_repl_test_module( - cwd_dir.path(), - "local.mjs", - "export const value = \"mjs\";\n", - )?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "const parent = await import(\"./parent.js\"); const other = await import(\"./local.mjs\"); console.log(parent.value); console.log(other.value);".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("child-parent")); - assert!(result.output.contains("mjs")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_supports_absolute_file_imports() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let module_dir = tempdir()?; - let cwd_dir = tempdir()?; - write_js_repl_test_module( - module_dir.path(), - "absolute.js", - "export const value = \"absolute\";\n", - )?; - let absolute_path_json = - serde_json::to_string(&module_dir.path().join("absolute.js").display().to_string())?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: format!( - "const mod = await import({absolute_path_json}); console.log(mod.value);" - ), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("absolute")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_imported_local_files_can_access_repl_globals() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - let expected_home_dir = serde_json::to_string("/tmp/codex-home")?; - write_js_repl_test_module( - cwd_dir.path(), - "globals.js", - &format!( - "const expectedHomeDir = {expected_home_dir};\nconsole.log(`tmp:${{codex.tmpDir === tmpDir}}`);\nconsole.log(`cwd:${{typeof codex.cwd}}:${{codex.cwd.length > 0}}`);\nconsole.log(`home:${{codex.homeDir === expectedHomeDir}}`);\nconsole.log(`tool:${{typeof codex.tool}}`);\nconsole.log(\"local-file-console-ok\");\n" - ), - )?; - - let (session, mut turn) = make_session_and_context().await; - session - .set_dependency_env(HashMap::from([( - "HOME".to_string(), - "/tmp/codex-home".to_string(), - )])) - .await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"./globals.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("tmp:true")); - assert!(result.output.contains("cwd:string:true")); - assert!(result.output.contains("home:true")); - assert!(result.output.contains("tool:function")); - assert!(result.output.contains("local-file-console-ok")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_reimports_local_files_after_edit() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - let helper_path = cwd_dir.path().join("helper.js"); - fs::write(&helper_path, "export const value = \"v1\";\n")?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let first = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "const { value: firstValue } = await import(\"./helper.js\");\nconsole.log(firstValue);".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(first.output.contains("v1")); - - fs::write(&helper_path, "export const value = \"v2\";\n")?; - - let second = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "console.log(firstValue);\nconst { value: secondValue } = await import(\"./helper.js\");\nconsole.log(secondValue);".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(second.output.contains("v1")); - assert!(second.output.contains("v2")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_reimports_local_files_after_fixing_failure() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - let helper_path = cwd_dir.path().join("broken.js"); - fs::write(&helper_path, "throw new Error(\"boom\");\n")?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let err = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "await import(\"./broken.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected broken module import to fail"); - assert!(err.to_string().contains("boom")); - - fs::write(&helper_path, "export const value = \"fixed\";\n")?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "console.log((await import(\"./broken.js\")).value);".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - assert!(result.output.contains("fixed")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_local_files_expose_node_like_import_meta() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - let pkg_dir = cwd_dir.path().join("node_modules").join("repl_meta_pkg"); - fs::create_dir_all(&pkg_dir)?; - fs::write( - pkg_dir.join("package.json"), - "{\n \"name\": \"repl_meta_pkg\",\n \"version\": \"1.0.0\",\n \"type\": \"module\",\n \"exports\": {\n \"import\": \"./index.js\"\n }\n}\n", - )?; - fs::write( - pkg_dir.join("index.js"), - "import { sep } from \"node:path\";\nexport const value = `pkg:${typeof sep}`;\n", - )?; - write_js_repl_test_module( - cwd_dir.path(), - "child.js", - "export const value = \"child-export\";\n", - )?; - write_js_repl_test_module( - cwd_dir.path(), - "meta.js", - "console.log(import.meta.url);\nconsole.log(import.meta.filename);\nconsole.log(import.meta.dirname);\nconsole.log(import.meta.main);\nconsole.log(import.meta.resolve(\"./child.js\"));\nconsole.log(import.meta.resolve(\"repl_meta_pkg\"));\nconsole.log(import.meta.resolve(\"node:fs\"));\nconsole.log((await import(import.meta.resolve(\"./child.js\"))).value);\nconsole.log((await import(import.meta.resolve(\"repl_meta_pkg\"))).value);\n", - )?; - let child_path = fs::canonicalize(cwd_dir.path().join("child.js"))?; - let child_url = url::Url::from_file_path(&child_path) - .expect("child path should convert to file URL") - .to_string(); - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let result = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"./meta.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await?; - let cwd_display = cwd_dir.path().display().to_string(); - let meta_path_display = cwd_dir.path().join("meta.js").display().to_string(); - assert!(result.output.contains("file://")); - assert!(result.output.contains(&meta_path_display)); - assert!(result.output.contains(&cwd_display)); - assert!(result.output.contains("false")); - assert!(result.output.contains(&child_url)); - assert!(result.output.contains("repl_meta_pkg")); - assert!(result.output.contains("node:fs")); - assert!(result.output.contains("child-export")); - assert!(result.output.contains("pkg:string")); - Ok(()) -} - -#[tokio::test] -async fn js_repl_rejects_top_level_static_imports_with_clear_error() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let (session, turn) = make_session_and_context().await; - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let err = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "import \"./local.js\";".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected top-level static import to be rejected"); - assert!( - err.to_string() - .contains("Top-level static import \"./local.js\" is not supported in js_repl") - ); - Ok(()) -} - -#[tokio::test] -async fn js_repl_local_files_reject_static_bare_imports() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - write_js_repl_test_package(cwd_dir.path(), "repl_counter", "pkg")?; - write_js_repl_test_module( - cwd_dir.path(), - "entry.js", - "import { value } from \"repl_counter\";\nconsole.log(value);\n", - )?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let err = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"./entry.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected static bare import to be rejected"); - assert!( - err.to_string() - .contains("Static import \"repl_counter\" is not supported from js_repl local files") - ); - Ok(()) -} - -#[tokio::test] -async fn js_repl_rejects_unsupported_file_specifiers() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - write_js_repl_test_module(cwd_dir.path(), "local.ts", "export const value = \"ts\";\n")?; - write_js_repl_test_module(cwd_dir.path(), "local", "export const value = \"noext\";\n")?; - fs::create_dir_all(cwd_dir.path().join("dir"))?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let unsupported_extension = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "await import(\"./local.ts\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected unsupported extension to be rejected"); - assert!( - unsupported_extension - .to_string() - .contains("Only .js and .mjs files are supported") - ); - - let extensionless = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "await import(\"./local\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected extensionless import to be rejected"); - assert!( - extensionless - .to_string() - .contains("Only .js and .mjs files are supported") - ); - - let directory = manager - .execute( - Arc::clone(&session), - Arc::clone(&turn), - Arc::clone(&tracker), - JsReplArgs { - code: "await import(\"./dir\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected directory import to be rejected"); - assert!( - directory - .to_string() - .contains("Directory imports are not supported") - ); - - let unsupported_url = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"https://example.com/test.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected unsupported url import to be rejected"); - assert!( - unsupported_url - .to_string() - .contains("Unsupported import specifier") - ); - Ok(()) -} - -#[tokio::test] -async fn js_repl_blocks_sensitive_builtin_imports_from_local_files() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let cwd_dir = tempdir()?; - write_js_repl_test_module( - cwd_dir.path(), - "blocked.js", - "import process from \"node:process\";\nconsole.log(process.pid);\n", - )?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let err = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"./blocked.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected blocked builtin import to be rejected"); - assert!( - err.to_string() - .contains("Importing module \"node:process\" is not allowed in js_repl") - ); - Ok(()) -} - -#[tokio::test] -async fn js_repl_local_files_do_not_escape_node_module_search_roots() -> anyhow::Result<()> { - if !can_run_js_repl_runtime_tests().await { - return Ok(()); - } - - let parent_dir = tempdir()?; - write_js_repl_test_package(parent_dir.path(), "repl_probe", "parent")?; - let cwd_dir = parent_dir.path().join("workspace"); - fs::create_dir_all(&cwd_dir)?; - write_js_repl_test_module( - &cwd_dir, - "entry.js", - "const { value } = await import(\"repl_probe\");\nconsole.log(value);\n", - )?; - - let (session, mut turn) = make_session_and_context().await; - turn.shell_environment_policy - .r#set - .remove("CODEX_JS_REPL_NODE_MODULE_DIRS"); - turn.cwd = cwd_dir.abs(); - turn.js_repl = Arc::new(JsReplHandle::with_node_path( - turn.config.js_repl_node_path.clone(), - Vec::new(), - )); - - let session = Arc::new(session); - let turn = Arc::new(turn); - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::default())); - let manager = turn.js_repl.manager().await?; - - let err = manager - .execute( - session, - turn, - tracker, - JsReplArgs { - code: "await import(\"./entry.js\");".to_string(), - timeout_ms: Some(10_000), - }, - ) - .await - .expect_err("expected parent node_modules lookup to be rejected"); - assert!(err.to_string().contains("repl_probe")); - Ok(()) -} diff --git a/codex-rs/core/src/tools/mod.rs b/codex-rs/core/src/tools/mod.rs index a6e31dc8b8e1..659a7d3e549a 100644 --- a/codex-rs/core/src/tools/mod.rs +++ b/codex-rs/core/src/tools/mod.rs @@ -3,7 +3,6 @@ pub(crate) mod context; pub(crate) mod events; pub(crate) mod handlers; pub(crate) mod hook_names; -pub(crate) mod js_repl; pub(crate) mod network_approval; pub(crate) mod orchestrator; pub(crate) mod parallel; diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index ad635126cf83..1f0dec6925bf 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -279,18 +279,6 @@ impl ToolRouter { payload, } = call; - let direct_js_repl_call = tool_name.namespace.is_none() - && matches!(tool_name.name.as_str(), "js_repl" | "js_repl_reset"); - if matches!(&source, ToolCallSource::Direct) - && turn.tools_config.js_repl_tools_only - && !direct_js_repl_call - { - return Err(FunctionCallError::RespondToModel( - "direct tool calls are disabled; use js_repl and codex.tool(...) instead" - .to_string(), - )); - } - let invocation = ToolInvocation { session, turn, diff --git a/codex-rs/core/src/tools/router_tests.rs b/codex-rs/core/src/tools/router_tests.rs index c83c65fc4e40..e8c098c41eb7 100644 --- a/codex-rs/core/src/tools/router_tests.rs +++ b/codex-rs/core/src/tools/router_tests.rs @@ -1,187 +1,15 @@ use std::collections::HashSet; use std::sync::Arc; -use crate::function_tool::FunctionCallError; use crate::session::tests::make_session_and_context; use crate::tools::context::ToolPayload; -use crate::turn_diff_tracker::TurnDiffTracker; use codex_protocol::models::ResponseItem; use codex_tools::ToolName; -use tokio_util::sync::CancellationToken; use super::ToolCall; -use super::ToolCallSource; use super::ToolRouter; use super::ToolRouterParams; -#[tokio::test] -#[expect( - clippy::await_holding_invalid_type, - reason = "test builds a router from session-owned MCP manager state" -)] -async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> { - let (session, mut turn) = make_session_and_context().await; - turn.tools_config.js_repl_tools_only = true; - - let session = Arc::new(session); - let turn = Arc::new(turn); - let mcp_tools = session - .services - .mcp_connection_manager - .read() - .await - .list_all_tools() - .await; - let deferred_mcp_tools = Some(mcp_tools.clone()); - let router = ToolRouter::from_config( - &turn.tools_config, - ToolRouterParams { - deferred_mcp_tools, - mcp_tools: Some(mcp_tools), - unavailable_called_tools: Vec::new(), - parallel_mcp_server_names: HashSet::new(), - discoverable_tools: None, - dynamic_tools: turn.dynamic_tools.as_slice(), - }, - ); - - let call = ToolCall { - tool_name: ToolName::plain("shell"), - call_id: "call-1".to_string(), - payload: ToolPayload::Function { - arguments: "{}".to_string(), - }, - }; - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let err = router - .dispatch_tool_call_with_code_mode_result( - session, - turn, - CancellationToken::new(), - tracker, - call, - ToolCallSource::Direct, - ) - .await - .err() - .expect("direct tool calls should be blocked"); - let FunctionCallError::RespondToModel(message) = err else { - panic!("expected RespondToModel, got {err:?}"); - }; - assert!(message.contains("direct tool calls are disabled")); - - Ok(()) -} - -#[tokio::test] -#[expect( - clippy::await_holding_invalid_type, - reason = "test builds a router from session-owned MCP manager state" -)] -async fn js_repl_tools_only_allows_js_repl_source_calls() -> anyhow::Result<()> { - let (session, mut turn) = make_session_and_context().await; - turn.tools_config.js_repl_tools_only = true; - - let session = Arc::new(session); - let turn = Arc::new(turn); - let mcp_tools = session - .services - .mcp_connection_manager - .read() - .await - .list_all_tools() - .await; - let deferred_mcp_tools = Some(mcp_tools.clone()); - let router = ToolRouter::from_config( - &turn.tools_config, - ToolRouterParams { - deferred_mcp_tools, - mcp_tools: Some(mcp_tools), - unavailable_called_tools: Vec::new(), - parallel_mcp_server_names: HashSet::new(), - discoverable_tools: None, - dynamic_tools: turn.dynamic_tools.as_slice(), - }, - ); - - let call = ToolCall { - tool_name: ToolName::plain("shell"), - call_id: "call-2".to_string(), - payload: ToolPayload::Function { - arguments: "{}".to_string(), - }, - }; - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let err = router - .dispatch_tool_call_with_code_mode_result( - session, - turn, - CancellationToken::new(), - tracker, - call, - ToolCallSource::JsRepl, - ) - .await - .err() - .expect("shell call with empty args should fail"); - let message = err.to_string(); - assert!( - !message.contains("direct tool calls are disabled"), - "js_repl source should bypass direct-call policy gate" - ); - - Ok(()) -} - -#[tokio::test] -async fn js_repl_tools_only_blocks_namespaced_js_repl_tool() -> anyhow::Result<()> { - let (session, mut turn) = make_session_and_context().await; - turn.tools_config.js_repl_tools_only = true; - - let session = Arc::new(session); - let turn = Arc::new(turn); - let router = ToolRouter::from_config( - &turn.tools_config, - ToolRouterParams { - deferred_mcp_tools: None, - mcp_tools: None, - unavailable_called_tools: Vec::new(), - parallel_mcp_server_names: HashSet::new(), - discoverable_tools: None, - dynamic_tools: turn.dynamic_tools.as_slice(), - }, - ); - - let call = ToolCall { - tool_name: ToolName::namespaced("mcp__server__", "js_repl"), - call_id: "call-namespaced-js-repl".to_string(), - payload: ToolPayload::Mcp { - server: "server".to_string(), - tool: "js_repl".to_string(), - raw_arguments: "{}".to_string(), - }, - }; - let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let err = router - .dispatch_tool_call_with_code_mode_result( - session, - turn, - CancellationToken::new(), - tracker, - call, - ToolCallSource::Direct, - ) - .await - .err() - .expect("namespaced js_repl calls should be blocked"); - let FunctionCallError::RespondToModel(message) = err else { - panic!("expected RespondToModel, got {err:?}"); - }; - assert!(message.contains("direct tool calls are disabled")); - - Ok(()) -} - #[tokio::test] #[expect( clippy::await_holding_invalid_type, diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 41dc422e3d0b..e556cab30fbf 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -80,8 +80,6 @@ pub(crate) fn build_specs_with_discoverable_tools( use crate::tools::handlers::CodeModeExecuteHandler; use crate::tools::handlers::CodeModeWaitHandler; use crate::tools::handlers::DynamicToolHandler; - use crate::tools::handlers::JsReplHandler; - use crate::tools::handlers::JsReplResetHandler; use crate::tools::handlers::ListDirHandler; use crate::tools::handlers::McpHandler; use crate::tools::handlers::McpResourceHandler; @@ -167,8 +165,6 @@ pub(crate) fn build_specs_with_discoverable_tools( let tool_suggest_handler = Arc::new(ToolSuggestHandler); let code_mode_handler = Arc::new(CodeModeExecuteHandler); let code_mode_wait_handler = Arc::new(CodeModeWaitHandler); - let js_repl_handler = Arc::new(JsReplHandler); - let js_repl_reset_handler = Arc::new(JsReplResetHandler); let unavailable_tool_handler = Arc::new(UnavailableToolHandler); let mut existing_spec_names = plan .specs @@ -212,12 +208,6 @@ pub(crate) fn build_specs_with_discoverable_tools( ToolHandlerKind::FollowupTaskV2 => { builder.register_handler(handler.name, Arc::new(FollowupTaskHandlerV2)); } - ToolHandlerKind::JsRepl => { - builder.register_handler(handler.name, js_repl_handler.clone()); - } - ToolHandlerKind::JsReplReset => { - builder.register_handler(handler.name, js_repl_reset_handler.clone()); - } ToolHandlerKind::ListAgentsV2 => { builder.register_handler(handler.name, Arc::new(ListAgentsHandlerV2)); } diff --git a/codex-rs/core/src/tools/tool_dispatch_trace.rs b/codex-rs/core/src/tools/tool_dispatch_trace.rs index b95dc1b69fc6..344b686348d3 100644 --- a/codex-rs/core/src/tools/tool_dispatch_trace.rs +++ b/codex-rs/core/src/tools/tool_dispatch_trace.rs @@ -71,7 +71,6 @@ fn tool_dispatch_invocation(invocation: &ToolInvocation) -> Option return None, }; Some(ToolDispatchInvocation { @@ -98,7 +97,6 @@ fn tool_dispatch_result( ToolCallSource::CodeMode { .. } => Some(ToolDispatchResult::CodeModeResponse { value: result.code_mode_result(payload), }), - ToolCallSource::JsRepl => None, } } diff --git a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs index b2a7cfe977eb..5f11816553cf 100644 --- a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs +++ b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs @@ -129,11 +129,6 @@ async fn dispatch_lifecycle_trace_records_direct_and_code_mode_requesters() -> a Ok(()) } -#[tokio::test] -async fn dispatch_lifecycle_trace_skips_noncanonical_boundaries() -> anyhow::Result<()> { - assert_dispatch_trace_skips(ToolCallSource::JsRepl).await -} - #[tokio::test] async fn dispatch_lifecycle_trace_records_unsupported_tool_failures() -> anyhow::Result<()> { let temp = TempDir::new()?; @@ -234,35 +229,6 @@ async fn missing_code_mode_wait_traces_only_the_wait_tool_call() -> anyhow::Resu Ok(()) } -async fn assert_dispatch_trace_skips(source: ToolCallSource) -> anyhow::Result<()> { - let temp = TempDir::new()?; - let (mut session, turn) = make_session_and_context().await; - attach_test_trace(&mut session, &turn, temp.path())?; - - let registry = ToolRegistry::with_handler_for_test( - codex_tools::ToolName::plain("test_tool"), - Arc::new(TestHandler), - ); - let session = Arc::new(session); - let turn = Arc::new(turn); - - registry - .dispatch_any(test_invocation( - session, - turn, - "skipped-call", - "test_tool", - source, - "{}", - )) - .await?; - - let replayed = codex_rollout_trace::replay_bundle(single_bundle_dir(temp.path())?)?; - assert_eq!(replayed.tool_calls, Default::default()); - - Ok(()) -} - fn test_invocation( session: Arc, turn: Arc, diff --git a/codex-rs/core/tests/suite/js_repl.rs b/codex-rs/core/tests/suite/js_repl.rs deleted file mode 100644 index 450aa08f0bb4..000000000000 --- a/codex-rs/core/tests/suite/js_repl.rs +++ /dev/null @@ -1,795 +0,0 @@ -#![allow(clippy::expect_used, clippy::unwrap_used)] - -use anyhow::Result; -use codex_config::types::McpServerConfig; -use codex_config::types::McpServerTransportConfig; -use codex_features::Feature; -use codex_protocol::protocol::EventMsg; -use core_test_support::responses; -use core_test_support::responses::ResponseMock; -use core_test_support::responses::ResponsesRequest; -use core_test_support::responses::ev_assistant_message; -use core_test_support::responses::ev_completed; -use core_test_support::responses::ev_custom_tool_call; -use core_test_support::responses::ev_response_created; -use core_test_support::responses::sse; -use core_test_support::skip_if_no_network; -use core_test_support::stdio_server_bin; -use core_test_support::test_codex::test_codex; -use core_test_support::wait_for_event_match; -use std::collections::HashMap; -use std::fs; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::time::Duration; -use tempfile::tempdir; -use wiremock::MockServer; - -fn custom_tool_output_text_and_success( - req: &ResponsesRequest, - call_id: &str, -) -> (String, Option) { - let (output, success) = req - .custom_tool_call_output_content_and_success(call_id) - .expect("custom tool output should be present"); - (output.unwrap_or_default(), success) -} - -fn assert_js_repl_ok(req: &ResponsesRequest, call_id: &str, expected_output: &str) { - let (output, success) = custom_tool_output_text_and_success(req, call_id); - assert_ne!( - success, - Some(false), - "js_repl call failed unexpectedly: {output}" - ); - assert!(output.contains(expected_output), "output was: {output}"); -} - -fn assert_js_repl_err(req: &ResponsesRequest, call_id: &str, expected_output: &str) { - let (output, success) = custom_tool_output_text_and_success(req, call_id); - assert_ne!(success, Some(true), "js_repl call should fail: {output}"); - assert!(output.contains(expected_output), "output was: {output}"); -} - -fn tool_names(body: &serde_json::Value) -> Vec { - body["tools"] - .as_array() - .expect("tools array should be present") - .iter() - .map(|tool| { - tool.get("name") - .and_then(|value| value.as_str()) - .or_else(|| tool.get("type").and_then(|value| value.as_str())) - .expect("tool should have a name or type") - .to_string() - }) - .collect() -} - -fn write_too_old_node_script(dir: &Path) -> Result { - #[cfg(windows)] - { - let path = dir.join("old-node.cmd"); - fs::write(&path, "@echo off\r\necho v0.0.1\r\n")?; - Ok(path) - } - - #[cfg(unix)] - { - let path = dir.join("old-node.sh"); - fs::write(&path, "#!/bin/sh\necho v0.0.1\n")?; - let mut permissions = fs::metadata(&path)?.permissions(); - permissions.set_mode(0o755); - fs::set_permissions(&path, permissions)?; - Ok(path) - } - - #[cfg(not(any(unix, windows)))] - { - anyhow::bail!("unsupported platform for js_repl test fixture"); - } -} - -async fn run_js_repl_turn( - server: &MockServer, - prompt: &str, - calls: &[(&str, &str)], -) -> Result { - let mut mocks = run_js_repl_sequence(server, prompt, calls).await?; - Ok(mocks - .pop() - .expect("js_repl test should return a request mock")) -} - -async fn run_js_repl_sequence( - server: &MockServer, - prompt: &str, - calls: &[(&str, &str)], -) -> Result> { - anyhow::ensure!( - !calls.is_empty(), - "js_repl test must include at least one call" - ); - - let mut builder = test_codex().with_config(|config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - }); - let test = builder.build(server).await?; - - responses::mount_sse_once( - server, - sse(vec![ - ev_response_created("resp-1"), - ev_custom_tool_call(calls[0].0, "js_repl", calls[0].1), - ev_completed("resp-1"), - ]), - ) - .await; - - let mut mocks = Vec::with_capacity(calls.len()); - for (response_index, (call_id, js_input)) in calls.iter().enumerate().skip(1) { - let response_id = format!("resp-{}", response_index + 1); - let mock = responses::mount_sse_once( - server, - sse(vec![ - ev_response_created(&response_id), - ev_custom_tool_call(call_id, "js_repl", js_input), - ev_completed(&response_id), - ]), - ) - .await; - mocks.push(mock); - } - - let final_response_id = format!("resp-{}", calls.len() + 1); - let final_mock = responses::mount_sse_once( - server, - sse(vec![ - ev_assistant_message("msg-1", "done"), - ev_completed(&final_response_id), - ]), - ) - .await; - mocks.push(final_mock); - - test.submit_turn(prompt).await?; - Ok(mocks) -} - -async fn assert_failed_cell_followup( - server: &MockServer, - prompt: &str, - failing_cell: &str, - followup_cell: &str, - expected_followup_output: &str, -) -> Result<()> { - let mocks = run_js_repl_sequence( - server, - prompt, - &[("call-1", failing_cell), ("call-2", followup_cell)], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok( - &mocks[1].single_request(), - "call-2", - expected_followup_output, - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_is_not_advertised_when_startup_node_is_incompatible() -> Result<()> { - skip_if_no_network!(Ok(())); - if std::env::var_os("CODEX_JS_REPL_NODE_PATH").is_some() { - return Ok(()); - } - - let server = responses::start_mock_server().await; - let temp = tempdir()?; - let old_node = write_too_old_node_script(temp.path())?; - - let mut builder = test_codex().with_config(move |config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - config.js_repl_node_path = Some(old_node); - }); - let test = builder.build(&server).await?; - let warning = wait_for_event_match(&test.codex, |event| match event { - EventMsg::Warning(ev) if ev.message.contains("Disabled `js_repl` for this session") => { - Some(ev.message.clone()) - } - _ => None, - }) - .await; - assert!( - warning.contains("Node runtime"), - "warning should explain the Node compatibility issue: {warning}" - ); - - let request_mock = responses::mount_sse_once( - &server, - sse(vec![ - ev_assistant_message("msg-1", "done"), - ev_completed("resp-1"), - ]), - ) - .await; - - test.submit_turn("hello").await?; - - let body = request_mock.single_request().body_json(); - let tools = tool_names(&body); - assert!( - !tools.iter().any(|tool| tool == "js_repl"), - "js_repl should be omitted when startup validation fails: {tools:?}" - ); - assert!( - !tools.iter().any(|tool| tool == "js_repl_reset"), - "js_repl_reset should be omitted when startup validation fails: {tools:?}" - ); - let instructions = body["instructions"].as_str().unwrap_or_default(); - assert!( - !instructions.contains("## JavaScript REPL (Node)"), - "startup instructions should not mention js_repl when it is disabled: {instructions}" - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_persists_top_level_destructured_bindings_and_supports_tla() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl twice", - &[ - ( - "call-1", - "const { context: liveContext, session } = await Promise.resolve({ context: 41, session: 1 }); console.log(liveContext + session);", - ), - ("call-2", "console.log(liveContext + session);"), - ], - ) - .await?; - - assert_js_repl_ok(&mocks[0].single_request(), "call-1", "42"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "42"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_commit_initialized_bindings_only() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl across a failed cell", - &[ - ("call-1", "const base = 40; console.log(base);"), - ( - "call-2", - "const { session } = await Promise.resolve({ session: 2 }); throw new Error(\"boom\"); const late = 99;", - ), - ("call-3", "console.log(base + session, typeof late);"), - ], - ) - .await?; - - assert_js_repl_ok(&mocks[0].single_request(), "call-1", "40"); - assert_js_repl_err(&mocks[1].single_request(), "call-2", "boom"); - assert_js_repl_ok(&mocks[2].single_request(), "call-3", "42 undefined"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_preserve_initialized_lexical_destructuring_bindings() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through partial destructuring failure", - &[ - ( - "call-1", - "const { a, b } = { a: 1, get b() { throw new Error(\"boom\"); } };", - ), - ( - "call-2", - "let aValue; try { aValue = a; } catch (error) { aValue = error.name; } let bValue; try { bValue = b; } catch (error) { bValue = error.name; } console.log(aValue, bValue);", - ), - ], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "1 ReferenceError"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_link_failures_keep_prior_module_state() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl across a link failure", - &[ - ("call-1", "const answer = 41; console.log(answer);"), - ("call-2", "import value from \"./foo\";"), - ("call-3", "console.log(answer + 1);"), - ], - ) - .await?; - - assert_js_repl_ok(&mocks[0].single_request(), "call-1", "41"); - assert_js_repl_err( - &mocks[1].single_request(), - "call-2", - "Top-level static import \"./foo\" is not supported in js_repl", - ); - assert_js_repl_ok(&mocks[2].single_request(), "call-3", "42"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_do_not_commit_unreached_hoisted_bindings() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through hoisted binding failure", - &[ - ( - "call-1", - "var early = 1; throw new Error(\"boom\"); var late = 2; function fn() { return 1; }", - ), - ( - "call-2", - "const late = 40; const fn = 1; console.log(early + late + fn);", - ), - ], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "42"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_do_not_preserve_hoisted_function_reads_before_declaration() --> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through unsupported hoisted function reads", - &[ - ( - "call-1", - "foo(); throw new Error(\"boom\"); function foo() {}", - ), - ( - "call-2", - "let value; try { foo; value = \"present\"; } catch (error) { value = error.name; } console.log(value);", - ), - ], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "ReferenceError"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_preserve_functions_when_declaration_sites_are_reached() -> Result<()> -{ - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through supported function declaration persistence", - &[ - ("call-1", "function foo() {} throw new Error(\"boom\");"), - ("call-2", "console.log(typeof foo);"), - ], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "function"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_preserve_prior_binding_writes_without_new_bindings() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through failed prior-binding writes", - &[ - ("call-1", "let x = 1; console.log(x);"), - ("call-2", "x = 2; throw new Error(\"boom\");"), - ("call-3", "console.log(x);"), - ], - ) - .await?; - - assert_js_repl_ok(&mocks[0].single_request(), "call-1", "1"); - assert_js_repl_err(&mocks[1].single_request(), "call-2", "boom"); - assert_js_repl_ok(&mocks[2].single_request(), "call-3", "2"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_var_persistence_boundaries() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let cases = [ - ( - "run js_repl through supported pre-declaration var writes", - "x = 5; y = 1; y += 2; z = 1; z++; throw new Error(\"boom\"); var x, y, z;", - "console.log(x, y, z);", - "5 3 2", - ), - ( - "run js_repl through short-circuited logical var assignments", - "x &&= 1; y ||= 2; z ??= 3; throw new Error(\"boom\"); var x, y, z;", - "let xValue; try { xValue = x; } catch (error) { xValue = error.name; } console.log(xValue, y, z);", - "ReferenceError 2 3", - ), - ( - "run js_repl through unsupported shadowed nested var writes", - "{ let x = 1; x = 2; } throw new Error(\"boom\"); var x;", - "let value; try { value = x; } catch (error) { value = error.name; } console.log(value);", - "ReferenceError", - ), - ( - "run js_repl through unsupported nested assignment writes", - "x = (y = 1); throw new Error(\"boom\"); var x, y;", - "let yValue; try { yValue = y; } catch (error) { yValue = error.name; } console.log(x, yValue);", - "1 ReferenceError", - ), - ( - "run js_repl through unsupported var destructuring recovery", - "var { a, b } = { a: 1, get b() { throw new Error(\"boom\"); } };", - "let aValue; try { aValue = a; } catch (error) { aValue = error.name; } let bValue; try { bValue = b; } catch (error) { bValue = error.name; } console.log(aValue, bValue);", - "ReferenceError ReferenceError", - ), - ]; - - for (prompt, failing_cell, followup_cell, expected_followup_output) in cases { - assert_failed_cell_followup( - &server, - prompt, - failing_cell, - followup_cell, - expected_followup_output, - ) - .await?; - } - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_failed_cells_commit_non_empty_loop_vars_but_skip_empty_loops() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mocks = run_js_repl_sequence( - &server, - "run js_repl through failed loop bindings", - &[ - ( - "call-1", - "for (var item of [2]) {} for (var emptyItem of []) {} throw new Error(\"boom\");", - ), - ( - "call-2", - "let itemValue; try { itemValue = item; } catch (error) { itemValue = error.name; } let emptyValue; try { emptyValue = emptyItem; } catch (error) { emptyValue = error.name; } console.log(itemValue, emptyValue);", - ), - ], - ) - .await?; - - assert_js_repl_err(&mocks[0].single_request(), "call-1", "boom"); - assert_js_repl_ok(&mocks[1].single_request(), "call-2", "2 ReferenceError"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_keeps_function_to_string_stable() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "run js_repl through function toString", - &[( - "call-1", - "function foo() { return 1; } console.log(foo.toString());", - )], - ) - .await?; - - let req = mock.single_request(); - assert_js_repl_ok(&req, "call-1", "function foo() { return 1; }"); - let (output, _) = custom_tool_output_text_and_success(&req, "call-1"); - assert!(!output.contains("__codexInternalMarkCommittedBindings")); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_allows_globalthis_shadowing_with_instrumented_bindings() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "run js_repl with shadowed globalThis", - &[( - "call-1", - "const globalThis = {}; const value = 1; console.log(typeof globalThis, value);", - )], - ) - .await?; - - let req = mock.single_request(); - assert_js_repl_ok(&req, "call-1", "object 1"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_invoke_builtin_tools() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "use js_repl to call a tool", - &[( - "call-1", - "const toolOut = await codex.tool(\"list_mcp_resources\", {}); console.log(toolOut.type);", - )], - ) - .await?; - - let req = mock.single_request(); - let (output, success) = custom_tool_output_text_and_success(&req, "call-1"); - assert_ne!( - success, - Some(false), - "js_repl call failed unexpectedly: {output}" - ); - assert!(output.contains("function_call_output")); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_can_invoke_mcp_tools_by_display_name() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let rmcp_test_server_bin = stdio_server_bin()?; - let mut builder = test_codex().with_config(move |config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - - let mut servers = config.mcp_servers.get().clone(); - servers.insert( - "rmcp".to_string(), - McpServerConfig { - transport: McpServerTransportConfig::Stdio { - command: rmcp_test_server_bin, - args: Vec::new(), - env: None, - env_vars: Vec::new(), - cwd: None, - }, - experimental_environment: None, - enabled: true, - required: false, - supports_parallel_tool_calls: false, - disabled_reason: None, - startup_timeout_sec: Some(Duration::from_secs(10)), - tool_timeout_sec: None, - default_tools_approval_mode: None, - enabled_tools: None, - disabled_tools: None, - scopes: None, - oauth_resource: None, - tools: HashMap::new(), - }, - ); - config - .mcp_servers - .set(servers) - .expect("test mcp servers should accept any configuration"); - }); - let test = builder.build(&server).await?; - - responses::mount_sse_once( - &server, - sse(vec![ - ev_response_created("resp-1"), - ev_custom_tool_call( - "call-1", - "js_repl", - r#" -const result = await codex.tool("mcp__rmcp__echo", { message: "ping" }); -console.log(result.output); -"#, - ), - ev_completed("resp-1"), - ]), - ) - .await; - let final_mock = responses::mount_sse_once( - &server, - sse(vec![ - ev_assistant_message("msg-1", "done"), - ev_completed("resp-2"), - ]), - ) - .await; - - test.submit_turn("use js_repl to call an MCP tool").await?; - - let req = final_mock.single_request(); - assert_js_repl_ok(&req, "call-1", "ECHOING: ping"); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_tool_call_rejects_recursive_js_repl_invocation() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "use js_repl recursively", - &[( - "call-1", - r#" -try { - await codex.tool("js_repl", "console.log('recursive')"); - console.log("unexpected-success"); -} catch (err) { - console.log(String(err)); -} -"#, - )], - ) - .await?; - - let req = mock.single_request(); - let (output, success) = custom_tool_output_text_and_success(&req, "call-1"); - assert_ne!( - success, - Some(false), - "js_repl call failed unexpectedly: {output}" - ); - assert!( - output.contains("js_repl cannot invoke itself"), - "expected recursion guard message, got output: {output}" - ); - assert!( - !output.contains("unexpected-success"), - "recursive js_repl call unexpectedly succeeded: {output}" - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_does_not_expose_process_global() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "check process visibility", - &[("call-1", "console.log(typeof process);")], - ) - .await?; - - let req = mock.single_request(); - let (output, success) = custom_tool_output_text_and_success(&req, "call-1"); - assert_ne!( - success, - Some(false), - "js_repl call failed unexpectedly: {output}" - ); - assert!(output.contains("undefined")); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_exposes_codex_path_helpers() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "check codex path helpers", - &[( - "call-1", - "console.log(`cwd:${typeof codex.cwd}:${codex.cwd.length > 0}`); console.log(`home:${codex.homeDir === null || typeof codex.homeDir === \"string\"}`);", - )], - ) - .await?; - - let req = mock.single_request(); - let (output, success) = custom_tool_output_text_and_success(&req, "call-1"); - assert_ne!( - success, - Some(false), - "js_repl call failed unexpectedly: {output}" - ); - assert!(output.contains("cwd:string:true")); - assert!(output.contains("home:true")); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_blocks_sensitive_builtin_imports() -> Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let mock = run_js_repl_turn( - &server, - "import a blocked module", - &[("call-1", "await import(\"node:process\");")], - ) - .await?; - - let req = mock.single_request(); - let (output, success) = custom_tool_output_text_and_success(&req, "call-1"); - assert_ne!( - success, - Some(true), - "blocked import unexpectedly succeeded: {output}" - ); - assert!(output.contains("Importing module \"node:process\" is not allowed in js_repl")); - - Ok(()) -} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 8f9f9e68406f..b4adc6559d96 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -51,7 +51,6 @@ mod hooks; mod hooks_mcp; mod image_rollout; mod items; -mod js_repl; mod json_result; mod live_cli; mod live_reload; diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index db8ad21aa549..a2e15654e6f1 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -24,7 +24,6 @@ use codex_core::config::Config; use codex_exec_server::CreateDirectoryOptions; use codex_exec_server::Environment; use codex_exec_server::HttpRequestParams; -use codex_features::Feature; use codex_login::CodexAuth; use codex_mcp::MCP_SANDBOX_STATE_META_CAPABILITY; use codex_models_manager::manager::RefreshStrategy; @@ -48,7 +47,6 @@ use codex_utils_cargo_bin::cargo_bin; use core_test_support::assert_regex_match; use core_test_support::remote_env_env_var; use core_test_support::responses; -use core_test_support::responses::ev_custom_tool_call; use core_test_support::responses::mount_models_once; use core_test_support::responses::mount_sse_once; use core_test_support::skip_if_no_network; @@ -1328,90 +1326,6 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -#[serial(mcp_test_value)] -async fn js_repl_emit_image_preserves_original_detail_for_mcp_images() -> anyhow::Result<()> { - skip_if_no_network!(Ok(())); - - let server = responses::start_mock_server().await; - let call_id = "js-repl-rmcp-image"; - let rmcp_test_server_bin = stdio_server_bin()?; - - let fixture = test_codex() - .with_model("gpt-5.3-codex") - .with_config(move |config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - insert_mcp_server( - config, - "rmcp", - stdio_transport(rmcp_test_server_bin, /*env*/ None, Vec::new()), - TestMcpServerOptions::default(), - ); - }) - .build(&server) - .await?; - - wait_for_mcp_tool(&fixture, "mcp__rmcp__image_scenario").await?; - - mount_sse_once( - &server, - responses::sse(vec![ - responses::ev_response_created("resp-1"), - ev_custom_tool_call( - call_id, - "js_repl", - r#" -const out = await codex.tool("mcp__rmcp__image_scenario", { - scenario: "image_only_original_detail", -}); -const imageItem = out.output.find((item) => item.type === "input_image"); -await codex.emitImage(imageItem); -"#, - ), - responses::ev_completed("resp-1"), - ]), - ) - .await; - let final_mock = mount_sse_once( - &server, - responses::sse(vec![ - responses::ev_assistant_message("msg-1", "done"), - responses::ev_completed("resp-2"), - ]), - ) - .await; - - fixture - .submit_turn("use js_repl to emit the rmcp image scenario output") - .await?; - - let output = final_mock.single_request().custom_tool_call_output(call_id); - let output_items = output["output"] - .as_array() - .expect("js_repl output should be content items"); - let image_item = output_items - .iter() - .find(|item| item.get("type").and_then(Value::as_str) == Some("input_image")) - .expect("js_repl should emit an input_image item"); - assert_eq!( - image_item.get("detail").and_then(Value::as_str), - Some("original") - ); - assert!( - image_item - .get("image_url") - .and_then(Value::as_str) - .is_some_and(|image_url| image_url.starts_with("data:image/png;base64,")), - "js_repl should emit a png data URL" - ); - - server.verify().await; - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[serial(mcp_test_value)] async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Result<()> { diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index bc2bf2361dd8..2391e35fdd6a 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -96,10 +96,6 @@ async fn empty_turn_environments_omits_environment_backed_tools() -> Result<()> .features .enable(Feature::UnifiedExec) .expect("unified exec should enable for test"); - config - .features - .enable(Feature::JsRepl) - .expect("js repl should enable for test"); config.include_apply_patch_tool = true; }); let test = builder.build(&server).await?; @@ -112,14 +108,7 @@ async fn empty_turn_environments_omits_environment_backed_tools() -> Result<()> tools.contains(&"update_plan".to_string()), "non-environment tool should remain available; got {tools:?}" ); - for environment_tool in [ - "exec_command", - "write_stdin", - "js_repl", - "js_repl_reset", - "apply_patch", - "view_image", - ] { + for environment_tool in ["exec_command", "write_stdin", "apply_patch", "view_image"] { assert!( !tools.contains(&environment_tool.to_string()), "{environment_tool} should be omitted for explicit empty turn environments; got {tools:?}" diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index 695acd0dd7db..972e500eeeb7 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -22,7 +22,6 @@ use codex_protocol::user_input::UserInput; use core_test_support::responses; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; -use core_test_support::responses::ev_custom_tool_call; use core_test_support::responses::ev_function_call; use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_models_once; @@ -871,233 +870,6 @@ async fn view_image_tool_does_not_force_original_resolution_with_capability_only Ok(()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_emit_image_attaches_local_image() -> anyhow::Result<()> { - skip_if_no_network!(Ok(())); - - let server = start_mock_server().await; - let mut builder = test_codex().with_config(|config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - }); - let TestCodex { - codex, - cwd, - session_configured, - .. - } = builder.build(&server).await?; - - let call_id = "js-repl-view-image"; - let js_input = r#" -const fs = await import("node:fs/promises"); -const path = await import("node:path"); -const imagePath = path.join(codex.tmpDir, "js-repl-view-image.png"); -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await fs.writeFile(imagePath, png); -const out = await codex.tool("view_image", { path: imagePath }); -await codex.emitImage(out); -"#; - - let first_response = sse(vec![ - ev_response_created("resp-1"), - ev_custom_tool_call(call_id, "js_repl", js_input), - ev_completed("resp-1"), - ]); - responses::mount_sse_once(&server, first_response).await; - - let second_response = sse(vec![ - ev_assistant_message("msg-1", "done"), - ev_completed("resp-2"), - ]); - let mock = responses::mount_sse_once(&server, second_response).await; - - let session_model = session_configured.model.clone(); - codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "use js_repl to write an image and attach it".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; - - let mut tool_event = None; - wait_for_event_with_timeout( - &codex, - |event| match event { - EventMsg::ViewImageToolCall(_) => { - tool_event = Some(event.clone()); - false - } - EventMsg::TurnComplete(_) => true, - _ => false, - }, - VIEW_IMAGE_TURN_COMPLETE_TIMEOUT, - ) - .await; - let tool_event = match tool_event { - Some(EventMsg::ViewImageToolCall(event)) => event, - other => panic!("expected ViewImageToolCall event, got {other:?}"), - }; - assert!( - tool_event.path.ends_with("js-repl-view-image.png"), - "unexpected image path: {}", - tool_event.path.display() - ); - - let req = mock.single_request(); - let body = req.body_json(); - assert_eq!( - image_messages(&body).len(), - 0, - "js_repl view_image should not inject a pending input image message" - ); - - let custom_output = req.custom_tool_call_output(call_id); - let output_items = custom_output - .get("output") - .and_then(Value::as_array) - .expect("custom_tool_call_output should be a content item array"); - let image_url = output_items - .iter() - .find_map(|item| { - (item.get("type").and_then(Value::as_str) == Some("input_image")) - .then(|| item.get("image_url").and_then(Value::as_str)) - .flatten() - }) - .expect("image_url present in js_repl custom tool output"); - assert!( - image_url.starts_with("data:image/png;base64,"), - "expected png data URL, got {image_url}" - ); - - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn js_repl_view_image_requires_explicit_emit() -> anyhow::Result<()> { - skip_if_no_network!(Ok(())); - - let server = start_mock_server().await; - #[allow(clippy::expect_used)] - let mut builder = test_codex().with_config(|config| { - config - .features - .enable(Feature::JsRepl) - .expect("test config should allow feature update"); - }); - let TestCodex { - codex, - cwd, - session_configured, - .. - } = builder.build(&server).await?; - - let call_id = "js-repl-view-image-no-emit"; - let js_input = r#" -const fs = await import("node:fs/promises"); -const path = await import("node:path"); -const imagePath = path.join(codex.tmpDir, "js-repl-view-image-no-emit.png"); -const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==", - "base64" -); -await fs.writeFile(imagePath, png); -const out = await codex.tool("view_image", { path: imagePath }); -console.log(out.type); -"#; - - let first_response = sse(vec![ - ev_response_created("resp-1"), - ev_custom_tool_call(call_id, "js_repl", js_input), - ev_completed("resp-1"), - ]); - responses::mount_sse_once(&server, first_response).await; - - let second_response = sse(vec![ - ev_assistant_message("msg-1", "done"), - ev_completed("resp-2"), - ]); - let mock = responses::mount_sse_once(&server, second_response).await; - - let session_model = session_configured.model.clone(); - codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "use js_repl to write an image but do not emit it".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_model, - effort: None, - service_tier: None, - summary: None, - collaboration_mode: None, - personality: None, - }) - .await?; - - let mut tool_event = None; - wait_for_event_with_timeout( - &codex, - |event| match event { - EventMsg::ViewImageToolCall(_) => { - tool_event = Some(event.clone()); - false - } - EventMsg::TurnComplete(_) => true, - _ => false, - }, - VIEW_IMAGE_TURN_COMPLETE_TIMEOUT, - ) - .await; - let tool_event = match tool_event { - Some(EventMsg::ViewImageToolCall(event)) => event, - other => panic!("expected ViewImageToolCall event, got {other:?}"), - }; - assert!( - tool_event.path.ends_with("js-repl-view-image-no-emit.png"), - "unexpected image path: {}", - tool_event.path.display() - ); - - let req = mock.single_request(); - let custom_output = req.custom_tool_call_output(call_id); - let output_items = custom_output.get("output").and_then(Value::as_array); - assert!( - output_items.is_none_or(|items| items - .iter() - .all(|item| item.get("type").and_then(Value::as_str) != Some("input_image"))), - "nested view_image should not auto-populate js_repl output" - ); - - Ok(()) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn view_image_tool_errors_when_path_is_directory() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); @@ -1572,4 +1344,3 @@ async fn replaces_invalid_local_image_after_bad_request() -> anyhow::Result<()> Ok(()) } -use codex_features::Feature; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 24b12f483326..c96e06279b1e 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -399,8 +399,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result codex_self_exe: arg0_paths.codex_self_exe.clone(), codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe.clone(), main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(), - js_repl_node_path: None, - js_repl_node_module_dirs: None, zsh_path: None, base_instructions: None, developer_instructions: None, diff --git a/codex-rs/features/BUILD.bazel b/codex-rs/features/BUILD.bazel index bcb084f321cf..c67f572eea9d 100644 --- a/codex-rs/features/BUILD.bazel +++ b/codex-rs/features/BUILD.bazel @@ -10,7 +10,5 @@ codex_rust_crate( "Cargo.toml", ], allow_empty = True, - ) + [ - "//codex-rs:node-version.txt", - ], + ), ) diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 527cef36f844..453b421c0f22 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -79,13 +79,13 @@ pub enum Feature { CodexHooks, // Experimental - /// Enable JavaScript REPL tools backed by a persistent Node kernel. + /// Removed compatibility flag for the deleted JavaScript REPL feature. JsRepl, - /// Enable a minimal JavaScript mode backed by Node's built-in vm runtime. + /// Enable JavaScript code mode backed by the in-process V8 runtime. CodeMode, /// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`). CodeModeOnly, - /// Only expose js_repl tools directly to the model. + /// Removed compatibility flag for the deleted JavaScript REPL tool-only mode. JsReplToolsOnly, /// Use the single unified PTY-backed exec tool. UnifiedExec, @@ -388,6 +388,12 @@ impl Features { "tui_app_server" => { continue; } + "js_repl" => { + continue; + } + "js_repl_tools_only" => { + continue; + } "image_detail_original" => { continue; } @@ -457,10 +463,6 @@ impl Features { if self.enabled(Feature::CodeModeOnly) && !self.enabled(Feature::CodeMode) { self.enable(Feature::CodeMode); } - if self.enabled(Feature::JsReplToolsOnly) && !self.enabled(Feature::JsRepl) { - tracing::warn!("js_repl_tools_only requires js_repl; disabling js_repl_tools_only"); - self.disable(Feature::JsReplToolsOnly); - } } } @@ -644,11 +646,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::JsRepl, key: "js_repl", - stage: Stage::Experimental { - name: "JavaScript REPL", - menu_description: "Enable a persistent Node-backed JavaScript REPL for interactive website debugging and other inline JavaScript execution capabilities. Requires Node >= v22.22.0 installed.", - announcement: "NEW: JavaScript REPL is now available in /experimental. Enable it, then start a new chat or restart Codex to use it.", - }, + stage: Stage::Removed, default_enabled: false, }, FeatureSpec { @@ -666,7 +664,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::JsReplToolsOnly, key: "js_repl_tools_only", - stage: Stage::UnderDevelopment, + stage: Stage::Removed, default_enabled: false, }, FeatureSpec { diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 08c16590a184..69d51a740e37 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -59,23 +59,6 @@ fn image_detail_original_is_removed_and_disabled_by_default() { assert_eq!(Feature::ImageDetailOriginal.default_enabled(), false); } -#[test] -fn js_repl_is_experimental_and_user_toggleable() { - let spec = Feature::JsRepl.info(); - let stage = spec.stage; - let expected_node_version = include_str!("../../node-version.txt").trim_end(); - - assert!(matches!(stage, Stage::Experimental { .. })); - assert_eq!(stage.experimental_menu_name(), Some("JavaScript REPL")); - assert_eq!( - stage.experimental_menu_description().map(str::to_owned), - Some(format!( - "Enable a persistent Node-backed JavaScript REPL for interactive website debugging and other inline JavaScript execution capabilities. Requires Node >= v{expected_node_version} installed." - )) - ); - assert_eq!(Feature::JsRepl.default_enabled(), false); -} - #[test] fn code_mode_only_requires_code_mode() { let mut features = Features::with_defaults(); @@ -222,6 +205,20 @@ fn image_detail_original_is_a_removed_feature_key() { ); } +#[test] +fn js_repl_features_are_removed_feature_keys() { + assert_eq!(Feature::JsRepl.stage(), Stage::Removed); + assert_eq!(Feature::JsRepl.default_enabled(), false); + assert_eq!(feature_for_key("js_repl"), Some(Feature::JsRepl)); + + assert_eq!(Feature::JsReplToolsOnly.stage(), Stage::Removed); + assert_eq!(Feature::JsReplToolsOnly.default_enabled(), false); + assert_eq!( + feature_for_key("js_repl_tools_only"), + Some(Feature::JsReplToolsOnly) + ); +} + #[test] fn tool_call_mcp_elicitation_is_stable_and_enabled_by_default() { assert_eq!(Feature::ToolCallMcpElicitation.stage(), Stage::Stable); @@ -353,6 +350,25 @@ fn from_sources_ignores_removed_image_detail_original_feature_key() { assert_eq!(features, Features::with_defaults()); } +#[test] +fn from_sources_ignores_removed_js_repl_feature_keys() { + let features_toml = FeaturesToml::from(BTreeMap::from([ + ("js_repl".to_string(), true), + ("js_repl_tools_only".to_string(), true), + ])); + + let features = Features::from_sources( + FeatureConfigSource { + features: Some(&features_toml), + ..Default::default() + }, + FeatureConfigSource::default(), + FeatureOverrides::default(), + ); + + assert_eq!(features, Features::with_defaults()); +} + #[test] fn multi_agent_v2_feature_config_deserializes_boolean_toggle() { let features: FeaturesToml = toml::from_str( diff --git a/codex-rs/node-version.txt b/codex-rs/node-version.txt deleted file mode 100644 index 85e502778f62..000000000000 --- a/codex-rs/node-version.txt +++ /dev/null @@ -1 +0,0 @@ -22.22.0 diff --git a/codex-rs/tools/README.md b/codex-rs/tools/README.md index 474f35e6ac0f..ac6bba853b26 100644 --- a/codex-rs/tools/README.md +++ b/codex-rs/tools/README.md @@ -22,7 +22,6 @@ schema and Responses API tool primitives that no longer need to live in - `ResponsesApiNamespace` - `ResponsesApiNamespaceTool` - code-mode `ToolSpec` adapters and `exec` / `wait` spec builders -- JS REPL spec builders - MCP resource, `list_dir`, and `test_sync_tool` spec builders - local host tool spec builders for shell/exec/request-permissions/view-image - collaboration and agent-job `ToolSpec` builders for spawn/send/wait/close, diff --git a/codex-rs/tools/src/js_repl_tool.rs b/codex-rs/tools/src/js_repl_tool.rs deleted file mode 100644 index 60089e0fb0b7..000000000000 --- a/codex-rs/tools/src/js_repl_tool.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::FreeformTool; -use crate::FreeformToolFormat; -use crate::JsonSchema; -use crate::ResponsesApiTool; -use crate::ToolSpec; -use std::collections::BTreeMap; - -pub fn create_js_repl_tool() -> ToolSpec { - // Keep JS input freeform, but block the most common malformed payload shapes - // (JSON wrappers, quoted strings, and markdown fences) before they reach the - // runtime `reject_json_or_quoted_source` validation. The API's regex engine - // does not support look-around, so this uses a "first significant token" - // pattern rather than negative lookaheads. - const JS_REPL_FREEFORM_GRAMMAR: &str = r#" -start: pragma_source | plain_source - -pragma_source: PRAGMA_LINE NEWLINE js_source -plain_source: PLAIN_JS_SOURCE - -js_source: JS_SOURCE - -PRAGMA_LINE: /[ \t]*\/\/ codex-js-repl:[^\r\n]*/ -NEWLINE: /\r?\n/ -PLAIN_JS_SOURCE: /(?:\s*)(?:[^\s{\"`]|`[^`]|``[^`])[\s\S]*/ -JS_SOURCE: /(?:\s*)(?:[^\s{\"`]|`[^`]|``[^`])[\s\S]*/ -"#; - - ToolSpec::Freeform(FreeformTool { - name: "js_repl".to_string(), - description: "Runs JavaScript in a persistent Node kernel with top-level await. This is a freeform tool: send raw JavaScript source text, optionally with a first-line pragma like `// codex-js-repl: timeout_ms=15000`; do not send JSON/quotes/markdown fences." - .to_string(), - format: FreeformToolFormat { - r#type: "grammar".to_string(), - syntax: "lark".to_string(), - definition: JS_REPL_FREEFORM_GRAMMAR.to_string(), - }, - }) -} - -pub fn create_js_repl_reset_tool() -> ToolSpec { - ToolSpec::Function(ResponsesApiTool { - name: "js_repl_reset".to_string(), - description: - "Restarts the js_repl kernel for this run and clears persisted top-level bindings." - .to_string(), - strict: false, - defer_loading: None, - parameters: JsonSchema::object(BTreeMap::new(), /*required*/ None, Some(false.into())), - output_schema: None, - }) -} - -#[cfg(test)] -#[path = "js_repl_tool_tests.rs"] -mod tests; diff --git a/codex-rs/tools/src/js_repl_tool_tests.rs b/codex-rs/tools/src/js_repl_tool_tests.rs deleted file mode 100644 index 7d6d63f179b9..000000000000 --- a/codex-rs/tools/src/js_repl_tool_tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::*; -use crate::JsonSchema; -use crate::ToolSpec; -use pretty_assertions::assert_eq; -use std::collections::BTreeMap; - -#[test] -fn js_repl_tool_uses_expected_freeform_grammar() { - let ToolSpec::Freeform(FreeformTool { format, .. }) = create_js_repl_tool() else { - panic!("js_repl should use a freeform tool spec"); - }; - - assert_eq!(format.syntax, "lark"); - assert!(format.definition.contains("PRAGMA_LINE")); - assert!(format.definition.contains("`[^`]")); - assert!(format.definition.contains("``[^`]")); - assert!(format.definition.contains("PLAIN_JS_SOURCE")); - assert!(format.definition.contains("codex-js-repl:")); - assert!(!format.definition.contains("(?!")); -} - -#[test] -fn js_repl_reset_tool_matches_expected_spec() { - assert_eq!( - create_js_repl_reset_tool(), - ToolSpec::Function(ResponsesApiTool { - name: "js_repl_reset".to_string(), - description: - "Restarts the js_repl kernel for this run and clears persisted top-level bindings." - .to_string(), - strict: false, - defer_loading: None, - parameters: JsonSchema::object( - BTreeMap::new(), - /*required*/ None, - Some(false.into()) - ), - output_schema: None, - }) - ); -} diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 2dc7c165d52c..fe140e4791e4 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -7,7 +7,6 @@ mod apply_patch_tool; mod code_mode; mod dynamic_tool; mod image_detail; -mod js_repl_tool; mod json_schema; mod local_tool; mod mcp_resource_tool; @@ -55,8 +54,6 @@ pub use dynamic_tool::parse_dynamic_tool; pub use image_detail::can_request_original_image_detail; pub use image_detail::normalize_output_image_detail; pub use image_detail::sanitize_original_image_detail; -pub use js_repl_tool::create_js_repl_reset_tool; -pub use js_repl_tool::create_js_repl_tool; pub use json_schema::AdditionalProperties; pub use json_schema::JsonSchema; pub use json_schema::JsonSchemaPrimitiveType; diff --git a/codex-rs/tools/src/tool_config.rs b/codex-rs/tools/src/tool_config.rs index 4c4689132d2e..8f27578c5d70 100644 --- a/codex-rs/tools/src/tool_config.rs +++ b/codex-rs/tools/src/tool_config.rs @@ -99,8 +99,6 @@ pub struct ToolsConfig { pub request_permissions_tool_enabled: bool, pub code_mode_enabled: bool, pub code_mode_only_enabled: bool, - pub js_repl_enabled: bool, - pub js_repl_tools_only: bool, pub can_request_original_image_detail: bool, pub collab_tools: bool, pub multi_agent_v2: bool, @@ -141,9 +139,6 @@ impl ToolsConfig { let include_apply_patch_tool = features.enabled(Feature::ApplyPatchFreeform); let include_code_mode = features.enabled(Feature::CodeMode); let include_code_mode_only = include_code_mode && features.enabled(Feature::CodeModeOnly); - let include_js_repl = features.enabled(Feature::JsRepl); - let include_js_repl_tools_only = - include_js_repl && features.enabled(Feature::JsReplToolsOnly); let include_collab_tools = features.enabled(Feature::Collab); let include_multi_agent_v2 = features.enabled(Feature::MultiAgentV2); let include_agent_jobs = features.enabled(Feature::SpawnCsv); @@ -221,8 +216,6 @@ impl ToolsConfig { request_permissions_tool_enabled, code_mode_enabled: include_code_mode, code_mode_only_enabled: include_code_mode_only, - js_repl_enabled: include_js_repl, - js_repl_tools_only: include_js_repl_tools_only, can_request_original_image_detail: include_original_image_detail, collab_tools: include_collab_tools, multi_agent_v2: include_multi_agent_v2, diff --git a/codex-rs/tools/src/tool_registry_plan.rs b/codex-rs/tools/src/tool_registry_plan.rs index 8ae620532cb0..6b024658f348 100644 --- a/codex-rs/tools/src/tool_registry_plan.rs +++ b/codex-rs/tools/src/tool_registry_plan.rs @@ -29,8 +29,6 @@ use crate::create_code_mode_tool; use crate::create_exec_command_tool; use crate::create_followup_task_tool; use crate::create_image_generation_tool; -use crate::create_js_repl_reset_tool; -use crate::create_js_repl_tool; use crate::create_list_agents_tool; use crate::create_list_dir_tool; use crate::create_list_mcp_resource_templates_tool; @@ -218,21 +216,6 @@ pub fn build_tool_registry_plan( ); plan.register_handler("update_plan", ToolHandlerKind::Plan); - if config.has_environment && config.js_repl_enabled { - plan.push_spec( - create_js_repl_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - plan.push_spec( - create_js_repl_reset_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - plan.register_handler("js_repl", ToolHandlerKind::JsRepl); - plan.register_handler("js_repl_reset", ToolHandlerKind::JsReplReset); - } - plan.push_spec( create_request_user_input_tool(request_user_input_tool_description( config.default_mode_request_user_input, diff --git a/codex-rs/tools/src/tool_registry_plan_tests.rs b/codex-rs/tools/src/tool_registry_plan_tests.rs index 13dffb1c2de2..c08666a2c203 100644 --- a/codex-rs/tools/src/tool_registry_plan_tests.rs +++ b/codex-rs/tools/src/tool_registry_plan_tests.rs @@ -437,7 +437,6 @@ fn disabled_environment_omits_environment_backed_tools() { let model_info = model_info(); let mut features = Features::with_defaults(); features.enable(Feature::UnifiedExec); - features.enable(Feature::JsRepl); let available_models = Vec::new(); let mut tools_config = ToolsConfig::new(&ToolsConfigParams { model_info: &model_info, @@ -462,8 +461,6 @@ fn disabled_environment_omits_environment_backed_tools() { assert_lacks_tool_name(&tools, "exec_command"); assert_lacks_tool_name(&tools, "write_stdin"); - assert_lacks_tool_name(&tools, "js_repl"); - assert_lacks_tool_name(&tools, "js_repl_reset"); assert_lacks_tool_name(&tools, "apply_patch"); assert_lacks_tool_name(&tools, "list_dir"); assert_lacks_tool_name(&tools, VIEW_IMAGE_TOOL_NAME); @@ -636,66 +633,6 @@ fn request_permissions_tool_is_independent_from_additional_permissions() { assert_lacks_tool_name(&tools, "request_permissions"); } -#[test] -fn js_repl_requires_feature_flag() { - let model_info = model_info(); - let features = Features::with_defaults(); - - let available_models = Vec::new(); - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - available_models: &available_models, - features: &features, - image_generation_tool_auth_allowed: true, - web_search_mode: Some(WebSearchMode::Cached), - session_source: SessionSource::Cli, - sandbox_policy: &SandboxPolicy::DangerFullAccess, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - }); - let (tools, _) = build_specs( - &tools_config, - /*mcp_tools*/ None, - /*deferred_mcp_tools*/ None, - &[], - ); - - assert!( - !tools.iter().any(|tool| tool.spec.name() == "js_repl"), - "js_repl should be disabled when the feature is off" - ); - assert!( - !tools.iter().any(|tool| tool.spec.name() == "js_repl_reset"), - "js_repl_reset should be disabled when the feature is off" - ); -} - -#[test] -fn js_repl_enabled_adds_tools() { - let model_info = model_info(); - let mut features = Features::with_defaults(); - features.enable(Feature::JsRepl); - - let available_models = Vec::new(); - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - available_models: &available_models, - features: &features, - image_generation_tool_auth_allowed: true, - web_search_mode: Some(WebSearchMode::Cached), - session_source: SessionSource::Cli, - sandbox_policy: &SandboxPolicy::DangerFullAccess, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - }); - let (tools, _) = build_specs( - &tools_config, - /*mcp_tools*/ None, - /*deferred_mcp_tools*/ None, - &[], - ); - - assert_contains_tool_names(&tools, &["js_repl", "js_repl_reset"]); -} - #[test] fn image_generation_tools_require_feature_and_supported_model() { let supported_model_info = model_info(); diff --git a/codex-rs/tools/src/tool_registry_plan_types.rs b/codex-rs/tools/src/tool_registry_plan_types.rs index b9d66a0c2cf1..724f1bd15d86 100644 --- a/codex-rs/tools/src/tool_registry_plan_types.rs +++ b/codex-rs/tools/src/tool_registry_plan_types.rs @@ -18,8 +18,6 @@ pub enum ToolHandlerKind { CodeModeWait, DynamicTool, FollowupTaskV2, - JsRepl, - JsReplReset, ListAgentsV2, ListDir, Mcp, diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index a800c9e7ed7b..3977d9d0eec2 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -1751,30 +1751,6 @@ async fn experimental_features_toggle_saves_on_exit() { assert_eq!(updates, vec![(expected_feature, true)]); } -#[tokio::test] -async fn experimental_popup_shows_js_repl_node_requirement() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - - let js_repl_description = FEATURES - .iter() - .find(|spec| spec.id == Feature::JsRepl) - .and_then(|spec| spec.stage.experimental_menu_description()) - .expect("expected js_repl experimental description"); - let node_requirement = js_repl_description - .split(". ") - .find(|sentence| sentence.starts_with("Requires Node >= v")) - .map(|sentence| sentence.trim_end_matches(" installed.")) - .expect("expected js_repl description to mention the Node requirement"); - - chat.open_experimental_popup(); - - let popup = render_bottom_popup(&chat, /*width*/ 120); - assert!( - popup.contains(node_requirement), - "expected js_repl feature description to mention the required Node version, got:\n{popup}" - ); -} - #[tokio::test] async fn experimental_popup_omits_stable_guardian_approval() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/utils/cargo-bin/src/lib.rs b/codex-rs/utils/cargo-bin/src/lib.rs index 6517a77c94fa..2cb25a6b17dd 100644 --- a/codex-rs/utils/cargo-bin/src/lib.rs +++ b/codex-rs/utils/cargo-bin/src/lib.rs @@ -91,10 +91,15 @@ fn resolve_bin_from_env(key: &str, value: OsString) -> Result /tmp/codex-app-server.log -``` - -```sh -RUST_LOG=codex_core::tools::js_repl=trace \ -LOG_FORMAT=json \ -codex app-server \ -2> /tmp/codex-app-server.log -``` - -In both cases, inspect `/tmp/codex-app-server.log` or whatever sink captures the process `stderr`. - -## Vendored parser asset (`meriyah.umd.min.js`) - -The kernel embeds a vendored Meriyah bundle at: - -- `codex-rs/core/src/tools/js_repl/meriyah.umd.min.js` - -Current source is `meriyah@7.0.0` from npm (`dist/meriyah.umd.min.js`). -Licensing is tracked in: - -- `third_party/meriyah/LICENSE` -- `NOTICE` - -### How this file was sourced - -From a clean temp directory: - -```sh -tmp="$(mktemp -d)" -cd "$tmp" -npm pack meriyah@7.0.0 -tar -xzf meriyah-7.0.0.tgz -cp package/dist/meriyah.umd.min.js /path/to/repo/codex-rs/core/src/tools/js_repl/meriyah.umd.min.js -cp package/LICENSE.md /path/to/repo/third_party/meriyah/LICENSE -``` - -### How to update to a newer version - -1. Replace `7.0.0` in the commands above with the target version. -2. Copy the new `dist/meriyah.umd.min.js` into `codex-rs/core/src/tools/js_repl/meriyah.umd.min.js`. -3. Copy the package license into `third_party/meriyah/LICENSE`. -4. Update the version string in the header comment at the top of `meriyah.umd.min.js`. -5. Update `NOTICE` if the upstream copyright notice changed. -6. Run the relevant `js_repl` tests. diff --git a/third_party/meriyah/LICENSE b/third_party/meriyah/LICENSE deleted file mode 100644 index 182e69f3514d..000000000000 --- a/third_party/meriyah/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2019 and later, KFlash and others. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE.