Fix Kimi and Xiaomi model metadata
Closes #5075, closes #5078. Refs #5087.
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
|
||||
- Fixed Anthropic-compatible replay for providers that return empty thinking signatures by adding an opt-in `allowEmptySignature` compatibility flag ([#4464](https://github.com/earendil-works/pi/issues/4464)).
|
||||
- Fixed OpenAI and OpenRouter GPT-5.5 Pro thinking level metadata to expose only supported medium, high, and xhigh efforts.
|
||||
- Fixed OpenCode Go Kimi K2.6 thinking-off requests to send `thinking: "none"` ([#5078](https://github.com/earendil-works/pi/issues/5078)).
|
||||
- Fixed Xiaomi Token Plan model metadata to omit unsupported `mimo-v2-flash` variants ([#5075](https://github.com/earendil-works/pi/issues/5075)).
|
||||
|
||||
## [0.76.0] - 2026-05-27
|
||||
|
||||
|
||||
@@ -269,6 +269,9 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
|
||||
// Pi's low/medium/high pass through verbatim; OpenRouter normalizes to Mercury's vocabulary.
|
||||
mergeThinkingLevelMap(model, { off: null });
|
||||
}
|
||||
if (model.provider === "opencode-go" && model.id === "kimi-k2.6") {
|
||||
mergeThinkingLevelMap(model, { off: "none" });
|
||||
}
|
||||
}
|
||||
|
||||
function getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined {
|
||||
@@ -900,6 +903,9 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
api = "openai-completions";
|
||||
baseUrl = `${variant.basePath}/v1`;
|
||||
}
|
||||
if (modelId === "kimi-k2.6") {
|
||||
compat = { ...(compat ?? {}), thinkingFormat: "string-thinking" };
|
||||
}
|
||||
if (modelId === "qwen3.5-plus" || modelId === "qwen3.6-plus") {
|
||||
api = "openai-completions";
|
||||
baseUrl = `${variant.basePath}/v1`;
|
||||
@@ -1116,6 +1122,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
for (const [modelId, model] of Object.entries(data.xiaomi.models)) {
|
||||
const m = model as ModelsDevModel;
|
||||
if (m.tool_call !== true) continue;
|
||||
if (provider.startsWith("xiaomi-token-plan-") && modelId === "mimo-v2-flash") continue;
|
||||
|
||||
models.push({
|
||||
id: modelId,
|
||||
|
||||
@@ -7968,7 +7968,9 @@ export const MODELS = {
|
||||
api: "openai-completions",
|
||||
provider: "opencode-go",
|
||||
baseUrl: "https://opencode.ai/zen/go/v1",
|
||||
compat: {"thinkingFormat":"string-thinking"},
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {"off":"none"},
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.95,
|
||||
@@ -15929,24 +15931,6 @@ export const MODELS = {
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"xiaomi-token-plan-ams": {
|
||||
"mimo-v2-flash": {
|
||||
id: "mimo-v2-flash",
|
||||
name: "MiMo-V2-Flash",
|
||||
api: "openai-completions",
|
||||
provider: "xiaomi-token-plan-ams",
|
||||
baseUrl: "https://token-plan-ams.xiaomimimo.com/v1",
|
||||
compat: {"requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.1,
|
||||
output: 0.3,
|
||||
cacheRead: 0.01,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"mimo-v2-omni": {
|
||||
id: "mimo-v2-omni",
|
||||
name: "MiMo-V2-Omni",
|
||||
@@ -16021,24 +16005,6 @@ export const MODELS = {
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"xiaomi-token-plan-cn": {
|
||||
"mimo-v2-flash": {
|
||||
id: "mimo-v2-flash",
|
||||
name: "MiMo-V2-Flash",
|
||||
api: "openai-completions",
|
||||
provider: "xiaomi-token-plan-cn",
|
||||
baseUrl: "https://token-plan-cn.xiaomimimo.com/v1",
|
||||
compat: {"requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.1,
|
||||
output: 0.3,
|
||||
cacheRead: 0.01,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"mimo-v2-omni": {
|
||||
id: "mimo-v2-omni",
|
||||
name: "MiMo-V2-Omni",
|
||||
@@ -16113,24 +16079,6 @@ export const MODELS = {
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"xiaomi-token-plan-sgp": {
|
||||
"mimo-v2-flash": {
|
||||
id: "mimo-v2-flash",
|
||||
name: "MiMo-V2-Flash",
|
||||
api: "openai-completions",
|
||||
provider: "xiaomi-token-plan-sgp",
|
||||
baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1",
|
||||
compat: {"requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.1,
|
||||
output: 0.3,
|
||||
cacheRead: 0.01,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"mimo-v2-omni": {
|
||||
id: "mimo-v2-omni",
|
||||
name: "MiMo-V2-Omni",
|
||||
|
||||
@@ -594,6 +594,13 @@ function buildParams(
|
||||
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
|
||||
togetherParams.reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
||||
}
|
||||
} else if (compat.thinkingFormat === "string-thinking" && model.reasoning) {
|
||||
const stringThinkingParams = params as typeof params & { thinking?: string };
|
||||
if (options?.reasoningEffort) {
|
||||
stringThinkingParams.thinking = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
||||
} else if (model.thinkingLevelMap?.off !== null) {
|
||||
stringThinkingParams.thinking = model.thinkingLevelMap?.off ?? "none";
|
||||
}
|
||||
} else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
||||
// OpenAI-style reasoning_effort
|
||||
(params as any).reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
||||
|
||||
@@ -387,8 +387,16 @@ export interface OpenAICompletionsCompat {
|
||||
requiresThinkingAsText?: boolean;
|
||||
/** Whether all replayed assistant messages must include an empty reasoning_content field when reasoning is enabled. Default: auto-detected from URL. */
|
||||
requiresReasoningContentOnAssistantMessages?: boolean;
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
|
||||
thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "qwen-chat-template";
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, and "string-thinking" uses top-level thinking: string. Default: "openai". */
|
||||
thinkingFormat?:
|
||||
| "openai"
|
||||
| "openrouter"
|
||||
| "deepseek"
|
||||
| "together"
|
||||
| "zai"
|
||||
| "qwen"
|
||||
| "qwen-chat-template"
|
||||
| "string-thinking";
|
||||
/** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */
|
||||
openRouterRouting?: OpenRouterRouting;
|
||||
/** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */
|
||||
|
||||
@@ -995,6 +995,51 @@ describe("openai-completions tool_choice", () => {
|
||||
expect(messages[0]).not.toHaveProperty("reasoning");
|
||||
});
|
||||
|
||||
it("sends thinking none for OpenCode Go Kimi K2.6 when thinking is off", async () => {
|
||||
const model = getModel("opencode-go", "kimi-k2.6")!;
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
|
||||
},
|
||||
{
|
||||
apiKey: "test",
|
||||
onPayload: (params: unknown) => {
|
||||
payload = params;
|
||||
},
|
||||
},
|
||||
).result();
|
||||
|
||||
const params = (payload ?? mockState.lastParams) as { thinking?: string; reasoning_effort?: string };
|
||||
expect(params.thinking).toBe("none");
|
||||
expect(params.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sends thinking effort for OpenCode Go Kimi K2.6 when thinking is enabled", async () => {
|
||||
const model = getModel("opencode-go", "kimi-k2.6")!;
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
|
||||
},
|
||||
{
|
||||
apiKey: "test",
|
||||
reasoning: "high",
|
||||
onPayload: (params: unknown) => {
|
||||
payload = params;
|
||||
},
|
||||
},
|
||||
).result();
|
||||
|
||||
const params = (payload ?? mockState.lastParams) as { thinking?: string; reasoning_effort?: string };
|
||||
expect(params.thinking).toBe("high");
|
||||
expect(params.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not double-count reasoning tokens in completion usage", async () => {
|
||||
mockState.chunks = [
|
||||
{
|
||||
|
||||
15
packages/ai/test/xiaomi-models.test.ts
Normal file
15
packages/ai/test/xiaomi-models.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getModel, getModels } from "../src/models.ts";
|
||||
|
||||
describe("Xiaomi MiMo models", () => {
|
||||
it("keeps mimo-v2-flash on the API billing provider", () => {
|
||||
expect(getModel("xiaomi", "mimo-v2-flash")).toBeDefined();
|
||||
});
|
||||
|
||||
it.each(["xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"] as const)(
|
||||
"omits mimo-v2-flash from %s",
|
||||
(provider) => {
|
||||
expect(getModels(provider).some((model) => model.id === "mimo-v2-flash")).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user