summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/13-25-retarget-headers-and-request-builder-specs.md
diff options
context:
space:
mode:
Diffstat (limited to '.rules/plan/13-25-retarget-headers-and-request-builder-specs.md')
-rw-r--r--.rules/plan/13-25-retarget-headers-and-request-builder-specs.md223
1 files changed, 223 insertions, 0 deletions
diff --git a/.rules/plan/13-25-retarget-headers-and-request-builder-specs.md b/.rules/plan/13-25-retarget-headers-and-request-builder-specs.md
new file mode 100644
index 0000000..e192f01
--- /dev/null
+++ b/.rules/plan/13-25-retarget-headers-and-request-builder-specs.md
@@ -0,0 +1,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).