# 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.
