0.167.9

Fixed

  • Anthropic streaming: имя инструмента и аргументы tool-вызовов терялись (#276 — оба P0). В streaming-режиме (весь интерактивный чат) инструментальный цикл на Anthropic-провайдере был сломан двумя независимыми дефектами в парсере SSE-событий internal/providers/anthropic.go. (1) Имя всегда пустое (#275): при content_block_start типа tool_use имя читалось из event.ContentBlock.Caller.ToolID — но поле Caller в SDK (ToolUseBlockCallerUnion) это subunion для programmatic tool calling, содержащий только Type/ToolID; для обычных tool_use-блоков оно не заполняется, и агент получал ToolCallRequest{Name: ""}registry.Get("") → nil → «tool not found». Правильное поле — event.ContentBlock.Name (оно и используется в non-streaming пути). (2) Аргументы почти всегда nil (#276): буфер partial-JSON (toolArgsBuf) наполняется по индексу content-блока (считает ВСЕ блоки: text, thinking, tool_use), а читался по порядковому номеру в срезе toolCalls. Индексы совпадали только если ответ состоит исключительно из tool_use-блоков с индекса 0; Claude почти всегда шлёт текстовую преамбулу (tool_use на индексе 1 → сдвиг на 1), при extended thinking — thinking=0, text=1, tool_use=2 (сдвиг на 2). В результат аргументы терялись или перепутывались между параллельными вызовами. Фикс: введена карта blockIdxToToolIdx, заполняемая при content_block_start/tool_use (индекс блока → позиция в toolCalls); финальный разбор обходит toolArgsBuf по индексу блока и резолвит позицию через карту; orphan-дельты (индекс без парного tool_use) игнорируются; fallback {"_raw": buf} при битом JSON сохранён. Тесты: новый internal/providers/anthropic_test.go — mock SSE-сервер (httest) с реалистичным протоколом (event: + data:, SDK диспатчит по SSE event name): имя из content_block.name; layout [text@0, tool_use@1]; [thinking@0, text@1, tool_use@2]; два параллельных tool_use с раздельными аргументами (в т.ч. split partial_json); text-блок без tool-вызовов; tool_use без name (без паники); orphan input_json_delta; оборванный JSON → _raw. 5 из 7 тестов падают на старом коде (проверено stash-прогоном). Проверки: go test ./... — все зелёные; go vet — 0; golangci-lint — 0; ocr review — 0 замечаний; make build — ок.