Files
deepagents/libs/code/deepagents_code/_markdown.py
Mason Daugherty 1d3feb1275 feat(code): selectable model for the Auto approval classifier (#5205)
Auto approval mode can now use a separate, cheaper model to review
actions instead of always reusing the main agent model. Set it with
`--auto-classifier-model`, `DEEPAGENTS_CODE_AUTO_CLASSIFIER_MODEL`,
`[models].auto_classifier`, or `/auto model` in the TUI.

---

In Auto mode, any gated tool call that deterministic policy can't clear
is reviewed by an LLM classifier. That review used the main agent model,
so every batch paid frontier-model price and latency for a short
verdict, in the middle of the turn.

The classifier decides what runs without asking you, so the behavior
around it is conservative:

- **The default is unchanged.** With nothing configured, the classifier
still uses the main agent model.
- **Precedence:** `/auto model` → launch flag → env var → `config.toml`
→ inherit. A blank value anywhere means "inherit".
- **Trusted sources only.** A project `.env` can't set the env var, so a
cloned repo can't quietly point your review at a weaker model. Shell
exports, the global `~/.deepagents/.env`, `config.toml`, the flag, and
`/auto model` all work as usual.
- **A broken classifier never falls back to the main model.** If the
configured model can't be built (bad spec, missing credentials, missing
provider package), the calls it would have reviewed are denied and don't
run, and Auto starts asking you after repeated failures. The error names
the model so you know what to fix. `/auto model` validates a spec before
accepting it.
- **You can always tell which model is reviewing.** The TUI names it
when Auto turns on and in `/auto model`, including one set by env var or
`config.toml`.
- **Main-model settings don't leak.** Cache control, prompt cache keys,
reasoning budgets and `--model-params` are provider-specific, so they
only travel when the classifier is the main model.
- **Nothing else about Auto changed.** Deterministic allow/deny, the
denial counters, replay detection, control-state checks, and the
fallback thresholds are all untouched.

`--auto-classifier-model` only works where Auto runs — interactive TUI,
no sandbox — and errors out otherwise instead of being quietly ignored.
`THREAT_MODEL.md` gains T14 for the tradeoff: a weaker classifier means
a weaker review, including against prompt injection in the content it
reads.

Not included: a recommended cheap model (there's no accuracy eval suite
yet), classifier-specific invocation params, a configurable timeout, and
cache invalidation when credentials rotate (a rotated key shows up as a
named classifier failure).

<details>
<summary>Test plan</summary>

Unit tests cover precedence, blank specs, the project/global `.env`
split, model caching, settings hygiene, fail-closed resolution and its
deadline, logging and trace fields, the per-run context for both setting
and clearing, the sandbox and headless guards, and the `/auto model`
paths.

Manual: launch with `--auto-classifier-model <spec>`, confirm reviews
use it, then `/auto model clear` to go back.

</details>

Made by [Open
SWE](https://openswe.vercel.app/agents/a1237a94-e79b-6c84-c74f-b69b5acfc7c4)

## References
- Plan:
https://openswe.vercel.app/agents/a1237a94-e79b-6c84-c74f-b69b5acfc7c4/plan

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-08-03 17:10:22 -04:00

27 lines
1013 B
Python

"""Escaping for external text that reaches Rich/Textual markdown source."""
from __future__ import annotations
MARKDOWN_ESCAPES = str.maketrans({char: f"\\{char}" for char in "\\&`*_[]<>|~"})
"""Translation table backing `escape_markdown`.
Covers the inline constructs Rich's markdown parser acts on (emphasis, code
spans, links, autolinks/HTML, HTML entities, strikethrough) plus the `|`
table-cell separator. Block-level punctuation (`#`, `-`, `>`) only has meaning
at the start of a line; line breaks are normalized before translation, and `>`
is escaped anyway because it is cheap.
"""
def escape_markdown(text: str) -> str:
"""Normalize line breaks and escape markdown syntax in external text.
Args:
text: Display string that may contain markdown punctuation.
Returns:
`text` on one line with markdown-significant characters escaped.
"""
normalized = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
return normalized.translate(MARKDOWN_ESCAPES)