summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/13-25-retarget-headers-and-request-builder-specs.md
blob: e192f018ba656b7eac1348fff1f01f39762c8fb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# Phase 13 — Retarget headers and request-builder specs

**Estimated time:** ~25 minutes
**Touches:**
`spec/dispatch/adapter/minimax/headers_spec.rb`,
`spec/dispatch/adapter/minimax/request_builder_spec.rb`.

## Goal

Update the two specs that exercise the most heavily-modified code:
the simplified `Headers.build` (phase 04) and the MiniMax-specific
constraints in `RequestBuilder.build` (phases 08, 09, 10).

## Pre-reading

Before editing, read these files in full so you know the current
shapes:

1. `lib/dispatch/adapter/minimax/headers.rb`
2. `lib/dispatch/adapter/minimax/request_builder.rb`
3. `spec/dispatch/adapter/minimax/headers_spec.rb`
4. `spec/dispatch/adapter/minimax/request_builder_spec.rb`

## Steps for headers_spec.rb

### 1. Delete every example that asserted on removed behavior

DELETE examples covering:

- `claude_code_only` mode
- `interleaved_thinking` flag handling
- `extra_betas` merging
- `anthropic-version` header presence
- `x-stainless-*` header presence
- `User-Agent: claude-cli/...` header
- OAuth token detection (`sk-ant-oat` prefix)
- The `is_oauth:` keyword
- `anthropic-beta` header construction

Removing these examples is NOT "weakening tests" — they target
deleted code. The behavior they covered no longer exists.

### 2. Replace with a focused MiniMax-only suite

The new spec should cover ONLY the new contract:

```ruby
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Dispatch::Adapter::MiniMax::Headers do
  describe ".build" do
    it "always sets Authorization to Bearer <api_key>" do
      headers = described_class.build(api_key: "k_abc")
      expect(headers["Authorization"]).to eq("Bearer k_abc")
    end

    it "always sets Content-Type to application/json" do
      headers = described_class.build(api_key: "k_abc")
      expect(headers["Content-Type"]).to eq("application/json")
    end

    it "sets Accept to application/json when stream is false" do
      headers = described_class.build(api_key: "k_abc", stream: false)
      expect(headers["Accept"]).to eq("application/json")
    end

    it "sets Accept to text/event-stream when stream is true" do
      headers = described_class.build(api_key: "k_abc", stream: true)
      expect(headers["Accept"]).to eq("text/event-stream")
    end

    it "lets caller extras pass through" do
      headers = described_class.build(api_key: "k", extra: { "X-Trace-Id" => "abc" })
      expect(headers["X-Trace-Id"]).to eq("abc")
    end

    it "never lets caller extras override Authorization" do
      headers = described_class.build(api_key: "k", extra: { "Authorization" => "Bearer evil" })
      expect(headers["Authorization"]).to eq("Bearer k")
    end

    it "does NOT include anthropic-version, x-stainless, or User-Agent headers" do
      headers = described_class.build(api_key: "k")
      expect(headers).not_to have_key("anthropic-version")
      expect(headers).not_to have_key("anthropic-beta")
      expect(headers.keys.grep(/x-stainless/i)).to eq([])
      expect(headers).not_to have_key("User-Agent")
    end
  end
end
```

## Steps for request_builder_spec.rb

### 1. Delete every example covering removed concerns

DELETE examples covering:

- `is_oauth:` parameter behavior (keyword removed in phase 02).
- `proxy_` prefixing of tool names (cloaking deleted in phase 02).
- `metadata: { user_id: ... }` cloaking-derived defaults
  (still emit a metadata block when caller supplies one, but no auto-
  generated user_id).
- Billing-payload synthetic system block.
- The `claude-3-5-haiku` skip-billing-block special case.

### 2. Update remaining examples to MiniMax shape

For each surviving example:

- Replace `"claude-sonnet-4-5-20250929"` (or whatever the old default
  was) with `"MiniMax-M2.7"` in `model` fields and assertions.
- Remove `is_oauth:` from any `RequestBuilder.build` call.
- Update `system` assertion expectations: with cloaking gone, the
  system block is exactly what the caller passed (string → wrapped in
  one text block; array → passed through; nil → omitted).

### 3. Add new examples for MiniMax constraints

Append new examples that exercise the work from phases 08, 09, 10:

```ruby
context "temperature validation" do
  it "raises ArgumentError when temperature is 0.0" do
    expect {
      described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil,
                            tools: [], base_url: "https://api.minimax.io/anthropic",
                            temperature: 0.0)
    }.to raise_error(ArgumentError, /temperature/)
  end

  it "raises ArgumentError when temperature exceeds 1.0" do
    expect {
      described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil,
                            tools: [], base_url: "https://api.minimax.io/anthropic",
                            temperature: 1.01)
    }.to raise_error(ArgumentError, /temperature/)
  end

  it "raises ArgumentError when temperature is negative" do
    expect {
      described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil,
                            tools: [], base_url: "https://api.minimax.io/anthropic",
                            temperature: -0.1)
    }.to raise_error(ArgumentError, /temperature/)
  end

  it "accepts temperature 1.0" do
    body = described_class.build(model_id: "MiniMax-M2.7", messages: [],
                                 system: nil, tools: [],
                                 base_url: "https://api.minimax.io/anthropic",
                                 temperature: 1.0)
    expect(body[:temperature]).to eq(1.0)
  end

  it "omits temperature when nil" do
    body = described_class.build(model_id: "MiniMax-M2.7", messages: [],
                                 system: nil, tools: [],
                                 base_url: "https://api.minimax.io/anthropic",
                                 temperature: nil)
    expect(body).not_to have_key(:temperature)
  end
end

context "ignored parameters" do
  it "strips top_k from the wire body even if forced via extras" do
    body = described_class.build(
      model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [],
      base_url: "https://api.minimax.io/anthropic"
    )
    body[:top_k] = 5  # simulate accidental injection
    described_class.send(:strip_ignored!, body)
    expect(body).not_to have_key(:top_k)
  end

  # Mirror examples for stop_sequences, service_tier, mcp_servers,
  # context_management, container.
end

context "image / document content rejection" do
  it "raises ArgumentError when an ImageBlock is present" do
    skip "interface gem must define ImageBlock" unless defined?(Dispatch::Adapter::ImageBlock)

    msg = Dispatch::Adapter::Message.new(
      role: "user",
      content: [Dispatch::Adapter::ImageBlock.new(source: { type: "base64", media_type: "image/png", data: "" })]
    )
    expect {
      described_class.build(
        model_id: "MiniMax-M2.7", messages: [msg], system: nil, tools: [],
        base_url: "https://api.minimax.io/anthropic"
      )
    }.to raise_error(ArgumentError, /MiniMax does not support image/)
  end
end
```

NOTE on `skip`: the rule says "no skipped or pending examples after
phase 17". If the interface gem actually exposes `ImageBlock` /
`DocumentBlock`, REMOVE the `skip` and replace with concrete
construction. Verify by reading
`reference/dispatch-adapter-minimax/Gemfile.lock` →
`dispatch-adapter-interface` source location, then grep that gem for
`class ImageBlock` / `class DocumentBlock`. If they exist, write the
spec without `skip`. If they don't exist, REMOVE the example entirely
(do not leave a `skip`). Document briefly in a comment why no example
exists for image rejection.

## Acceptance criteria

- `headers_spec.rb` and `request_builder_spec.rb` both pass cleanly
  (no `.skip`, no `.pending`).
- `grep -n 'is_oauth\|sk-ant-oat\|claude_code_only\|anthropic-beta\|x-stainless\|claude-cli\|proxy_\|interleaved_thinking\|extra_betas' spec/dispatch/adapter/minimax/headers_spec.rb spec/dispatch/adapter/minimax/request_builder_spec.rb`
  returns ZERO matches.
- `bundle exec rubocop --autocorrect-all` exits 0.

## Verification

Run `run_tests` with `project_path=reference/dispatch-adapter-minimax`.
Both rubocop and the two retargeted spec files must pass cleanly. Other
spec failures are acceptable here (handled in phases 14–16).