fix(ai): use adaptive thinking for Kimi Coding
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Kimi Coding requests to use Anthropic adaptive thinking effort without token budgets, and enabled empty thinking signatures for K3 and `kimi-for-coding`.
|
||||||
- Fixed Kimi K3 pricing metadata for Moonshot AI and Moonshot AI China.
|
- Fixed Kimi K3 pricing metadata for Moonshot AI and Moonshot AI China.
|
||||||
- Fixed Kimi Coding K3 thinking-level metadata to expose only the supported `max` level ([#6737](https://github.com/earendil-works/pi/issues/6737)).
|
- Fixed Kimi Coding K3 thinking-level metadata to expose only the supported `max` level ([#6737](https://github.com/earendil-works/pi/issues/6737)).
|
||||||
- Fixed catalog generation restoring xAI models removed in 0.80.9 ([#6736](https://github.com/earendil-works/pi/issues/6736)).
|
- Fixed catalog generation restoring xAI models removed in 0.80.9 ([#6736](https://github.com/earendil-works/pi/issues/6736)).
|
||||||
|
|||||||
@@ -1641,6 +1641,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||||||
const normalizedId = kimiAliases.has(modelId) ? "kimi-for-coding" : modelId;
|
const normalizedId = kimiAliases.has(modelId) ? "kimi-for-coding" : modelId;
|
||||||
const normalizedName = kimiAliases.has(modelId) ? "Kimi For Coding" : m.name || normalizedId;
|
const normalizedName = kimiAliases.has(modelId) ? "Kimi For Coding" : m.name || normalizedId;
|
||||||
const isKimiK3 = normalizedId === "k3";
|
const isKimiK3 = normalizedId === "k3";
|
||||||
|
const allowEmptySignature = isKimiK3 || normalizedId === "kimi-for-coding";
|
||||||
|
|
||||||
models.push({
|
models.push({
|
||||||
id: normalizedId,
|
id: normalizedId,
|
||||||
@@ -1650,6 +1651,10 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||||||
// Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages
|
// Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: { ...KIMI_STATIC_HEADERS },
|
headers: { ...KIMI_STATIC_HEADERS },
|
||||||
|
compat: {
|
||||||
|
...(allowEmptySignature ? { allowEmptySignature: true } : {}),
|
||||||
|
forceAdaptiveThinking: true,
|
||||||
|
},
|
||||||
reasoning: isKimiK3 || m.reasoning === true,
|
reasoning: isKimiK3 || m.reasoning === true,
|
||||||
...(isKimiK3 ? { thinkingLevelMap: KIMI_K3_THINKING_LEVEL_MAP } : {}),
|
...(isKimiK3 ? { thinkingLevelMap: KIMI_K3_THINKING_LEVEL_MAP } : {}),
|
||||||
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const KIMI_CODING_MODELS = {
|
|||||||
provider: "kimi-coding",
|
provider: "kimi-coding",
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: {"User-Agent":"KimiCLI/1.5"},
|
headers: {"User-Agent":"KimiCLI/1.5"},
|
||||||
|
compat: {"forceAdaptiveThinking":true},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -29,6 +30,7 @@ export const KIMI_CODING_MODELS = {
|
|||||||
provider: "kimi-coding",
|
provider: "kimi-coding",
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: {"User-Agent":"KimiCLI/1.5"},
|
headers: {"User-Agent":"KimiCLI/1.5"},
|
||||||
|
compat: {"allowEmptySignature":true,"forceAdaptiveThinking":true},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"},
|
thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":null,"xhigh":null,"max":"max"},
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
@@ -48,6 +50,7 @@ export const KIMI_CODING_MODELS = {
|
|||||||
provider: "kimi-coding",
|
provider: "kimi-coding",
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: {"User-Agent":"KimiCLI/1.5"},
|
headers: {"User-Agent":"KimiCLI/1.5"},
|
||||||
|
compat: {"allowEmptySignature":true,"forceAdaptiveThinking":true},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -66,6 +69,7 @@ export const KIMI_CODING_MODELS = {
|
|||||||
provider: "kimi-coding",
|
provider: "kimi-coding",
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: {"User-Agent":"KimiCLI/1.5"},
|
headers: {"User-Agent":"KimiCLI/1.5"},
|
||||||
|
compat: {"forceAdaptiveThinking":true},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
@@ -84,6 +88,7 @@ export const KIMI_CODING_MODELS = {
|
|||||||
provider: "kimi-coding",
|
provider: "kimi-coding",
|
||||||
baseUrl: "https://api.kimi.com/coding",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
headers: {"User-Agent":"KimiCLI/1.5"},
|
headers: {"User-Agent":"KimiCLI/1.5"},
|
||||||
|
compat: {"forceAdaptiveThinking":true},
|
||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text"],
|
input: ["text"],
|
||||||
cost: {
|
cost: {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ const EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS = [
|
|||||||
"anthropic/claude-opus-4-8",
|
"anthropic/claude-opus-4-8",
|
||||||
"anthropic/claude-sonnet-5",
|
"anthropic/claude-sonnet-5",
|
||||||
"cloudflare-ai-gateway/claude-fable-5",
|
"cloudflare-ai-gateway/claude-fable-5",
|
||||||
|
"kimi-coding/k2p7",
|
||||||
|
"kimi-coding/k3",
|
||||||
|
"kimi-coding/kimi-for-coding",
|
||||||
|
"kimi-coding/kimi-for-coding-highspeed",
|
||||||
|
"kimi-coding/kimi-k2-thinking",
|
||||||
"opencode/claude-opus-4-8",
|
"opencode/claude-opus-4-8",
|
||||||
"vercel-ai-gateway/anthropic/claude-opus-4.8",
|
"vercel-ai-gateway/anthropic/claude-opus-4.8",
|
||||||
"vercel-ai-gateway/anthropic/claude-sonnet-5",
|
"vercel-ai-gateway/anthropic/claude-sonnet-5",
|
||||||
@@ -27,7 +32,7 @@ describe("Anthropic adaptive thinking model metadata", () => {
|
|||||||
expect(flaggedModels).toEqual(expect.arrayContaining([...EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS].sort()));
|
expect(flaggedModels).toEqual(expect.arrayContaining([...EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS].sort()));
|
||||||
expect(flaggedModels).toEqual(
|
expect(flaggedModels).toEqual(
|
||||||
flaggedModels.filter((modelId) =>
|
flaggedModels.filter((modelId) =>
|
||||||
/(opus[-.]4[-.][678]|sonnet[-.]4[-.]6|sonnet[-.]5|fable[-.]5)/.test(modelId),
|
/(opus[-.]4[-.][678]|sonnet[-.]4[-.]6|sonnet[-.]5|fable[-.]5|kimi-coding\/)/.test(modelId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { streamSimple } from "../src/compat.ts";
|
import { getModel, streamSimple } from "../src/compat.ts";
|
||||||
import type { AssistantMessage, Context, Model } from "../src/types.ts";
|
import type { AssistantMessage, Context, Model } from "../src/types.ts";
|
||||||
|
|
||||||
interface AnthropicPayload {
|
interface AnthropicPayload {
|
||||||
@@ -32,13 +32,18 @@ function makeModel(allowEmptySignature?: boolean): Model<"anthropic-messages"> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeContext(thinkingSignature: string, thinking = "internal reasoning"): Context {
|
function makeContext(
|
||||||
|
thinkingSignature: string,
|
||||||
|
thinking = "internal reasoning",
|
||||||
|
provider = "xiaomi-token-plan-ams",
|
||||||
|
model = "mimo-v2.5-pro",
|
||||||
|
): Context {
|
||||||
const assistant: AssistantMessage = {
|
const assistant: AssistantMessage = {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: [{ type: "thinking", thinking, thinkingSignature }],
|
content: [{ type: "thinking", thinking, thinkingSignature }],
|
||||||
provider: "xiaomi-token-plan-ams",
|
provider,
|
||||||
api: "anthropic-messages",
|
api: "anthropic-messages",
|
||||||
model: "mimo-v2.5-pro",
|
model,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
usage: {
|
usage: {
|
||||||
input: 0,
|
input: 0,
|
||||||
@@ -91,4 +96,13 @@ describe("Anthropic empty thinking signature compat", () => {
|
|||||||
const assistant = payload.messages?.find((message) => message.role === "assistant");
|
const assistant = payload.messages?.find((message) => message.role === "assistant");
|
||||||
expect(assistant?.content).toEqual([{ type: "thinking", thinking: "internal reasoning", signature: "" }]);
|
expect(assistant?.content).toEqual([{ type: "thinking", thinking: "internal reasoning", signature: "" }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["k3", "kimi-for-coding"] as const)("allows empty signatures for Kimi Coding %s", async (modelId) => {
|
||||||
|
const model = getModel("kimi-coding", modelId);
|
||||||
|
expect(model.compat?.allowEmptySignature).toBe(true);
|
||||||
|
|
||||||
|
const payload = await capturePayload(model, makeContext(" ", "internal reasoning", "kimi-coding", modelId));
|
||||||
|
const assistant = payload.messages?.find((message) => message.role === "assistant");
|
||||||
|
expect(assistant?.content).toEqual([{ type: "thinking", thinking: "internal reasoning", signature: "" }]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,6 +89,22 @@ describe("Anthropic forceAdaptiveThinking compat override", () => {
|
|||||||
expect(payload.output_config).toEqual({ effort: "xhigh" });
|
expect(payload.output_config).toEqual({ effort: "xhigh" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["k2p7", "medium", "medium"],
|
||||||
|
["k3", "max", "max"],
|
||||||
|
["kimi-for-coding", "medium", "medium"],
|
||||||
|
["kimi-for-coding-highspeed", "medium", "medium"],
|
||||||
|
["kimi-k2-thinking", "medium", "medium"],
|
||||||
|
] as const)(
|
||||||
|
"uses adaptive thinking effort without a token budget for Kimi Coding %s",
|
||||||
|
async (modelId, reasoning, effort) => {
|
||||||
|
const payload = await capturePayload(getModel("kimi-coding", modelId), { reasoning });
|
||||||
|
|
||||||
|
expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||||
|
expect(payload.output_config).toEqual({ effort });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("allows built-in adaptive models to opt out with compat.forceAdaptiveThinking false", async () => {
|
it("allows built-in adaptive models to opt out with compat.forceAdaptiveThinking false", async () => {
|
||||||
const model: Model<"anthropic-messages"> = {
|
const model: Model<"anthropic-messages"> = {
|
||||||
...getModel("anthropic", "claude-opus-4-8"),
|
...getModel("anthropic", "claude-opus-4-8"),
|
||||||
|
|||||||
Reference in New Issue
Block a user