# Phase 14 — Retarget response-builder and streaming-internals specs **Estimated time:** ~25 minutes **Touches:** `spec/dispatch/adapter/minimax/response_builder_spec.rb`, `spec/dispatch/adapter/minimax/sse_parser_spec.rb`, `spec/dispatch/adapter/minimax/stream_collector_spec.rb`. ## Goal Update the three specs that consume the new fixtures created in phase 12. The structural change is small: with `is_oauth` and cloaking gone (phase 02), there is no `proxy_` prefix to strip, and the response / stream pipelines are pure Anthropic-compatible parsers fed by MiniMax- shaped data. ## Pre-reading 1. `spec/fixtures/sse/*.sse` (already updated in phase 12) 2. `spec/fixtures/responses/*.json` (already updated in phase 12) 3. `lib/dispatch/adapter/minimax/response_builder.rb` 4. `lib/dispatch/adapter/minimax/sse_parser.rb` 5. `lib/dispatch/adapter/minimax/stream_collector.rb` ## Steps for response_builder_spec.rb ### 1. Drop the `is_oauth` parameter from every call `ResponseBuilder.build(json, model_info: ..., is_oauth: ...)` becomes `ResponseBuilder.build(json, model_info: ...)`. Phase 02 removed the keyword from the lib code; remove it from all spec calls here. ### 2. Update fixture references and assertions The fixtures are now MiniMax-flavoured: - `model: "MiniMax-M2.7"` instead of a Claude id. - Token counts changed (input 15/50/42, output 8/18/24). - `id` strings begin with `msg_minimax_*` and `toolu_minimax_*`. - Tool name in tool-use fixtures is `"get_weather"` with input `{"city":"Boston"}`. Update every assertion that compared against old values. ### 3. Drop the cloaking strip-prefix examples Any example asserting that `proxy_get_weather` arrives in the response and is stripped to `get_weather` before being returned should be DELETED. There is no proxy prefix to strip anymore — MiniMax sends plain tool names. ### 4. Cost assertions Examples that asserted `usage.cost.total_cost > 0.0` (or any non-zero sub-cost) must be updated to expect `0.0` because phase 06 zeroed the pricing table. Token counts are still meaningful — only the *per-token rate* is zero. ### 5. Remove cache_creation/cache_read examples that depended on non-zero pricing if any. If the old `messages-text.json` exposed cache token counts, the fixture in phase 12 may not include them. Add them back ONLY IF the spec needs to exercise that code path (i.e. a `usage_hash` with `cache_creation_input_tokens` / `cache_read_input_tokens`). If you do, edit `spec/fixtures/responses/messages-text.json` to add those keys to `usage` — phase 12's fixture omits them but adding them is fine. ## Steps for sse_parser_spec.rb ### 1. Update model id references Replace `"claude-sonnet-4-5-20250929"` (or whatever Claude model id was used) with `"MiniMax-M2.7"` in any in-spec hand-built event payloads. ### 2. Re-load fixtures Specs that read SSE fixtures by path (`File.read("spec/fixtures/sse/text-only.sse")`) will pick up the new content automatically. Update the assertions to match the new event count and content: - `text-only.sse` → 8 frames (text-only fixture: message_start, content_block_start, ping, content_block_delta×2, content_block_stop, message_delta, message_stop). Verify by reading the fixture. - `tool-use.sse` → 7 frames. - `thinking-then-text.sse` → 11 frames. Update text/tool/thinking string assertions to match the new fixture content (`"Hello, world!"` → `"Hello, "` + `"world!"`, `"get_weather"` with city=Boston, etc.). ### 3. Truncated fixtures `truncated-mid-text.sse` ends mid-frame after emitting some text deltas. The parser is content-agnostic at chunk boundary; the higher- level `StreamCollector` decides whether to retry. The parser-level spec should assert that `flush` raises `RequestError` (because there is dangling non-empty data when the stream ends). `truncated-before-message-start.sse` is just a `ping` event followed by EOF — the parser yields the ping (or silently drops it depending on implementation) and `flush` is a no-op since there is no dangling data. ## Steps for stream_collector_spec.rb ### 1. Drop `is_oauth:` from `StreamCollector.new` calls Phase 02 removed the keyword. Remove from spec setup. ### 2. Update fixture-driven assertions For the `text-only.sse` fixture, the collector should now assemble: - One `TextBlock` with text `"Hello, world!"` - `stop_reason: :end_turn` - `usage.input_tokens: 15` - `usage.output_tokens: 8` - `model: "MiniMax-M2.7"` For `tool-use.sse`: - Zero text blocks. - One `ToolUseBlock` with id `toolu_minimax01`, name `get_weather`, arguments `{"city" => "Boston"}`. - `stop_reason: :tool_use`. - `usage.input_tokens: 50`, `usage.output_tokens: 18`. For `thinking-then-text.sse`: - One `ThinkingBlock` with thinking `"Considering the question... the answer is two."` and signature `""` (or `nil`, depending on how the existing builder handles empty signatures — match the actual lib behavior). - One `TextBlock` with text `"The answer is 2."` - `stop_reason: :end_turn`. ### 3. Drop cloaking assertions Any assertion that the collector strips a `proxy_` prefix from tool names should be DELETED — there is no prefix to strip. ### 4. Cost assertions Same as response builder spec: update any cost assertions to expect `0.0` since pricing is zeroed. ## Acceptance criteria - `response_builder_spec.rb`, `sse_parser_spec.rb`, and `stream_collector_spec.rb` all pass cleanly. - No `.skip` or `pending` examples remain in any of the three. - `grep -n 'is_oauth\|proxy_\|claude-sonnet\|claude-3' spec/dispatch/adapter/minimax/{response_builder_spec,sse_parser_spec,stream_collector_spec}.rb` returns ZERO matches. - `bundle exec rubocop --autocorrect-all` exits 0. ## Verification Run `run_tests` with `project_path=reference/dispatch-adapter-minimax`. Rubocop and these three retargeted specs must pass. Other spec failures (chat_*, list_models, etc.) are acceptable and fixed in phases 15–16.