Commit Graph

76 Commits

  • stop blocking unified_exec on Windows (#19435)
    ## Summary
    - remove the Windows-specific unified-exec environment block from tool
    selection
    - keep `unified_exec` default-off on Windows unless the feature is
    explicitly enabled
    - normalize model-provided `shell_type = unified_exec` to
    `shell_command` when the feature is disabled
    - drop obsolete tests tied to the removed environment gate and keep the
    feature-flag regression coverage
    
    ## Why
    Now that the session/long-lived process backend is implemented for the
    Windows sandbox, we don't need to hard disable it anymore. We will be
    rolling out slowly using a feature gate.
    
    ## Impact
    This allows manual Windows opt-in in CLI and app-backed flows while
    preserving the existing default-off behavior for Windows users.
    
    ---------
    
    Co-authored-by: canvrno-oai <kbond@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • [tool_suggest] Improve tool_suggest triggering conditions. (#20091)
    ## Summary
    - Tighten `tool_suggest` guidance so it prefers explicit plugin install
    requests, while still allowing a connector install when the relevant
    plugin is already installed and a needed connector from that plugin is
    missing.
    - Tell the model not to call `tool_suggest` in parallel with other
    tools.
    
    ## Testing
    - `cargo test -p codex-tools tool_suggest`
    - `cargo test -p codex-core tool_suggest`
  • Delete multi_agent_v2 followup_task interrupt parameter (#20139)
    Messages sent with `followup_task` already arrive at their target
    recipient promptly (at message boundaries while sampling, or after the
    pending tool call completes) -- having `interrupt` is not worth the
    added complexity.
  • feat: disable capabilities by model provider (#19442)
    ## Why
    
    Unsupported features must fail closed and Codex must not expose
    OpenAI-hosted fallback paths when the active provider cannot support
    them. In practice, Bedrock should not surface app connectors, MCP
    servers, tool search/suggestions, image generation, web search, or JS
    REPL until those paths are explicitly supported for that provider.
    
    This PR moves that decision into provider-owned capability metadata
    instead of scattering Bedrock-specific checks across callers.
    
    ## What changed
    
    - Adds `ProviderCapabilities` to `codex-model-provider`, with default
    support for existing providers and a Bedrock override that disables
    unsupported launch surfaces.
    - Adds `ToolCapabilityBounds` to `codex-tools` so provider capability
    limits can clamp otherwise-enabled tool config.
    - Applies capability bounds when building session and review-thread tool
    config.
    - Routes MCP/app connector configuration through
    `McpManager::mcp_config`, which filters configured MCP servers and app
    connectors based on the active provider.
    - Updates app-server MCP list/read paths to use the filtered MCP config.
    - Adds coverage for default provider capabilities, Bedrock disabled
    capabilities, and optional tool-surface clamping.
    
    ## Testing
    
    built locally and verified that bedrock responses api now return without
    errors calling unsupported tools.
  • Support disabling tool suggest for specific tools. (#20072)
    ## Summary
    - Add `disable_tool_suggest` to app and plugin config, schema, and
    TypeScript output
    - Exclude disabled connectors and plugins from tool suggestion discovery
    - Persist "never show again" tool-suggestion choices back into
    `config.toml`
    - Update config docs and add coverage for connector and plugin
    suppression
    
    ## Testing
    - Added and updated unit tests for config persistence and tool-suggest
    filtering
    - Not run (not requested)
  • Make MultiAgentV2 wait minimum configurable (#20052)
    ## Why
    
    MultiAgentV2 `wait_agent` currently clamps short waits to a fixed 10
    second minimum. That default is still useful for preventing tight
    polling loops, but it is too rigid for environments that need faster
    mailbox wake-up checks or a larger minimum to discourage frequent
    polling.
    
    This PR makes the minimum wait timeout configurable from the existing
    MultiAgentV2 feature config section, so operators can tune the behavior
    without changing the legacy multi-agent tool surface.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.min_wait_timeout_ms`.
    - Defaulted the new setting to the existing 10 second floor.
    - Validated the configured value as `1..=3600000`, matching the existing
    one hour maximum wait bound.
    - Applied the configured minimum to MultiAgentV2 `wait_agent` runtime
    clamping.
    - Plumbed the configured minimum into the `wait_agent` tool schema,
    including the effective default when the minimum is above the normal 30
    second default.
    - Regenerated `core/config.schema.json`.
    
    ## Verification
    
    - `cargo test -p codex-features`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core --lib multi_agent_v2`
    - `just fix -p codex-core`
  • permissions: make runtime config profile-backed (#19606)
    ## Why
    
    This supersedes #19391. During stack repair, GitHub marked #19391 as
    merged into a temporary stack branch rather than into `main`, so the
    runtime-config change needed a fresh PR.
    
    `PermissionProfile` is now the canonical permissions shape after #19231
    because it can distinguish `Managed`, `Disabled`, and `External`
    enforcement while also carrying filesystem rules that legacy
    `SandboxPolicy` cannot represent cleanly. Core config and session state
    still needed to accept profile-backed permissions without forcing every
    profile through the strict legacy bridge, which rejected valid runtime
    profiles such as direct write roots.
    
    The unrelated CI/test hardening that previously rode along with this PR
    has been split into #19683 so this PR stays focused on the permissions
    model migration.
    
    ## What Changed
    
    - Adds `Permissions.permission_profile` and
    `SessionConfiguration.permission_profile` as constrained runtime state,
    while keeping `sandbox_policy` as a legacy compatibility projection.
    - Introduces profile setters that keep `PermissionProfile`, split
    filesystem/network policies, and legacy `SandboxPolicy` projections
    synchronized.
    - Uses a compatibility projection for requirement checks and legacy
    consumers instead of rejecting profiles that cannot round-trip through
    `SandboxPolicy` exactly.
    - Updates config loading, config overrides, session updates, turn
    context plumbing, prompt permission text, sandbox tags, and exec request
    construction to carry profile-backed runtime permissions.
    - Preserves configured deny-read entries and `glob_scan_max_depth` when
    command/session profiles are narrowed.
    - Adds `PermissionProfile::read_only()` and
    `PermissionProfile::workspace_write()` presets that match legacy
    defaults.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19606).
    * #19395
    * #19394
    * #19393
    * #19392
    * __->__ #19606
  • Add goal model tools (3 / 5) (#18075)
    Adds the model-facing goal tools on top of the app-server API from PR 2.
    
    ## Why
    
    Once goals are persisted and exposed to clients, the model needs a
    small, constrained tool surface for goal workflows. The tool contract
    should let the model inspect goals, create them only when explicitly
    requested, and mark them complete without giving it broad control over
    user/runtime-owned state.
    
    ## What changed
    
    - Added `get_goal`, `create_goal`, and `update_goal` tool specs behind
    the `goals` feature flag.
    - Added core goal tool handlers that validate objectives and token
    budgets before mutating persisted state.
    - Constrained `create_goal` to create only when no goal exists, with
    optional `token_budget` only when a budget is explicitly provided.
    - Tightened the `create_goal` instructions so the model does not infer
    goals from ordinary task requests.
    - Constrained `update_goal` to expose only goal completion; pause,
    resume, clear, and budget-limited transitions remain user- or
    runtime-controlled.
    - Registered the goal tools in the tool registry and kept them out of
    review contexts where they should not appear.
    
    ## Verification
    
    - Added tool-registry coverage for feature gating and tool availability.
    - Added core session tests for create/get/update behavior, duplicate
    goal rejection, budget validation, and completion-only updates.
  • feat: surface multi-agent thread limit in spawn description (#19360)
    ## Summary
    - Thread `agent_max_threads` into `ToolsConfig` and
    `SpawnAgentToolOptions`.
    - Render the configured `max_concurrent_threads_per_session` value in
    the MultiAgentV2 `spawn_agent` description.
    - Cover the description text in `codex-tools` unit tests and
    `codex-core` tool spec tests.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core spawn_agent_description`
    - `git diff --check`
    
    ## Notes
    - `cargo test -p codex-core` was also attempted, but unrelated
    environment-sensitive tests failed with the active local environment.
    Examples: approvals reviewer defaults observed `AutoReview` instead of
    `User`, request-permissions event tests did not emit events, and
    proxy-env tests saw `http://127.0.0.1:50604` from the active proxy
    environment.
    
    Co-authored-by: Codex <noreply@openai.com>
  • guide Windows to use -WindowStyle Hidden for Start-Process calls (#19044)
    Sometimes codex runs `Start-Process` to start up a service or something
    similar, which launches a user-visible powershell window that probably
    doesn't get cleaned up. This instruction change encourages it to do so
    using a hidden window.
    
    This was reported in
    https://openai.slack.com/archives/C09K6H5DZC4/p1776741272870519
    
    One caveat is that this change won't do anything to cleanup these
    processes, but it will stop them from polluting the user's visible
    workspace
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [tool search] support namespaced deferred dynamic tools (#18413)
    Deferred dynamic tools need to round-trip a namespace so a tool returned
    by `tool_search` can be called through the same registry key that core
    uses for dispatch.
    
    This change adds namespace support for dynamic tool specs/calls,
    persists it through app-server thread state, and routes dynamic tool
    calls by full `ToolName` while still sending the app the leaf tool name.
    Deferred dynamic tools must provide a namespace; non-deferred dynamic
    tools may remain top-level.
    
    It also introduces `LoadableToolSpec` as the shared
    function-or-namespace Responses shape used by both `tool_search` output
    and dynamic tool registration, so dynamic tools use the same wrapping
    logic in both paths.
    
    Validation:
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tool_search`
    
    ---------
    
    Co-authored-by: Sayan Sisodiya <sayan@openai.com>
  • [codex] prefer inherited spawn agent model (#18701)
    This updates the spawn-agent tool contract so subagents are presented as
    inheriting the parent model by default. The visible model list is now
    framed as optional overrides, the model parameter tells callers to leave
    it unset and the delegation guidance no longer nudges models toward
    picking a smaller/mini override.
    
    Fixes reports that 5.4 would occasionally pick 5.2 or lower as
    sub-agents.
  • Update image outputs to default to high detail (#18386)
    Do not assume the default `detail`.
  • enable tool search over dynamic tools (#18263)
    ## Summary
    
    - Normalize deferred MCP and dynamic tools into `ToolSearchEntry` values
    before constructing `ToolSearchHandler`.
    - Move the tool-search entry adapter out of `tools/handlers` and into
    `tools/tool_search_entry.rs` so the handlers directory stays focused on
    handlers.
    - Keep `ToolSearchHandler` operating over one generic entry list for
    BM25 search, namespace grouping, and per-bucket default limits.
    
    ## Why
    
    Follow-up cleanup for #17849. The dynamic tool-search support made the
    handler juggle source-specific MCP and dynamic tool lists, index
    arithmetic, output conversion, and namespace emission. This keeps source
    adaptation outside the handler so the search loop itself is smaller and
    source-agnostic.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-core tools::handlers::tool_search::tests`
    - `git diff --check`
    - `cargo test -p codex-core` currently fails in unrelated
    `plugins::manager::tests::list_marketplaces_ignores_installed_roots_missing_from_config`;
    rerunning that single test fails the same way at
    `core/src/plugins/manager_tests.rs:1692`.
    
    ---------
    
    Co-authored-by: pash <pash@openai.com>
  • Launch image generation by default (#17153)
    ## Summary
    - Promote `image_generation` from under-development to stable
    - Enable image generation by default in the feature registry
    - Update feature coverage for the new launch-state expectation
    - Add the missing image-generation auth fixture field in a tool registry
    test
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-features`
    - `cargo test -p codex-tools` currently fails:
    `test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search` needs
    its expected default tool list updated for `image_generation`
  • [code mode] defer mcp tools from exec description (#17287)
    ## Summary
    - hide deferred MCP/app nested tool descriptions from the `exec` prompt
    in code mode
    - add short guidance that omitted nested tools are still available
    through `ALL_TOOLS`
    - cover the code_mode_only path with an integration test that discovers
    and calls a deferred app tool
    
    ## Motivation
    `code_mode_only` exposes only top-level `exec`/`wait`, but the `exec`
    description could still include a large nested-tool reference. This
    keeps deferred nested tools callable while avoiding that prompt bloat.
    
    ## Tests
    - `just fmt`
    - `just fix -p codex-code-mode`
    - `just fix -p codex-tools`
    - `cargo test -p codex-code-mode
    exec_description_mentions_deferred_nested_tools_when_available`
    - `cargo test -p codex-tools
    create_code_mode_tool_matches_expected_spec`
    - `cargo test -p codex-core
    code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools`
  • Update ToolSearch to be enabled by default (#17854)
    ## Summary
    - Promote `Feature::ToolSearch` to `Stable` and enable it in the default
    feature set
    - Update feature tests and tool registry coverage to match the new
    default
    - Adjust the search-tool integration test to assert the default-on path
    and explicit disable fallback
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-features`
    - `cargo test -p codex-core --test all search_tool`
    - `cargo test -p codex-tools`
  • Support original-detail metadata on MCP image outputs (#17714)
    ## Summary
    - honor `_meta["codex/imageDetail"] == "original"` on MCP image content
    and map it to `detail: "original"` where supported
    - strip that detail back out when the active model does not support
    original-detail image inputs
    - update code-mode `image(...)` to accept individual MCP image blocks
    - teach `js_repl` / `codex.emitImage(...)` to preserve the same hint
    from raw MCP image outputs
    - document the new `_meta` contract and add generic RMCP-backed coverage
    across protocol, core, code-mode, and js_repl paths
  • Fix empty tool descriptions (#17946)
    ## Summary
    - Ensure direct namespaced MCP tool groups are emitted with a non-empty
    namespace description even when namespace metadata is missing or blank.
    - Add regression coverage for missing MCP namespace descriptions.
    
    ## Cause
    Latest `main` can serialize a direct namespaced MCP tool group with an
    empty top-level `description`. The namespace description path used
    `unwrap_or_default()` when `tool_namespaces` did not include metadata
    for that namespace, so the outbound Responses API payload could contain
    a tool like `{"type":"namespace","description":""}`. The Responses API
    rejects that because namespace tool descriptions must be a non-empty
    string.
    
    ## Fix
    - Add a fallback namespace description: `Tools in the <namespace>
    namespace.`
    - Preserve provided namespace descriptions after trimming, but treat
    blank descriptions as missing.
    
    ### Issue I am seeing
    This is what I am seeing on the local build.
    <img width="1593" height="488" alt="Screenshot 2026-04-15 at 10 55 55
    AM"
    src="https://github.com/user-attachments/assets/bab668ba-bf17-4c71-be4e-b102202fce57"
    />
    
    ---------
    
    Co-authored-by: Sayan Sisodiya <sayan@openai.com>
  • register all mcp tools with namespace (#17404)
    stacked on #17402.
    
    MCP tools returned by `tool_search` (deferred tools) get registered in
    our `ToolRegistry` with a different format than directly available
    tools. this leads to two different ways of accessing MCP tools from our
    tool catalog, only one of which works for each. fix this by registering
    all MCP tools with the namespace format, since this info is already
    available.
    
    also, direct MCP tools are registered to responsesapi without a
    namespace, while deferred MCP tools have a namespace. this means we can
    receive MCP `FunctionCall`s in both formats from namespaces. fix this by
    always registering MCP tools with namespace, regardless of deferral
    status.
    
    make code mode track `ToolName` provenance of tools so it can map the
    literal JS function name string to the correct `ToolName` for
    invocation, rather than supporting both in core.
    
    this lets us unify to a single canonical `ToolName` representation for
    each MCP tool and force everywhere to use that one, without supporting
    fallbacks.
  • Always enable original image detail on supported models (#17665)
    ## Summary
    
    This PR removes `image_detail_original` as a runtime experiment and
    makes original image detail available whenever the selected model
    supports it.
    
    Concretely, this change:
    - drops the `image_detail_original` feature flag from the feature
    registry and generated config schema
    - makes tool-emitted image detail depend only on
    `ModelInfo.supports_image_detail_original`
    - updates `view_image` and `code_mode`/`js_repl` image emission to use
    that capability check directly
    - removes now-redundant experiment-specific tests and instruction
    coverage
    - keeps backward compatibility for existing configs by silently ignoring
    a stale `features.image_detail_original` entry
    
    The net effect is that `detail: "original"` is always available on
    supported models, without requiring an experiment toggle.
  • chore: refactor name and namespace to single type (#17402)
    avoid passing them both around, unify on a type. this now also keys
    `ToolRegistry`.
    
    tests pass
  • Add output_schema to code mode render (#17210)
    This updates code-mode tool rendering so MCP tools can surface
    structured output types from their `outputSchema`.
    
    What changed:
    - Detect MCP tool-call result wrappers from the output schema shape
    instead of relying on tool-name parsing or provenance flags.
    - Render shared TypeScript aliases once for MCP tool results
    (`CallToolResult`, `ContentBlock`, etc.) so multiple MCP tool
    declarations stay compact.
    - Type `structuredContent` from the tool definition's `outputSchema`
    instead of rendering it as `unknown`.
    - Update the shared MCP aliases to match the MCP draft `CallToolResult`
    schema more closely.
    
    Example:
    - Before: `declare const tools: { mcp__rmcp__echo(args: { env_var?:
    string; message: string; }): Promise<{ _meta?: unknown; content:
    Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };`
    - After: `declare const tools: { mcp__rmcp__echo(args: { env_var?:
    string; message: string; }): Promise<CallToolResult<{ echo: string; env:
    string | null; }>>; };`
  • preserve search results order in tool_search_output (#17263)
    we used to alpha-sort tool search results because we were using
    `BTreeMap`, which threw away the actual search result ordering.
    
    Now we use a vec to preserve it.
    
    ### Tests
    Updated tests
  • [mcp] Expand tool search to custom MCPs. (#16944)
    - [x] Expand tool search to custom MCPs.
    - [x] Rename several variables/fields to be more generic.
    
    Updated tool & server name lifecycles:
    
    **Raw Identity**
    
    ToolInfo.server_name is raw MCP server name.
    ToolInfo.tool.name is raw MCP tool name.
    MCP calls route back to raw via parse_tool_name() returning
    (tool.server_name, tool.tool.name).
    mcpServerStatus/list now groups by raw server and keys tools by
    Tool.name: mod.rs:599
    App-server just forwards that grouped raw snapshot:
    codex_message_processor.rs:5245
    
    **Callable Names**
    
    On list-tools, we create provisional callable_namespace / callable_name:
    mcp_connection_manager.rs:1556
    For non-app MCP, provisional callable name starts as raw tool name.
    For codex-apps, provisional callable name is sanitized and strips
    connector name/id prefix; namespace includes connector name.
    Then qualify_tools() sanitizes callable namespace + name to ASCII alnum
    / _ only: mcp_tool_names.rs:128
    Note: this is stricter than Responses API. Hyphen is currently replaced
    with _ for code-mode compatibility.
    
    **Collision Handling**
    
    We do initially collapse example-server and example_server to the same
    base.
    Then qualify_tools() detects distinct raw namespace identities behind
    the same sanitized namespace and appends a hash to the callable
    namespace: mcp_tool_names.rs:137
    Same idea for tool-name collisions: hash suffix goes on callable tool
    name.
    Final list_all_tools() map key is callable_namespace + callable_name:
    mcp_connection_manager.rs:769
    
    **Direct Model Tools**
    
    Direct MCP tool declarations use the full qualified sanitized key as the
    Responses function name.
    The raw rmcp Tool is converted but renamed for model exposure.
    
    **Tool Search / Deferred**
    
    Tool search result namespace = final ToolInfo.callable_namespace:
    tool_search.rs:85
    Tool search result nested name = final ToolInfo.callable_name:
    tool_search.rs:86
    Deferred tool handler is registered as "{namespace}:{name}":
    tool_registry_plan.rs:248
    When a function call comes back, core recombines namespace + name, looks
    up the full qualified key, and gets the raw server/tool for MCP
    execution: codex.rs:4353
    
    **Separate Legacy Snapshot**
    
    collect_mcp_snapshot_from_manager_with_detail() still returns a map
    keyed by qualified callable name.
    mcpServerStatus/list no longer uses that; it uses
    McpServerStatusSnapshot, which is raw-inventory shaped.
  • Fix missing fields (#17149)
    Fix missing `image_generation_tool_auth_allowed` in two locations.
  • release ready, enabling only for siwc users (#17046)
    **Disabling Image-Gen for Non-SIWC Codex Users**
    
    We are only enabling image-gen feature for SIWC Codex users until there
    comes a fix in ResponsesAPI to omit output from responses.completed, to
    prevent the following issues:
    
    1. websocket blows up due to heavier load (images) than before (text) 
    2. http parser streams through n^2 of n-base64 bytes (sum of base64s of
    all images generated in turn) that causes long delays in
    turn_completion.
  • Configure multi_agent_v2 spawn agent hints (#17071)
    Allow multi_agent_v2 features to have its own temporary configuration
    under `[features.multi_agent_v2]`
    
    ```
    [features.multi_agent_v2]
    enabled = true
    usage_hint_enabled = false
    usage_hint_text = "Custom delegation guidance."
    hide_spawn_agent_metadata = true
    ```
    
    Absent `usage_hint_text` means use the default hint.
    
    ```
    [features]
    multi_agent_v2 = true
    ```
    
    still works as the boolean shorthand.
  • Support anyOf and enum in JsonSchema (#16875)
    This brings us into better alignment with the JSON schema subset that is
    supported in
    <https://developers.openai.com/api/docs/guides/structured-outputs#supported-schemas>,
    and also allows us to render richer function signatures in code mode
    (e.g., anyOf{null, OtherObjectType})
  • Add regression tests for JsonSchema (#17052)
    Tests added for existing JsonSchema in
    `codex-rs/tools/src/json_schema_tests.rs`:
    
    - `parse_tool_input_schema_coerces_boolean_schemas`
    - `parse_tool_input_schema_infers_object_shape_and_defaults_properties`
    - `parse_tool_input_schema_normalizes_integer_and_missing_array_items`
    - `parse_tool_input_schema_sanitizes_additional_properties_schema`
    -
    `parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only`
    - `parse_tool_input_schema_infers_number_from_numeric_keywords`
    - `parse_tool_input_schema_infers_number_from_multiple_of`
    -
    `parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords`
    - `parse_tool_input_schema_defaults_empty_schema_to_string`
    - `parse_tool_input_schema_infers_array_from_prefix_items`
    -
    `parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object`
    -
    `parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only`
    
    Tests that we expect to fail on the baseline normalizer, but pass with
    the new JsonSchema:
    
    - `parse_tool_input_schema_preserves_nested_nullable_type_union`
    - `parse_tool_input_schema_preserves_nested_any_of_property`
  • Use model metadata for Fast Mode status (#16949)
    Fast Mode status was still tied to one model name in the TUI and
    model-list plumbing. This changes the model metadata shape so a model
    can advertise additional speed tiers, carries that field through the
    app-server model list, and uses it to decide when to show Fast Mode
    status.
    
    For people using Codex, the behavior is intended to stay the same for
    existing models. Fast Mode still requires the existing signed-in /
    feature-gated path; the difference is that the UI can now recognize any
    model the model list marks as Fast-capable, instead of requiring a new
    client-side slug check.
  • Disable env-bound tools when exec server is none (#16349)
    ## Summary
    - make `CODEX_EXEC_SERVER_URL=none` map to an explicit disabled
    environment mode instead of inferring from a missing URL
    - expose environment capabilities (`exec_enabled`, `filesystem_enabled`)
    so tool building can gate behavior explicitly and future
    multi-environment work has a clearer seam
    - suppress env-backed tools when the relevant capability is unavailable,
    including exec tools, `js_repl`, `apply_patch`, `list_dir`, and
    `view_image`
    - keep handler/runtime backstops so disabled environments still reject
    execution if a tool path somehow bypasses registration
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-exec-server`
    - `cargo test -p codex-tools
    disabled_environment_omits_environment_backed_tools`
    - `cargo test -p codex-tools
    environment_capabilities_gate_exec_and_filesystem_tools_independently`
    - remote devbox Bazel build via `codex-applied-devbox`:
    `//codex-rs/cli:cli`
  • Move tool registry plan tests into codex-tools (#16521)
    ## Why
    
    #16513 moved pure tool-registry planning into `codex-tools`, but much of
    the corresponding spec/feature-gating coverage still lived in
    `codex-core`. That leaves the tests for planner behavior in the crate
    that no longer owns that logic and makes the next extraction steps
    harder to review.
    
    ## What
    
    Move the planner-only `spec_tests.rs` coverage into
    `codex-rs/tools/src/tool_registry_plan_tests.rs` and wire it up from
    `codex-rs/tools/src/tool_registry_plan.rs` using the crate-local `#[path
    = "tool_registry_plan_tests.rs"] mod tests;` pattern.
    
    The `codex-core` test file now keeps the core-side integration checks:
    router-visible model tool lists, namespaced handler alias registration,
    shell adapter behavior, and MCP schema edge cases that still exercise
    the `core` binding layer.
    
    ## Verification
    
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::spec::tests`
  • Extract tool registry planning into codex-tools (#16513)
    ## Why
    This is a larger step in the `codex-core` -> `codex-tools` migration
    called out in `AGENTS.md`.
    
    `codex-rs/core/src/tools/spec.rs` had become mostly pure tool-spec
    assembly plus handler registration. That made it hard to move more of
    the tool-definition layer into `codex-tools`, because the runtime
    binding and the crate-independent planning logic were still interleaved
    in one function.
    
    Splitting those concerns gives `codex-tools` ownership of the
    declarative registry plan while keeping `codex-core` responsible for
    instantiating concrete handlers.
    
    ## What Changed
    - Add a `codex-tools` registry-plan layer in
    `codex-rs/tools/src/tool_registry_plan.rs` and
    `codex-rs/tools/src/tool_registry_plan_types.rs`.
    - Move feature-gated tool-spec assembly, MCP/dynamic tool conversion,
    tool-search aliases, and code-mode nested-plan expansion into
    `codex-tools`.
    - Keep `codex-rs/core/src/tools/spec.rs` as the core-side adapter that
    maps each planned handler kind to concrete runtime handler instances.
    - Update `spec_tests.rs` to import the moved `codex_tools` symbols
    directly instead of relying on top-level `spec.rs` re-exports.
    
    This is intended to be a straight refactor with no behavior change and
    no new test surface.
    
    ## Verification
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::spec::tests`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16513).
    * #16521
    * __->__ #16513
  • Extract code-mode nested tool collection into codex-tools (#16509)
    ## Why
    This is another small step in the `codex-core` -> `codex-tools`
    migration described in `AGENTS.md`.
    
    `core/src/tools/spec.rs` and `core/src/tools/code_mode/mod.rs` were both
    hand-rolling the same pure transformation: convert visible `ToolSpec`s
    into code-mode nested tool definitions, then sort and deduplicate by
    tool name. That logic does not depend on core runtime state or handlers,
    so keeping it in `codex-core` makes `spec.rs` harder to peel out later
    than it needs to be.
    
    ## What Changed
    - Add `collect_code_mode_tool_definitions()` to
    `codex-rs/tools/src/code_mode.rs`.
    - Reuse that helper from `codex-rs/core/src/tools/spec.rs` when
    assembling the `exec` tool description.
    - Reuse the same helper from `codex-rs/core/src/tools/code_mode/mod.rs`
    when exposing nested tool metadata to the code-mode runtime.
    
    This is intended to be a straight refactor with no behavior change and
    no new test surface.
    
    ## Verification
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::spec::tests`
    - `cargo test -p codex-core code_mode_only_`
  • Extract request_user_input normalization into codex-tools (#16503)
    ## Why
    This is another incremental step in the `codex-core` -> `codex-tools`
    migration called out in `AGENTS.md`: keep pure tool-definition and
    wire-shaping logic out of `codex-core` so the core crate can stay
    focused on runtime orchestration.
    
    `request_user_input` already had its spec and mode-availability helpers
    in `codex-tools` after #16471. The remaining argument validation and
    normalization still lived in the core runtime handler, which left that
    tool split across the two crates.
    
    ## What Changed
    - Export `REQUEST_USER_INPUT_TOOL_NAME` and
    `normalize_request_user_input_args()` from
    `codex-rs/tools/src/request_user_input_tool.rs`.
    - Use that `codex-tools` surface from `codex-rs/core/src/tools/spec.rs`
    and `codex-rs/core/src/tools/handlers/request_user_input.rs`.
    - Keep the core handler responsible for payload parsing, session
    dispatch, cancellation handling, and response serialization.
    
    This is intended to be a straight refactor with no behavior change.
    
    ## Verification
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core request_user_input`
  • Extract tool-suggest wire helpers into codex-tools (#16499)
    ## Why
    
    This is another straight-refactor step in the `codex-tools` migration.
    
    `core/src/tools/handlers/tool_suggest.rs` still owned request/response
    payload structs, elicitation metadata shaping, and connector-completion
    predicates that do not depend on `codex-core` session/runtime internals.
    Per the `AGENTS.md` guidance to keep shrinking `codex-core`, this moves
    that pure wire-format logic into `codex-rs/tools` so the core handler
    keeps only session orchestration, plugin/config refresh, and MCP cache
    updates.
    
    ## What changed
    
    - Added `codex-rs/tools/src/tool_suggest.rs` and exported its API from
    `codex-rs/tools/src/lib.rs`.
    - Moved `ToolSuggestArgs`, `ToolSuggestResult`, `ToolSuggestMeta`,
    `build_tool_suggestion_elicitation_request()`,
    `all_suggested_connectors_picked_up()`, and
    `verified_connector_suggestion_completed()` into `codex-tools`.
    - Rewired `core/src/tools/handlers/tool_suggest.rs` to consume those
    exports directly.
    - Ported the existing pure helper tests from
    `core/src/tools/handlers/tool_suggest_tests.rs` to
    `tools/src/tool_suggest_tests.rs` without adding new behavior coverage.
    
    ## Validation
    
    ```shell
    cargo test -p codex-tools
    cargo test -p codex-core tools::handlers::tool_suggest::tests
    just argument-comment-lint
    ```
  • Extract tool-search output helpers into codex-tools (#16497)
    ## Why
    
    This is the next straight-refactor step in the `codex-tools` migration
    that follows #16493.
    
    `codex-rs/core` still owned a chunk of pure tool-discovery metadata and
    response shaping even though the corresponding `tool_search` /
    `tool_suggest` specs already live in `codex-rs/tools`. Per the guidance
    in `AGENTS.md`, this moves that crate-agnostic logic out of `codex-core`
    so the handler crate keeps only the BM25 ranking/orchestration and
    runtime glue.
    
    ## What changed
    
    - Moved the canonical `tool_search` / `tool_suggest` tool names and the
    `tool_search` default limit into `codex-rs/tools/src/tool_discovery.rs`.
    - Added `ToolSearchResultSource` and
    `collect_tool_search_output_tools()` in `codex-tools` so namespace
    grouping and deferred Responses API tool serialization happen outside
    `codex-core`.
    - Rewired `ToolSearchHandler`, `ToolSuggestHandler`, and
    `core/src/tools/spec.rs` to consume those exports directly from
    `codex-tools`.
    - Ported the existing `tool_search` serializer tests from
    `core/src/tools/handlers/tool_search_tests.rs` to
    `tools/src/tool_discovery_tests.rs` without adding new behavior
    coverage.
    
    ## Validation
    
    ```shell
    cargo test -p codex-tools
    cargo test -p codex-core tools::spec::tests
    just argument-comment-lint
    ```
  • Extract built-in tool spec constructors into codex-tools (#16493)
    ## Why
    
    `core/src/tools/spec.rs` still had a few built-in tool specs assembled
    inline even though those definitions are pure metadata and already live
    conceptually in `codex-tools`. Keeping that construction in `codex-core`
    makes `spec.rs` do more than registry orchestration and slows the
    migration toward a right-sized `codex-tools` crate.
    
    This continues the extraction stack from #16379, #16471, #16477, #16481,
    and #16482.
    
    ## What Changed
    
    - added `create_local_shell_tool()`, `create_web_search_tool(...)`, and
    `create_image_generation_tool(...)` to `codex-rs/tools/src/tool_spec.rs`
    - exported those helpers from `codex-rs/tools/src/lib.rs`
    - switched `codex-rs/core/src/tools/spec.rs` to call those helpers
    instead of constructing `ToolSpec::LocalShell`, `ToolSpec::WebSearch`,
    and `ToolSpec::ImageGeneration` inline
    - removed the remaining core-local web-search content-type constant and
    made the affected spec test assert the literal expected values directly
    
    This is intended to be a straight refactor: tool behavior and wire shape
    should not change.
    
    ## Testing
    
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::spec::tests`
  • Extract update_plan tool spec into codex-tools (#16481)
    ## Why
    
    `codex-rs/core/src/tools/handlers/plan.rs` still owned both the
    `update_plan` runtime handler and the static tool definition. The tool
    definition is pure metadata, so keeping it in `codex-core` works against
    the ongoing effort to move tool-spec code into `codex-tools` and keep
    `codex-core` focused on orchestration and execution paths.
    
    This continues the extraction work from #16379, #16471, and #16477.
    
    ## What Changed
    
    - added `codex-rs/tools/src/plan_tool.rs` with
    `create_update_plan_tool()`
    - re-exported that constructor from `codex-rs/tools/src/lib.rs`
    - updated `codex-rs/core/src/tools/spec.rs` and
    `codex-rs/core/src/tools/spec_tests.rs` to use the `codex-tools` export
    instead of a core-local static
    - removed the old `PLAN_TOOL` definition from
    `codex-rs/core/src/tools/handlers/plan.rs`; the `PlanHandler` runtime
    logic still stays in `codex-core`
    - tightened two `codex-core` aliases to `#[cfg(test)]` now that
    production code no longer needs them
    
    ## Testing
    
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::spec::tests`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16481).
    * #16482
    * __->__ #16481