Skip to content

Commit 1db5a43

Browse files
committed
test(ai): record MiniMax messages compatibility
1 parent 08a7080 commit 1db5a43

6 files changed

Lines changed: 170 additions & 2 deletions

File tree

packages/ai/STATUS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LLM Provider Parity Status
22

3-
Last reviewed: 2026-07-16
3+
Last reviewed: 2026-07-17
44

55
This file tracks the gap between the native `@opencode-ai/ai` package and the AI SDK provider packages that opencode still depends on for many catalog/runtime paths.
66

@@ -20,7 +20,7 @@ This file tracks the gap between the native `@opencode-ai/ai` package and the AI
2020
| OpenAI Responses WebSocket | `src/protocols/openai-responses.ts`, `src/route/transport/websocket.ts` | Present as `OpenAI.responsesWebSocket(...)`. | Runner/catalog support explicitly must not downgrade WebSocket routes; broader runtime selection is not complete. |
2121
| OpenAI-compatible Chat | `src/protocols/openai-compatible-chat.ts`, `src/providers/openai-compatible.ts` | Usable for generic Chat and several profiles: Baseten, Cerebras, DeepInfra, DeepSeek, Fireworks, Groq, TogetherAI. | Family quirks are mostly endpoint defaults, not full typed behavior. |
2222
| OpenAI-compatible Responses | `src/protocols/openai-compatible-responses.ts`, `src/providers/openai-compatible-responses.ts` | Usable for deployments that implement the OpenAI Responses wire protocol. | No named family profiles or recorded deployment coverage yet. |
23-
| Anthropic-compatible Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic-compatible.ts` | Usable for deployments that implement the Anthropic Messages wire protocol. Named Anthropic composes this base. | No named compatible family profiles or recorded deployment coverage yet. |
23+
| Anthropic-compatible Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic-compatible.ts` | Usable for deployments that implement the Anthropic Messages wire protocol. Named Anthropic composes this base; MiniMax M3 has recorded text and tool-loop coverage. | No named compatible family profiles yet. |
2424
| Anthropic Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic.ts` | Usable. Supports tools, thinking, cache control, images, server-hosted tool events, and usage. | Provider option surface is small. Beta/header handling, metadata, and newer Messages fields need a typed parity pass. |
2525
| Gemini Developer API | `src/protocols/gemini.ts`, `src/providers/google.ts` | Usable for Google API key flow. Supports text, images, tools, thinking signatures, and cache usage. | This is not Vertex. Typed provider options are narrow; many Gemini request fields currently require raw `http.body` overlays. |
2626
| Vertex Gemini | `src/protocols/gemini.ts`, `src/providers/google-vertex.ts` | Usable through API-key express mode, explicit OAuth tokens, or ADC with project/location endpoint derivation, including tuned `endpoints/...` deployments. | Core runner/catalog mapping and recorded provider coverage are missing. |

packages/ai/script/setup-recording-env.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ const PROVIDERS: ReadonlyArray<Provider> = [
161161
vars: [{ name: "TOGETHER_AI_API_KEY" }],
162162
validate: (env) => validateBearer("https://api.together.xyz/v1/models", Redacted.make(env.TOGETHER_AI_API_KEY)),
163163
},
164+
{
165+
id: "minimax",
166+
label: "MiniMax",
167+
tier: "compatible",
168+
note: "Anthropic-compatible Messages text/tool recorded tests",
169+
vars: [{ name: "MINIMAX_API_KEY" }],
170+
validate: (env) =>
171+
HttpClientRequest.get("https://api.minimax.io/anthropic/v1/models").pipe(
172+
HttpClientRequest.setHeader("x-api-key", Redacted.value(Redacted.make(env.MINIMAX_API_KEY))),
173+
executeRequest,
174+
),
175+
},
164176
{
165177
id: "mistral",
166178
label: "Mistral",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"version": 1,
3+
"metadata": {
4+
"provider": "minimax",
5+
"protocol": "anthropic-messages",
6+
"route": "anthropic-messages",
7+
"transport": "http",
8+
"model": "MiniMax-M3",
9+
"tags": [
10+
"prefix:anthropic-compatible-messages",
11+
"provider:minimax",
12+
"protocol:anthropic-messages",
13+
"text",
14+
"golden"
15+
],
16+
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-text",
17+
"recordedAt": "2026-07-18T03:42:22.893Z"
18+
},
19+
"interactions": [
20+
{
21+
"transport": "http",
22+
"request": {
23+
"method": "POST",
24+
"url": "https://api.minimax.io/anthropic/v1/messages",
25+
"headers": {
26+
"anthropic-version": "2023-06-01",
27+
"content-type": "application/json"
28+
},
29+
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"You are concise.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Reply exactly with: Hello!\"}]}],\"stream\":true,\"max_tokens\":40,\"temperature\":0}"
30+
},
31+
"response": {
32+
"status": 200,
33+
"headers": {
34+
"content-type": "text/event-stream; charset=utf-8"
35+
},
36+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"1a0b363d0882af316faebcec4d4855a8\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":53,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"!\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":53,\"output_tokens\":2,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
37+
}
38+
}
39+
]
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"version": 1,
3+
"metadata": {
4+
"provider": "minimax",
5+
"protocol": "anthropic-messages",
6+
"route": "anthropic-messages",
7+
"transport": "http",
8+
"model": "MiniMax-M3",
9+
"tags": [
10+
"prefix:anthropic-compatible-messages",
11+
"provider:minimax",
12+
"protocol:anthropic-messages",
13+
"tool",
14+
"tool-call",
15+
"golden"
16+
],
17+
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-tool-call",
18+
"recordedAt": "2026-07-18T03:42:23.876Z"
19+
},
20+
"interactions": [
21+
{
22+
"transport": "http",
23+
"request": {
24+
"method": "POST",
25+
"url": "https://api.minimax.io/anthropic/v1/messages",
26+
"headers": {
27+
"anthropic-version": "2023-06-01",
28+
"content-type": "application/json"
29+
},
30+
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Call tools exactly as requested.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Call get_weather with city exactly Paris.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
31+
},
32+
"response": {
33+
"status": 200,
34+
"headers": {
35+
"content-type": "text/event-stream; charset=utf-8"
36+
},
37+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"6731ecc323233459d1792df9a733dd98\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":404,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_function_vkxtif4epmvm_1\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":290,\"output_tokens\":27,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
38+
}
39+
}
40+
]
41+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"version": 1,
3+
"metadata": {
4+
"provider": "minimax",
5+
"protocol": "anthropic-messages",
6+
"route": "anthropic-messages",
7+
"transport": "http",
8+
"model": "MiniMax-M3",
9+
"tags": [
10+
"prefix:anthropic-compatible-messages",
11+
"provider:minimax",
12+
"protocol:anthropic-messages",
13+
"tool",
14+
"tool-loop",
15+
"golden"
16+
],
17+
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-tool-loop",
18+
"recordedAt": "2026-07-18T03:42:25.248Z"
19+
},
20+
"interactions": [
21+
{
22+
"transport": "http",
23+
"request": {
24+
"method": "POST",
25+
"url": "https://api.minimax.io/anthropic/v1/messages",
26+
"headers": {
27+
"anthropic-version": "2023-06-01",
28+
"content-type": "application/json"
29+
},
30+
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
31+
},
32+
"response": {
33+
"status": 200,
34+
"headers": {
35+
"content-type": "text/event-stream; charset=utf-8"
36+
},
37+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"3807fa12f9ecb9357df511e099da6da0\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":417,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_function_yr64rwmre4gr_1\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":303,\"output_tokens\":27,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
38+
}
39+
},
40+
{
41+
"transport": "http",
42+
"request": {
43+
"method": "POST",
44+
"url": "https://api.minimax.io/anthropic/v1/messages",
45+
"headers": {
46+
"anthropic-version": "2023-06-01",
47+
"content-type": "application/json"
48+
},
49+
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"call_function_yr64rwmre4gr_1\",\"name\":\"get_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_function_yr64rwmre4gr_1\",\"content\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
50+
},
51+
"response": {
52+
"status": 200,
53+
"headers": {
54+
"content-type": "text/event-stream; charset=utf-8"
55+
},
56+
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"92f8a1e86f29946eb2699d40a088fc08\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":41,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":430,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" is sunny.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":41,\"output_tokens\":4,\"cache_read_input_tokens\":430,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
57+
}
58+
}
59+
]
60+
}

packages/ai/test/provider/golden.recorded.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Anthropic from "../../src/providers/anthropic"
2+
import * as AnthropicCompatible from "../../src/providers/anthropic-compatible"
23
import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare"
34
import * as Google from "../../src/providers/google"
45
import * as OpenAI from "../../src/providers/openai"
@@ -17,6 +18,11 @@ const anthropic = Anthropic.configure({
1718
})
1819
const anthropicHaiku = anthropic.model("claude-haiku-4-5-20251001")
1920
const anthropicOpus = anthropic.model("claude-opus-4-7")
21+
const minimax = AnthropicCompatible.configure({
22+
apiKey: process.env.MINIMAX_API_KEY ?? "fixture",
23+
baseURL: "https://api.minimax.io/anthropic/v1",
24+
provider: "minimax",
25+
}).model("MiniMax-M3")
2026
const google = Google.configure({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? "fixture" })
2127
const gemini = google.model("gemini-2.5-flash")
2228
const xai = XAI.configure({ apiKey: process.env.XAI_API_KEY ?? "fixture" })
@@ -108,6 +114,15 @@ describeRecordedGoldenScenarios([
108114
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
109115
],
110116
},
117+
{
118+
name: "MiniMax M3 Anthropic-compatible",
119+
prefix: "anthropic-compatible-messages",
120+
protocol: "anthropic-messages",
121+
model: minimax,
122+
requires: ["MINIMAX_API_KEY"],
123+
options: { redact: { allowRequestHeaders: ["anthropic-version"] } },
124+
scenarios: ["text", "tool-call", "tool-loop"],
125+
},
111126
{
112127
name: "Gemini 2.5 Flash",
113128
prefix: "gemini",

0 commit comments

Comments
 (0)