# 2.15 — Anthropic dialect scope boundary (GREEN) Phase: 2 — LLM surfaces Stage: GREEN Depends on: [2.10](2.10-anthropic-request-translation.md), [2.13](2.13-anthropic-error-shape.md) The only client of `/llm/*` is the first-party riotpiao frontend. It is not Claude Code and not the Anthropic SDK, so the surface implements a deliberately narrow slice of the Messages API. The narrowness is the design; the risk is a half-built feature that appears to work. - [ ] The unsupported set is enumerated in one place and covers at least: `tools` and `tool_choice`, `tool_use` blocks, `tool_result` turns, image content blocks, prompt-caching controls and cache headers, the batch API, and user messages carrying more than one content block - [ ] Each unsupported feature is detected during request translation, before any upstream is contacted - [ ] Rejection uses the Anthropic error shape `{"type":"error","error":{"type":"invalid_request_error","message":"..."}}` and the message names the specific unsupported feature, not just "unsupported" - [ ] No unsupported feature is silently ignored, stripped, or partially honoured; a request containing one never produces a 200 - [ ] A request combining a supported and an unsupported field is rejected, not serviced with the unsupported part dropped - [ ] The `/v1/*` OpenAI surface is unaffected: tool calling continues to work there exactly as it does today - [ ] Every entry in the unsupported set has a test asserting the rejection, so widening scope forces a deliberate test change rather than a quiet code change - [ ] The list is documented on the surface, so the frontend can see the boundary without reading gateway code Enabling any of these later must be an explicit config or code change accompanied by its own translation work and tests. A silent partial implementation is the specific failure this task exists to prevent. ## Verify ```bash curl -s localhost:8080/llm/v1/messages -H 'content-type: application/json' \ -d '{"model":"reasoning","max_tokens":8,"tools":[{"name":"x"}],"messages":[{"role":"user","content":"hi"}]}' # expected: 4xx, error.message names tools, no upstream stub hit curl -s localhost:8080/llm/v1/messages -H 'content-type: application/json' \ -d '{"model":"reasoning","max_tokens":8,"messages":[{"role":"user","content":[{"type":"image","source":{}}]}]}' # expected: 4xx, error.message names image content blocks, no upstream stub hit curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/v1/chat/completions \ -H 'content-type: application/json' \ -d '{"model":"reasoning","messages":[{"role":"user","content":"hi"}],"tools":[{"type":"function","function":{"name":"x"}}]}' # expected: 200, tool calling still works on the OpenAI surface ```