diff options
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.md | 223 |
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). |
