0.167.10

Fixed

  • Сохранность полей сообщений на round-trip + фиксы Anthropic-провайдера (#278 — оба P0; #281 — P1 round-trip часть; #290 — P1). Батч из аудита контекстного слоя 2026-08 (audit:2026-08-context). (1) #277 — thinking-блоки не возвращались в истории → HTTP 400 при thinking_level=deep с инструментами. Anthropic требует эхо-возврата thinking/redacted_thinking-блоков с криптографической подписью вместе с tool_use того же assistant-хода. Раньше: стрим вычитывал только текст thinking (signature игнорировался), non-streaming путь вообще не обрабатывал эти типы блоков, а providers.Message не имел полей для их переноса. Фикс: новый тип providers.ReasoningBlock{Type, Thinking, Signature, Data}; поля ReasoningBlocks в Message и LLMResponse; в стриме блоки собираются по индексу content-блока из content_block_start + thinking_delta + signature_delta (накопление через strings.Builder, O(n); strict-by-construction: блок эмитится только если наблюдался его content_block_start, у thinking непусты и текст и подпись, у redacted — data; signature_delta прикрепляется только к существующему thinking-блоку); non-streaming парсер обрабатывает thinking/redacted_thinking; convertRequest эмитит блоки перед text/tool_use, гейтится на включённость thinking в текущем запросе (эхо при выключенном thinking — тоже 400); runner.go переносит resp.ReasoningBlocks в assistant-сообщение хода. (2) #278 — cache_control: до 5 breakpoint’ов при лимите Anthropic 4; CacheLevel терялся в конвертерах. ContextBuilder размечал 3 system-секции, но toProviderMessages/fromProviderMessages не переносили CacheLevel — все приходили как zero value CacheStable и каждая получала breakpoint (3 system + tools + last message = 5 > 4 → 400). Фикс: конвертеры (и сборка истории в context.go) переносят CacheLevel (а также IsError, ReasoningBlocks); breakpoint-бюджет централизован в convertRequest (≤ 4, с приоритетом system → tools → last message): system — ровно один на последнем кэшируемом блоке (CacheStable всегда; для коротких CacheSemiStable-секций — порог минимальной кэшируемой длины, fallback на более ранний блок), tools — на последнем определении, последнее сообщение — на последнем кэшируемом блоке (раньше — на первом: при N параллельных tool_result хвост выпадал из кэшируемого префикса). (3) #281 (round-trip) — IsError терялся в конвертерах: toProviderMessages/fromProviderMessages переносят флаг → NewToolResultBlock(..., is_error) и extra-поле is_error для OpenAI-пути теперь реальные; <tool_error> для провайдеров без нативного tool-calling заработал. БД-колонка is_error — вне этого батча (миграция 106, трекается в #280). (4) #290 — описания инструментов не отправлялись на Anthropic: ToolParam.Description заполняется из ToolDefinition.Description; JSON-схема инструментов передаётся целиком через ToolInputSchemaParam.ExtraFields ($defs, enum, oneOf/anyOf, additionalProperties), а не только properties/required; required — строго строки; ошибки marshal схемы логируются. Тесты: anthropic_test.go — сборка thinking+подписи в стриме, redacted, drop неподписанных/пустых/без-start блоков, non-streaming parse, эхо перед tool_use, гейт по thinking-флагу запроса, кэш-лимиты (1 system breakpoint, breakpoint на последнем tool_result, ≤ 4 суммарно, порог SemiStable, fallback), description/$defs-passthrough, nil-properties; p3_infra_test.go — тавтологические тесты переписаны на реальный round-trip через конвертеры (IsError/CacheLevel/ReasoningBlocks); runner_test.go — блоки доезжают до spec.Messages следующей итерации. Проверки: go test ./... — все зелёные; go vet — 0; golangci-lint — 0; make build-cross — 6/6 бинарников; ocr review — 0 замечаний после 7 итераций (устранены: эхо при выключенном thinking, асимметрия фильтров, O(n²)-накопление, orphan signature_delta, дубли breakpoints и др.).