Repository navigation
refactor(rocm): break the chat and freeform-planner import cycle with three leaf modules - #888
Conversation
`chat.rs` imports from `freeform_planner.rs` and `freeform_planner.rs` calls back into `chat::`, so the two modules form an import cycle. Part of it is a handful of small argv helpers that live in one module and are used by the other. Move them into a new leaf module, `rocm_argv.rs`, with no `crate::` dependencies: `chat_cli_arg_value`, `chat_cli_arg_value_checked`, `chat_cli_has_flag` and `ensure_flag` from `chat.rs`, and `format_structured_tool_call`, `quote_tool_arg`, `serve_args_request_cpu_device` and `device_policy_is_cpu_mode` from `freeform_planner.rs`. The bodies are unchanged; `chat_cli_arg_value_checked` and `device_policy_is_cpu_mode` become `pub(crate)` because they are now called across a module boundary. `freeform_planner.rs` calls `rocm_argv` directly instead of `chat::chat_cli_*`. Every other caller keeps its path: `chat.rs` re-exports `ensure_flag` (read by `freeform_execution.rs`) and `freeform_planner.rs` re-exports `format_structured_tool_call` and `serve_args_request_cpu_device`. Pure move: no behaviour change. docs/architecture.md and the one comment that named `quote_tool_arg`'s old file are updated. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
The planner and the chat tool policy both read the same facts out of a natural-language request: which phrases it contains, which install folder, TheRock build date or TheRock version it names, and which serving engine it asks for. They lived in `chat.rs` (and one in `freeform_planner.rs`), so `freeform_planner.rs` had to import `chat` for them, which is half of the import cycle between the two modules. Move them into a new leaf module, `prompt_text.rs`, which depends only on `therock::RuntimeVersionSelector`: `any_substring`, `requested_install_prefix_from_prompt`, `requested_bare_install_prefix_from_prompt`, `clean_requested_install_prefix`, `looks_like_user_path`, `requested_therock_build_date_from_prompt`, `requested_therock_version_from_prompt` and `looks_like_therock_runtime_version` from `chat.rs`, and `infer_engine_from_request` from `freeform_planner.rs`. `infer_engine_from_request_does_not_match_aim_as_a_substring` moves with its function. The bodies are unchanged; `requested_therock_version_from_prompt` becomes `pub(crate)` because `chat.rs` now calls it across a module boundary. `freeform_planner.rs` calls `prompt_text` directly instead of `chat::any_substring` and `chat::requested_*`. `chat.rs` re-exports `any_substring`, which `freeform_answer.rs` still reaches as `chat::any_substring`. Pure move: no behaviour change. docs/architecture.md is updated. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
`freeform_planner.rs` still imported `chat` for one thing after the argv and prompt-text helpers moved out: `chat::validate_chat_rocm_command_safety`, the check that refuses a CPU or public-bind `serve` and a system install folder. That was the last edge back from the planner to `chat.rs`, so the two modules still imported each other. Move the tool-call validation and approval policy into a new module, `tool_policy.rs`: `ChatRocmCommandAction`, `ChatToolApprovalRequest`, `chat_tool_approval_request`, `validate_chat_tool_call` and the `validate_chat_*_tool_call` family, `validate_required_chat_string`, `validate_optional_chat_integer`, `chat_rocm_command_action`, `normalized_chat_rocm_command_args`, `chat_install_driver_action`, `chat_rocm_command_action_from_args`, `canonicalize_chat_rocm_command`, `validate_chat_rocm_command_safety`, `chat_install_prefix_is_system`, `chat_tool_call_is_read_only`, `rocm_chat_tool_requested_args`, `rocm_chat_tool_requested_command`, `push_optional_json_cli_arg` and `json_string`. The 37 tests that exercise only these items move with them; their names are unchanged. The bodies are unchanged. `normalized_chat_rocm_command_args`, `json_string`, `chat_install_prefix_is_system`, `chat_rocm_command_action` and `rocm_chat_tool_requested_command` become `pub(crate)` because `chat.rs` calls them across the new module boundary. The module calls nothing in `chat.rs` or `freeform_planner.rs`; it uses `rocm_argv`, `providers::ChatToolCall`, `service_launch::is_loopback_host`, `services_text::validate_service_id`, `therock` and `driver_install::host_install_driver_only_reports`, none of which imports `chat`. `freeform_planner.rs` now calls it directly, so the only edge left between the two is `chat` -> `freeform_planner`. `chat.rs` re-exports what other modules reach as `chat::<item>`: `validate_chat_tool_call`, `chat_tool_call_is_read_only` and `chat_tool_approval_request` for `dash_seam.rs`, and the items that only other modules' tests read (`driver_plan.rs`, `system_packages.rs`). `freeform_planner.rs` no longer re-exports `serve_args_request_cpu_device`: its only outside reader was `chat::validate_chat_rocm_command_safety`, which moved. The small `rocm_command_call` test helper is duplicated into the new test module because a staying chat test uses it too. Pure move: no behaviour change. docs/architecture.md, the module docs that named `chat.rs` as the reader of moved code, and the test-name comment in `main.rs` are updated. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
The module doc of `prompt_text.rs` and the `docs/architecture.md` entries for it and for `rocm_argv.rs` said the helpers were shared by the chat tool policy. `tool_policy.rs` does not use `prompt_text`; the readers are called by the prompt-intent heuristics in `chat.rs` and by the freeform planner (and `freeform_answer.rs` reaches `any_substring` through `chat`). `rocm_argv` is called by `chat.rs`, `tool_policy.rs` and the planner. Also add `therock::RuntimeVersionSelector` to the dependencies listed for `tool_policy.rs`, and say that `canonicalize_chat_rocm_command` is called by the argv classifier rather than by `validate_chat_rocm_command_safety`. Docs and comments only. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are identified for the shared-helper extraction.
No open issues 🎉
What changed in this PR
This refactor removes the mutual dependency between chat and freeform planning by extracting shared helpers into three modules, preparing the assistant code for further splits without intended behavior changes.
Changes:
- Move argument handling, prompt-text extraction, and tool-call policy into dedicated modules.
- Update the planner to use those modules directly, preserving existing caller paths through re-exports.
- Relocate existing tests and update architecture documentation and source comments.
| File | Description |
|---|---|
docs/architecture.md |
Documents the extracted modules and revised dependencies. |
crates/rocm-core/src/quoting.rs |
Updates the comment identifying the tool-argument quoter’s location. |
apps/rocm/src/tool_policy.rs |
Houses relocated tool validation, approval policy, and tests. |
apps/rocm/src/services_text.rs |
Updates comments describing validation callers. |
apps/rocm/src/rocm_argv.rs |
Collects shared argument parsing, flag handling, and command formatting. |
apps/rocm/src/prompt_text.rs |
Collects prompt extraction and engine inference helpers with the existing inference test. |
apps/rocm/src/main.rs |
Registers the three modules and updates a test-reference comment. |
apps/rocm/src/freeform_planner.rs |
Replaces chat imports with direct helper-module calls and preserves the formatting re-export. |
apps/rocm/src/driver_install.rs |
Updates the comment naming the policy helper’s caller. |
Review effort: Balanced
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review (Claude Code) · 2daed56
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Review — no blocking findings
Full review of the whole change (fetched the PR head read-only, never checked out; diffed against the merge-base with main). No ticket named for this change — it's framed as one incremental step in an ongoing, undirected split of the assistant code (the larger chat.rs/providers.rs splits "follow separately"); no specific tracking ticket is claimed to be closed by this PR.
Blocking
None
Non-blocking
None
Decisions for the author
None
Positive signals
- Every moved function and test body was verified byte-for-byte identical between its origin in
chat.rs/freeform_planner.rsat the base commit and its new location inrocm_argv.rs,prompt_text.rs, andtool_policy.rs— including the safety-criticalvalidate_chat_rocm_command_safetyandchat_install_prefix_is_system(no-CPU-fallback and system-install-folder refusal logic), confirmed unchanged apart from the documentedpub(crate)visibility widenings. - The stated goal is independently verifiable and true:
freeform_planner.rshas zerochat::/use crate::chatreferences at HEAD, and none of the three new modules' ownuseblocks importchatorfreeform_planner, so the cycle is genuinely broken rather than just hidden behind a re-export. - Every old external call path (
chat::ensure_flag,chat::any_substring,chat::validate_chat_tool_call,chat::chat_tool_call_is_read_only,chat::chat_tool_approval_request,freeform_planner::format_structured_tool_call, plus the#[cfg(test)]-gated pathsdriver_plan.rsandsystem_packages.rsuse) resolves correctly through the new re-export shims; a crate-wide grep found no stale reference to a moved symbol. - Test count is exactly preserved: 137
#[test]occurrences inchat.rs+freeform_planner.rsat base, 137 across the five touched files at head (73+26+0+1+37) — matching the PR's claim that tests were moved, not dropped or duplicated. - Module doc comments and
docs/architecture.mdupdates were checked against the actualusestatements and call sites in the new/changed files and are accurate.
Deployment notes
- Pure internal refactor; no user-visible behavior, no migration, no new dependency.
- The author's own description lists nine open PRs (#563, #243, #299, #678, #723, #724, #725, #817, #818) that touch the same files and will need manual merge-order handling; GitHub currently reports
mergeable_state: blocked(most likely pending required review/status, not a real conflict againstmainas of the reviewed commit). - At review time, four hardware-runner E2E check runs (W7900, MI300X, Strix Halo Ubuntu nightly/release) were still queued rather than completed; every check that had completed was green (build-and-test, clippy, CodeQL, Windows build/test, Commit signatures+sign-off, Coverage ratcheted, E2E consolidated report and the completed E2E hardware lanes).
What this covered
Full review of all 10 changed files between base 3944380 and head 2daed56 (apps/rocm/src/{chat.rs,freeform_planner.rs,freeform_answer.rs,rocm_argv.rs,prompt_text.rs,tool_policy.rs,main.rs,driver_install.rs,services_text.rs}, crates/rocm-core/src/quoting.rs, docs/architecture.md), each read completely alongside the moved symbols' full bodies at both commits, plus the call sites in dash_seam.rs, comfyui.rs, freeform_execution.rs, system_packages.rs, driver_plan.rs, and providers.rs that reach the moved items. Agent-instruction adherence was checked against the repo's AGENTS.md (architecture guardrails, coverage-floor mechanism, doc-link convention); nothing in this diff violates it. History was checked via git log on chat.rs/freeform_planner.rs: recent activity is the same ongoing module-splitting series this PR belongs to, with no sign of an unrelated bugfix being silently reverted by the move.
Did not run: cargo check/cargo test (no Rust toolchain in the review environment) — compile and test-pass correctness was instead verified by manual, exhaustive cross-referencing of every import, visibility, and call site, and by reading the PR's own green CI (clippy, build-and-test on Linux/Windows, the full affected-crate test suite, and the e2e suite all passed on the reviewed commit). The "prior-changes" pass (do past review comments on these files apply here) found nothing to check — no human review threads existed on this PR beyond an automated bot's "no issues" comment. Not reconciled against any human discussion thread, since none existed at review time.
🤖 Generated with Claude Code
juhovainio
left a comment
There was a problem hiding this comment.
I read this as a pure move, and it holds up. Comparing removed and added lines (ignoring whitespace), the code in the three new modules matches what left chat.rs and freeform_planner.rs. The only differences are the nine visibility widenings, the new use blocks, re-wrapped signatures, call-site prefixes, and the duplicated test helper you disclosed. Test counts balance (37 moved out of chat.rs, 1 out of freeform_planner.rs), and I found no leaks of internal names.
Nothing blocking. Three small things, left inline: a wrong caller attribution in docs/architecture.md (serve, not install sdk), a comment in chat.rs that disagrees with the re-export gating, and a ragged comment re-wrap in prompt_text.rs.
Two things for you to weigh: the chat_cli_* names now live in rocm_argv, and nothing enforces the "planner never imports chat" rule at module level (the crate-edges check is crate-level only). Also, merge order against #563 and #243 needs sequencing.
One thing I couldn't check from the diff: that none of tool_policy's dependencies (providers, therock) import chat.
CI: the two Strix Halo Ubuntu E2E lanes (nightly and release) failed in the "Run E2E tests on Strix Halo" step and I couldn't get the logs. The diff changes no behaviour and the other E2E lanes passed, so it looks like a lane flake, but a re-run would confirm. W7900 and MI300X were still pending. This is a diff-level read, not a runtime check.
| - [`freeform_answer.rs`](../apps/rocm/src/freeform_answer.rs) — mechanical relocation, no owned types: the answers a freeform request can get without running a command — `render_freeform_read_only_answer`, which answers a single approval-free `examine`, `comfyui status` or `comfyui logs` action, and `render_freeform_examine_answer`, the plain-language ROCm status it prints for `examine`. `run_freeform` in [`main.rs`](../apps/rocm/src/main.rs) is the only caller of the first, which is `pub(crate)` and re-imported there; the second is private. Besides `chat.rs` (`any_substring`, re-exported there from [`prompt_text.rs`](../apps/rocm/src/prompt_text.rs)), it depends on `comfyui.rs`, `runtimes_cmd.rs` and `therock.rs`, none of which calls back into it. No unit test calls either function directly. | ||
| - [`freeform_execution.rs`](../apps/rocm/src/freeform_execution.rs) — mechanical relocation, dispatch-adjacent, owns a `pub(crate)` type (as of Phase 6e's follow-up, ROCMAI-726; see the Phase 6a variant note above for the owned-types pattern). Holds the freeform natural-language planner's plan-*execution*/consent half that [`freeform_planner.rs`](../apps/rocm/src/freeform_planner.rs) below deliberately left out: `validate_freeform_execution_action()`, `prepare_freeform_execution()`, `apply_freeform_execution_consent()`, `render_freeform_execution_header()`, and the `pub(crate)` `FreeformExecution` struct that `prepare_freeform_execution` returns and `render_freeform_execution_header` consumes. `main.rs`'s `execute_freeform_next_action()` and `run_freeform()` stay in `main.rs` — both are dispatch-level, not part of deciding whether a plan's next action is safe to execute — and `execute_freeform_next_action()` calls in via the unqualified re-import `use crate::freeform_execution::{prepare_freeform_execution, render_freeform_execution_header};` (`run_freeform()` only calls `execute_freeform_next_action()`, never this module directly), matching [`driver_install.rs`](../apps/rocm/src/driver_install.rs)'s dispatch-adjacent form rather than `freeform_planner.rs`'s own qualified-path form. `prepare_freeform_execution` takes the already-built `plan` by value rather than rebuilding it from `request`/`paths`/`config` (ROCMAI-725's single-build guarantee, inherited unchanged from `main.rs`); `prepare_freeform_execution`/`render_freeform_execution_header` are `pub(crate)` for that cross-module call; `FreeformExecution` was already `pub(crate)` (read directly by `main.rs` and this module's own tests); `validate_freeform_execution_action`/`apply_freeform_execution_consent` have no caller outside this module and stay module-private, unchanged from before the move. The cluster's six regression tests moved out of `main.rs`'s shared `mod tests` into this module's own `mod tests`, inheriting the qualified `freeform_planner::` path they already used to reach `freeform_planner.rs`'s items as `main.rs`'s former tests — this module reaches in from outside using the same qualified path `main.rs` and `providers.rs`'s tests use (unlike `chat.rs`/`comfyui.rs`, which import it unqualified). | ||
| - [`freeform_planner.rs`](../apps/rocm/src/freeform_planner.rs) — full domain extraction (Phase 6e, ROCMAI-87). Owns the freeform natural-language planner cluster's types (`StructuredRequestPlan`, `PlannerIntent`, `PlannedToolCall`, `FreeformPlanAction`, `ProviderPlannerResponse`, `ProviderPlannerToolCall`, all module-private except `FreeformPlanAction`, `StructuredRequestPlan` and `PlannedToolCall`, which are `pub(crate)`), the deterministic plan builder (`build_freeform_plan*`), the provider-assisted path (`resolve_freeform_plan_with_provider`, `build_provider_planner_prompt`, `provider_planner_response_to_plan`, `validate_provider_planner_tool_call`), and plan rendering (`render_structured_request_plan`). [`main.rs`](../apps/rocm/src/main.rs) calls `build_freeform_plan_with_context`/`render_structured_request_plan` directly for the dispatch/review flow ([`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs)'s `render_launch_summary` → `host_default_engine` is another call into this module, unrelated to planning: bare `rocm`'s non-interactive launch summary), while [`freeform_execution.rs`](../apps/rocm/src/freeform_execution.rs) above calls `plan_next_action`/`format_structured_tool_call` for the execution flow and reads `FreeformPlanAction`/`StructuredRequestPlan` by type for its execution-validation functions — those live there, not here, because they decide whether a plan's next action is safe to *execute*, a dispatch-adjacent concern distinct from *building* the plan; `main.rs`'s `run_freeform` builds a plan exactly once via `build_freeform_plan_with_context` and threads that same value through `execute_freeform_next_action` to `prepare_freeform_execution` rather than rebuilding it (ROCMAI-725: building a plan can call a configured cloud `planner_provider`, which is not guaranteed deterministic, so a second independent build could silently disagree with the first). [`chat.rs`](../apps/rocm/src/chat.rs)'s `natural_language_plan` MCP tool gets the same single-build guarantee from `plan_freeform_request_with_context`, which builds once and returns both the rendered text and the next action; `chat.rs` also calls `format_structured_tool_call` directly, like [`comfyui.rs`](../apps/rocm/src/comfyui.rs) does, and its `validate_chat_rocm_command_safety` reads `serve_args_request_cpu_device` directly as a no-CPU-fallback safety check on provider-proposed `serve` args. `host_default_engine`, `build_freeform_plan_with_context`, `render_structured_request_plan`, `plan_next_action`, `plan_freeform_request_with_context`, `provider_planner_response_to_plan`, `format_structured_tool_call`, and `serve_args_request_cpu_device` are `pub(crate)` for these reasons, as are `infer_engine_from_request` (read from `chat.rs`) and `plan_driver_request` (read from [`driver_plan.rs`](../apps/rocm/src/driver_plan.rs)'s test module); `freeform_plan_next_action` and `render_freeform_plan` are test-only (`#[cfg(test)]`) and `FreeformPlanAction` was already `pub(crate)` before the split. `parse_freeform_invocation`/`should_treat_as_freeform` (deciding whether an invocation is freeform at all) stay in `main.rs`, a dispatch-level concern outside both this module and `freeform_execution.rs`. The cluster's regression tests live in `freeform_planner.rs`'s own `mod tests`; the handful of `main.rs` tests that exercise the invocation classifier/clap-suggestion-avoidance stay in `main.rs`'s shared `mod tests`, since neither is specific to this module or to `freeform_execution.rs` (and neither touches `freeform_planner::` at all), and the ROCMAI-725 regression tests proving the single-build guarantee live in `providers.rs`'s `mod tests` (reusing its mock local-provider server), reaching this module's items through the qualified `freeform_planner::` path like any other external caller. | ||
| - [`freeform_planner.rs`](../apps/rocm/src/freeform_planner.rs) — full domain extraction (Phase 6e, ROCMAI-87). Owns the freeform natural-language planner cluster's types (`StructuredRequestPlan`, `PlannerIntent`, `PlannedToolCall`, `FreeformPlanAction`, `ProviderPlannerResponse`, `ProviderPlannerToolCall`, all module-private except `FreeformPlanAction`, `StructuredRequestPlan` and `PlannedToolCall`, which are `pub(crate)`), the deterministic plan builder (`build_freeform_plan*`), the provider-assisted path (`resolve_freeform_plan_with_provider`, `build_provider_planner_prompt`, `provider_planner_response_to_plan`, `validate_provider_planner_tool_call`), and plan rendering (`render_structured_request_plan`). [`main.rs`](../apps/rocm/src/main.rs) calls `build_freeform_plan_with_context`/`render_structured_request_plan` directly for the dispatch/review flow ([`examine_report_text.rs`](../apps/rocm/src/examine_report_text.rs)'s `render_launch_summary` → `host_default_engine` is another call into this module, unrelated to planning: bare `rocm`'s non-interactive launch summary), while [`freeform_execution.rs`](../apps/rocm/src/freeform_execution.rs) above calls `plan_next_action`/`format_structured_tool_call` for the execution flow and reads `FreeformPlanAction`/`StructuredRequestPlan` by type for its execution-validation functions — those live there, not here, because they decide whether a plan's next action is safe to *execute*, a dispatch-adjacent concern distinct from *building* the plan; `main.rs`'s `run_freeform` builds a plan exactly once via `build_freeform_plan_with_context` and threads that same value through `execute_freeform_next_action` to `prepare_freeform_execution` rather than rebuilding it (ROCMAI-725: building a plan can call a configured cloud `planner_provider`, which is not guaranteed deterministic, so a second independent build could silently disagree with the first). [`chat.rs`](../apps/rocm/src/chat.rs)'s `natural_language_plan` MCP tool gets the same single-build guarantee from `plan_freeform_request_with_context`, which builds once and returns both the rendered text and the next action; `chat.rs` also calls `format_structured_tool_call` directly, like [`comfyui.rs`](../apps/rocm/src/comfyui.rs) does, while [`tool_policy.rs`](../apps/rocm/src/tool_policy.rs)'s `validate_chat_rocm_command_safety` reads `serve_args_request_cpu_device` from [`rocm_argv.rs`](../apps/rocm/src/rocm_argv.rs) directly as a no-CPU-fallback safety check on provider-proposed `serve` args; `validate_provider_planner_tool_call` calls that same function for an `install sdk` call. `host_default_engine`, `build_freeform_plan_with_context`, `render_structured_request_plan`, `plan_next_action`, `plan_freeform_request_with_context` and `provider_planner_response_to_plan` are `pub(crate)` for these reasons, as is `plan_driver_request` (read from [`driver_plan.rs`](../apps/rocm/src/driver_plan.rs)'s test module); `format_structured_tool_call` lives in [`rocm_argv.rs`](../apps/rocm/src/rocm_argv.rs) and is re-exported here with `pub(crate) use`, so the callers above keep their `freeform_planner::` path, while the planner's own argv lookups call `rocm_argv`, its phrase and engine readers call [`prompt_text.rs`](../apps/rocm/src/prompt_text.rs) and its safety check calls `tool_policy.rs`, directly: the planner no longer imports `chat`, and only `chat` imports the planner (`plan_freeform_request_with_context`); `freeform_plan_next_action` and `render_freeform_plan` are test-only (`#[cfg(test)]`) and `FreeformPlanAction` was already `pub(crate)` before the split. `parse_freeform_invocation`/`should_treat_as_freeform` (deciding whether an invocation is freeform at all) stay in `main.rs`, a dispatch-level concern outside both this module and `freeform_execution.rs`. The cluster's regression tests live in `freeform_planner.rs`'s own `mod tests`; the handful of `main.rs` tests that exercise the invocation classifier/clap-suggestion-avoidance stay in `main.rs`'s shared `mod tests`, since neither is specific to this module or to `freeform_execution.rs` (and neither touches `freeform_planner::` at all), and the ROCMAI-725 regression tests proving the single-build guarantee live in `providers.rs`'s `mod tests` (reusing its mock local-provider server), reaching this module's items through the qualified `freeform_planner::` path like any other external caller. |
There was a problem hiding this comment.
The sentence saying validate_provider_planner_tool_call calls that same function "for an install sdk call" is inaccurate: the install sdk branch calls tool_policy::validate_chat_rocm_command_safety, and serve_args_request_cpu_device is called in the serve branch. Please say serve, or name the function the install-sdk path really calls.
There was a problem hiding this comment.
Correct, that was wrong: validate_provider_planner_tool_call calls serve_args_request_cpu_device in its own serve branch and hands an install sdk call to tool_policy::validate_chat_rocm_command_safety. The sentence now says exactly that (5339c70).
| //! | ||
| //! A leaf module: it depends only on `therock::RuntimeVersionSelector`, and | ||
| //! neither `chat.rs` nor `freeform_planner.rs` has to import the other for | ||
| //! these helpers. `tool_policy.rs` does not use it. They were relocated verbatim from `chat.rs` and |
There was a problem hiding this comment.
Nit: the header comment re-wrap left a ragged line ("...tool_policy.rs does not use it. They were relocated verbatim from chat.rs and"); please reflow.
There was a problem hiding this comment.
Reflowed the header comment to normal wrapping, no wording change (5339c70).
| render_services_text, validate_service_id, | ||
| render_services_text, | ||
| }; | ||
| // The tool-call policy lives in `tool_policy.rs`. `chat_tool_approval_request`, |
There was a problem hiding this comment.
This comment says ChatRocmCommandAction and rocm_chat_tool_requested_args are read through chat:: only by other modules' tests, but they are re-exported ungated while the two truly test-only items are #[cfg(test)]. Please make the comment and the gating agree.
There was a problem hiding this comment.
The comment was wrong and the gating was right: chat.rs calls ChatRocmCommandAction and rocm_chat_tool_requested_args in its own non-test code, so that import cannot be #[cfg(test)]. It is pub(crate) only because the tests in driver_plan.rs and system_packages.rs read both through chat::, while the two truly test-only items stay #[cfg(test)]. I corrected the comment and left the code alone (5339c70).
architecture.md: validate_provider_planner_tool_call reads serve_args_request_cpu_device in its own serve branch; an install sdk call goes to tool_policy::validate_chat_rocm_command_safety. The sentence had the two swapped. chat.rs: ChatRocmCommandAction and rocm_chat_tool_requested_args are used by chat.rs itself, so their import is ungated by necessity; only chat_install_driver_action and chat_rocm_command_action_from_args are test-only and cfg(test). Make the comment say so. prompt_text.rs: reflow the module header. Comments and docs only. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
main changed code this branch moved out of chat.rs and freeform_planner.rs, so the conflicts are resolved by applying main's change at the new home: - tool_policy.rs takes the control-character screening of tool names and arguments (reject_control_characters_in_tool_arguments and its helpers), the wider check in normalized_chat_rocm_command_args, and the quoted --prefix paths in the three command-preview tests that moved with it. - rocm_argv.rs takes the shell_quote based format_structured_tool_call and drops quote_tool_arg, which main removed. It now depends on the leaf shell_quote.rs, and its header and architecture.md entry say so. - freeform_planner.rs keeps its imports of prompt_text, rocm_argv and tool_policy and adds main's has_terminal_control_character. - chat.rs keeps its slimmer form plus main's command-preview property tests, which import has_terminal_control_character themselves. - quoting.rs and the architecture.md providers entries take main's text. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
|
Pushed b070434: one commit for the three inline findings (5339c70) and two merges of On your other notes. Nothing in Merge resolution worth a look: main's control-character screening of tool arguments now lives in |
juhovainio
left a comment
There was a problem hiding this comment.
Re-reviewed at b070434 after the merges from main. I compared the removed and added lines across the .rs files: the moves are verbatim apart from imports, call-site path rewrites, rustfmt of the widened signatures and docs. No logic line differs.
- Test count is conserved (37 moved to
tool_policy.rs, 1 toprompt_text.rs). - The new leaf modules import nothing from
chat, so the cycle is broken as described. - Visibility widening is limited to items with a cross-module caller, and the test-only re-exports are gated on
cfg(test). architecture.md, the module docs and themain.rscomment match the code.
Non-blocking:
- The history has two "Merge origin/main" commits; a rebase before merge would match the repo's preference.
chat.rsremains a re-export hub. That is documented and fine here, but follow-up splits could repoint callers at the leaf modules and drop the re-exports.- The
rocm_command_calltest helper is duplicated, as the PR body notes.
This is based on a read of the diff, not a local build. clippy and the build/test jobs were still pending when I looked, so please confirm they come back green before merging.
Summary
chat.rsandfreeform_planner.rsinapps/rocmimported each other: the planner called back intochat::for a handful of small helpers whilechat.rscalls the planner to build its plans. Mutual imports like that make it hard to split either file, because every helper either side shares has to stay reachable from both. This PR removes the cycle without changing behaviour, by moving the three groups of leaf helpers the planner reached intochatinto modules of their own. It is part of the ongoing split of the assistant code; the larger splits ofchat.rsandproviders.rsfollow separately.After the last commit
freeform_planner.rshas nochatimport in production code and the only edge left between the two modules ischat->freeform_planner(plan_freeform_request_with_context).What moved
Every item moved verbatim. Visibility widened only where the item is now called across a module boundary.
chat_cli_arg_value,chat_cli_arg_value_checked,chat_cli_has_flag,ensure_flagchat.rsrocm_argv.rs(new, nocrate::dependencies)format_structured_tool_call,quote_tool_arg,serve_args_request_cpu_device,device_policy_is_cpu_modefreeform_planner.rsrocm_argv.rsany_substring,requested_install_prefix_from_prompt,requested_bare_install_prefix_from_prompt,clean_requested_install_prefix,looks_like_user_path,requested_therock_build_date_from_prompt,requested_therock_version_from_prompt,looks_like_therock_runtime_versionchat.rsprompt_text.rs(new, depends only ontherock::RuntimeVersionSelector)infer_engine_from_requestand its testinfer_engine_from_request_does_not_match_aim_as_a_substringfreeform_planner.rsprompt_text.rsChatRocmCommandAction,ChatToolApprovalRequest,chat_tool_approval_request,validate_chat_tool_call, thevalidate_chat_*_tool_callfamily,validate_required_chat_string,validate_optional_chat_integer,chat_rocm_command_action,normalized_chat_rocm_command_args,chat_install_driver_action,chat_rocm_command_action_from_args,canonicalize_chat_rocm_command,validate_chat_rocm_command_safety,chat_install_prefix_is_system,chat_tool_call_is_read_only,rocm_chat_tool_requested_args,rocm_chat_tool_requested_command,push_optional_json_cli_arg,json_string, plus the 37 chat tests that exercise only these itemschat.rstool_policy.rs(new)Widened to
pub(crate):chat_cli_arg_value_checkedanddevice_policy_is_cpu_mode(rocm_argv),requested_therock_version_from_prompt(prompt_text), andnormalized_chat_rocm_command_args,json_string,chat_install_prefix_is_system,chat_rocm_command_action,rocm_chat_tool_requested_command(tool_policy).Every other module keeps its path through re-exports:
chat::ensure_flag(freeform_execution.rs),chat::any_substring(freeform_answer.rs),chat::validate_chat_tool_call,chat::chat_tool_call_is_read_onlyandchat::chat_tool_approval_request(dash_seam.rs),freeform_planner::format_structured_tool_call(comfyui.rs,freeform_execution.rs,providers.rstests), and thechat::paths thatdriver_plan.rsandsystem_packages.rsread from their test modules (a re-export gated oncfg(test)wherechat.rsitself does not call the item). The one deliberate exception is thatfreeform_planner.rsnow calls the new modules directly, because importing throughchatwould keep the cycle.freeform_planner.rsno longer re-exportsserve_args_request_cpu_device: its only outside reader wasvalidate_chat_rocm_command_safety, which moved.Production line counts:
chat.rs4111 -> 3009,freeform_planner.rs1293 -> 1242, newrocm_argv.rs123,prompt_text.rs182,tool_policy.rs963.Import edges between the modules involved (production code only,
use crate::andcrate::<module>paths, before this PR fromorigin/mainand after it):chatfreeform_plannerrocm_argvprompt_texttool_policyBefore:
chat<->freeform_planner, the only two-way edge touching any of these modules. After: no two-way edge between any of them and any other module (chat->freeform_planneris one-way). A grep forchat::oruse crate::chatinfreeform_planner.rsfinds nothing.Verification
Ran:
cargo fmt --all --checkandcargo clippy -p rocm -p xtask --all-targets -- -D warnings: clean (clippy onrocmre-run after the final rebase)cargo test -p rocm --no-fail-fastbefore the final rebase: all pass (therocmbinary: 1458 passed, 7 ignored; the otherrocmtargets pass too)cargo test -p rocm --bin rocm -- --listand-- --list --ignored, againstorigin/main, after each commit: empty (1465 tests, 7 ignored, none lost or added; module paths change, leaf names do not). After the final rebase onto a newerorigin/mainthe diff shows only additions, the 10 property tests thatmaingained in the meantime (gpu_selection_proptests.rs,providers/sse_emitter_proptests.rs,test_support.rs); none is missingcargo test -p rocm advised_commands: pass (also after the final rebase) (the pins onapps/rocm/src/chat.rsstill match their strings, none of which moved)cargo xtask manifest --check,cargo xtask check-crate-edges,cargo test -p xtask ascii: passcargo test -p e2e-cucumber --lib: 196 passedorigin/main: only identifiers such asrun_internal_mcp_calland the existing words "private" in docs matched; nothing internalNot run:
cargo xtask e2eandscripts/smoke_local.py(left to CI); the Windows cross-check (cargo clippy --target x86_64-pc-windows-msvc) cannot run on the Linux build host becauseaws-lc-sysneeds a Windows C toolchain, so the Windows lane is the check. No item gated oncfg(windows)moved; the onecfg(unix)test that moved,the_assistant_cannot_name_a_system_install_folder_by_respelling_it, keeps its gate. No new tests were added and no test body changed, so there was nothing new to break-test.User-visible behaviour
None: a pure move of code and its tests, with no change to any command, output, exit code, file or engine selection. No Gherkin scenario applies. The existing chat and freeform scenarios in
tests/e2e-cucumbercover these paths and are unmodified; CI runs them.Notes for reviewers
Read the commits in order; each of the first three compiles and passes clippy and the tests on its own, and the fourth changes only comments and docs.
rocm_argv.rs: the argv helpers (leaf, nocrate::dependencies). The planner'schat::chat_cli_*calls switch to it.prompt_text.rs: the phrase, install-folder, TheRock date/version and engine readers. The planner'schat::any_substringandchat::requested_*calls switch to it.tool_policy.rs: the tool-call validation and approval policy and its tests. The planner's one remainingchat::call switches to it. This is the large diff, but nearly all of it is lines deleted fromchat.rsand added verbatim to the new file.docs/architecture.md(prompt_textis used bychat.rs's prompt-intent code and the planner, not bytool_policy) and completes the dependency list oftool_policy.git diff --color-moved=dimmed-zebramakes the moves easy to confirm.Deviations worth a look:
freeform_planner.rsstops re-exportingserve_args_request_cpu_device(no reader left); the smallrocm_command_calltest helper is duplicated into the new test module because a chat test that stays also uses it;chat_tool_call_requests_therock_install_without_prefixandrocm_command_args_install_sdk_without_prefixstay inchat.rs(they are intent heuristics, not policy). Module docs that namedchat.rsas the reader of moved code (services_text.rs,driver_install.rs,quoting.rs) and the test-name comment inmain.rsare updated;docs/architecture.mddescribes the three new modules and thechat.rsandfreeform_planner.rsentries.Open PRs that touch the same code, for merge order: #563 rewrites
chat_tool_approval_request,validate_chat_tool_callandnormalized_chat_rocm_command_argsand editsquote_tool_arg(and its mention inquoting.rs), so it conflicts with commits 1 and 3. #243 editsvalidate_chat_launch_server_tool_callandvalidate_chat_rocm_command_safetyand adds tests that callvalidate_chat_tool_call, so it conflicts with commit 3. #299, #678 and #724 touchchat.rsonly outside the moved code (chat_tool_call_display_label,run_internal_mcp_call,render_services_tool_result_text, the imports) and #818 only a test, so at most theuseblock ofchat.rsneeds a manual merge. #243, #299, #563, #678, #723, #724, #725, #817 and others also touchdocs/architecture.md.