aiohttp's Application._middlewares is a FrozenList, not a plain tuple.
Replacing it with a tuple worked at install time but broke later when
AppRunner.setup() called _middlewares.freeze() -- tuples don't have
.freeze(), causing startup crash:
'tuple' object has no attribute 'freeze'
Switched to app._middlewares.append(middleware), which is safe because
the FrozenList is still mutable at middleware-install time (the freeze
happens later in AppRunner.setup()). Updated the test mock to use a
list instead of a tuple so it matches real aiohttp behavior.
31/31 tests in test_command_middleware.py pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the upstream Stage 1 slash-command preprocessor
(api_server_slash.py) as an aiohttp middleware installed by the
bootstrap patch. Intercepts gateway commands (/help, /commands,
/profile, /provider) and returns synthetic responses instead of
forwarding them to the LLM, which would hallucinate wrong replies.
Stateful commands (/model, /new, /retry, etc.) get a deterministic
decline notice. Feature-detects and skips when the upstream module
exists.
- /v1/chat/completions: synthetic SSE stream or JSON chat.completion
- /v1/runs: injects message.delta + run.completed into adapter queue
- Auth check runs before command logic (matches upstream order)
- Fail-open: any middleware error falls through to the original handler
- 31 new tests (unit + aiohttp integration)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>