Skip to content

ai-providers: one shared formatter for command results and rejected actions (single fencing call site) #845

Open

Description

@smart-ssh-planner

Goal

The text that ai-providers builds from a command result, a rejected action and a history entry exists exactly once. Remote output is wrapped by fence_untrusted at a single place, so a future hardening of that wrapping cannot be applied to one provider and forgotten in another.

Context

crates/ai-providers/src/anthropic.rs and crates/ai-providers/src/openai_compatible.rs each carry their own copy of the history formatting:

Function anthropic.rs openai_compatible.rs
role_str :342 :387 (pub(crate))
message_content_text :354 :400 (pub(crate))
format_action_rejected :384 :432
format_command_result (calls fence_untrusted for stdout and stderr) :408–468 :455–518

A token-level comparison shows format_command_result is 98 % identical (42 of 44 code lines); the other three are identical apart from comments, which already say "see identical comment in …". openai_responses.rs:36–39 imports role_str and message_content_text from openai_compatible. fence_untrusted is called at anthropic.rs:453,458 and openai_compatible.rs:506,511.

The fenced format of remote output (<stdout>, <stderr>, <security_notice>, <cancelled_by_user>, <waiting_for_input>, <output_truncated>) is specified in Specs 0013, 0027, 0039 and 0043, and is security invariant 8 in .claude/skills/smart-ssh-coder/references/security-invariants.md.

Requirements

  • New module crates/ai-providers/src/common/prompt_format.rs (with common/mod.rs) that holds role_str, message_content_text, format_action_rejected and format_command_result once, pub(crate).
  • anthropic.rs, openai_compatible.rs and openai_responses.rs call the shared functions; their local copies are deleted.
  • The produced strings stay byte-identical for every MessageContent variant and every combination of cancelled, waiting_for_input and output.truncated. If the two copies differ in any produced byte today, stop and describe the difference in the PR instead of choosing one silently.
  • The explanatory comments of both copies are merged into one doc comment on the shared function (no "see identical comment in …" left).
  • openai_responses.rs no longer imports role_str or message_content_text from openai_compatible.

Acceptance Criteria

  • grep -rn "fence_untrusted(" crates/ai-providers/src (production code, tests excluded) finds call sites only in common/prompt_format.rs; no provider file calls fence_untrusted itself.
  • grep -rn "fn format_command_result\|fn format_action_rejected\|fn message_content_text\|fn role_str" crates/ai-providers/src finds each function once.
  • New unit tests in common/prompt_format.rs pin the full output of format_command_result (normal, cancelled, cancelled + waiting for input, truncated, non-UTF-8 output, output that contains a closing </stdout> tag) and of format_action_rejected (all three RejectionReason variants).
  • A test asserts that the Anthropic and the OpenAI-compatible request bodies render the same command-result text for the same history entry.
  • The existing tests that call format_command_result (anthropic.rs:~3403, openai_compatible.rs:~2751) still pass, moved or unchanged.
  • The number of tests in cargo test -p ai-providers -- --list does not decrease.
  • Gate green: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace, cargo build --workspace.

Technical Notes

  • Security-relevant (fencing of untrusted remote output, review priority elevated): read security-invariants.md (section "Nicht vertrauenswürdiger Inhalt") and Spec 0039 before changing anything. The wrapping must never get weaker: no tag, notice or escape may be dropped or reordered.
  • This is the first, low-risk step of consolidating the provider crate. The HTTP retry loop, the frame loop and the max-tokens retry follow in separate issues; do not touch them here.
  • Pure move plus deduplication; no change to request bodies, prompts or logging.
  • New code comments in English.

Spec impact: none

Open questions

  • Module name: common/prompt_format.rs (recommended, matches the follow-up modules common/http_retry.rs, common/frame_loop.rs) or history.rs at crate root. Recommendation: common/.

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. 21 remaining items

  3. marked this as blocking 15 issues on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    Priority

    P1 - High

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions