mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(ai): revert kimi-coding switch to Chat Completions
Reverts b06c1f2. The pi.dev model catalog is served to every client
since 0.80.7, so released clients received kimi-coding models with
api openai-completions and baseUrl https://api.kimi.com/coding/v1.
Their kimi-coding provider only implements Anthropic Messages and
ignores model.api, so requests went to /coding/v1/v1/messages and
failed with 404. The change will re-land together with a bump of the
catalog's minimum pi version.
refs #10720, refs #10985
This commit is contained in:
parent
4a5d372915
commit
6a1f799b67
9 changed files with 99 additions and 113 deletions
|
|
@ -10,10 +10,6 @@
|
||||||
|
|
||||||
- Added cache-preserving thinking level changes for GPT-6 models on the openai provider: with an API key, the level is sent as configuration_update input items instead of changing reasoning.effort, controlled by compat.supportsMidConvoEffort ([#10853](https://github.com/earendil-works/pi/issues/10853))
|
- Added cache-preserving thinking level changes for GPT-6 models on the openai provider: with an API key, the level is sent as configuration_update input items instead of changing reasoning.effort, controlled by compat.supportsMidConvoEffort ([#10853](https://github.com/earendil-works/pi/issues/10853))
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Changed the `kimi-coding` provider to use OpenAI Chat Completions at `https://api.kimi.com/coding/v1`, the protocol used by the official Kimi Code client. K3, K3-256K, and Kimi For Coding now accept tools added mid-conversation without invalidating the prompt cache. `models.json` overrides of the `kimi-coding` base URL must now include `/v1` ([#10720](https://github.com/earendil-works/pi/issues/10720))
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed tool-call argument parsing dropping fields when a string contains an invalid `\u` escape such as `\usepackage` ([#10868](https://github.com/earendil-works/pi/issues/10868))
|
- Fixed tool-call argument parsing dropping fields when a string contains an invalid `\u` escape such as `\usepackage` ([#10868](https://github.com/earendil-works/pi/issues/10868))
|
||||||
|
|
|
||||||
|
|
@ -343,21 +343,13 @@ const KIMI_K3_COST = {
|
||||||
cacheRead: 0.3,
|
cacheRead: 0.3,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
} as const;
|
} as const;
|
||||||
// Kimi Chat Completions settings shared by Moonshot and Kimi Coding.
|
// Kimi Coding is subscription-backed, so models.dev reports zero cost. Use the
|
||||||
const KIMI_OPENAI_COMPAT: OpenAICompletionsCompat = {
|
// equivalent Moonshot API rates to estimate the value of subscription usage.
|
||||||
supportsStore: false,
|
const KIMI_CODING_IMPLIED_COSTS: Record<string, Model<Api>["cost"]> = {
|
||||||
supportsDeveloperRole: false,
|
k3: KIMI_K3_COST,
|
||||||
supportsReasoningEffort: false,
|
"kimi-for-coding": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
||||||
maxTokensField: "max_tokens",
|
"kimi-for-coding-highspeed": { input: 1.9, output: 8, cacheRead: 0.38, cacheWrite: 0 },
|
||||||
supportsStrictMode: false,
|
"kimi-k2-thinking": { input: 0.6, output: 2.5, cacheRead: 0.15, cacheWrite: 0 },
|
||||||
thinkingFormat: "deepseek",
|
|
||||||
};
|
|
||||||
// Kimi K3-style models take reasoning_effort and need earlier reasoning sent back.
|
|
||||||
const KIMI_K3_OPENAI_COMPAT: OpenAICompletionsCompat = {
|
|
||||||
...KIMI_OPENAI_COMPAT,
|
|
||||||
requiresReasoningContentOnAssistantMessages: true,
|
|
||||||
thinkingFormat: "openai",
|
|
||||||
supportsReasoningEffort: true,
|
|
||||||
};
|
};
|
||||||
const OPENROUTER_KIMI_K3_MODEL_IDS = new Set(["moonshotai/kimi-k3", "~moonshotai/kimi-latest"]);
|
const OPENROUTER_KIMI_K3_MODEL_IDS = new Set(["moonshotai/kimi-k3", "~moonshotai/kimi-latest"]);
|
||||||
|
|
||||||
|
|
@ -942,7 +934,7 @@ function applyOpenAIToolSearchMetadata(model: Model<Api>): void {
|
||||||
// tool-bearing system messages. Kimi K3 accepts both forms; Fireworks and OpenCode pass
|
// tool-bearing system messages. Kimi K3 accepts both forms; Fireworks and OpenCode pass
|
||||||
// its tool-bearing form through. GitHub Copilot forwards K3 text but silently drops its
|
// its tool-bearing form through. GitHub Copilot forwards K3 text but silently drops its
|
||||||
// tool-bearing message. DeepSeek V4 Pro and OpenAI models behind OpenRouter also accept
|
// tool-bearing message. DeepSeek V4 Pro and OpenAI models behind OpenRouter also accept
|
||||||
// plain system text in place. Kimi Coding sets these flags in createKimiCodingModels().
|
// plain system text in place.
|
||||||
function applyOpenAICompletionsTranscriptMetadata(model: Model<Api>): void {
|
function applyOpenAICompletionsTranscriptMetadata(model: Model<Api>): void {
|
||||||
if (model.api !== "openai-completions") return;
|
if (model.api !== "openai-completions") return;
|
||||||
const isKimiK3 =
|
const isKimiK3 =
|
||||||
|
|
@ -1777,58 +1769,6 @@ function processFireworksModels(provider: ModelsDevProvider | undefined): Model<
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kimi Coding is subscription-backed, so models.dev reports zero cost. Use the
|
|
||||||
// equivalent Moonshot API rates to estimate the value of subscription usage.
|
|
||||||
const KIMI_CODING_IMPLIED_COSTS: Record<string, Model<Api>["cost"]> = {
|
|
||||||
k3: KIMI_K3_COST,
|
|
||||||
"kimi-for-coding": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
|
||||||
"kimi-for-coding-highspeed": { input: 1.9, output: 8, cacheRead: 0.38, cacheWrite: 0 },
|
|
||||||
};
|
|
||||||
// Models whose https://api.kimi.com/coding/v1/models entry reports effort levels and
|
|
||||||
// dynamic tool loading (tools declared in later system messages). Highspeed reports
|
|
||||||
// neither and rejects tool-bearing system messages with a 400.
|
|
||||||
const KIMI_CODING_K3_STYLE_MODEL_IDS = new Set(["k3", "k3-256k", "kimi-for-coding"]);
|
|
||||||
|
|
||||||
// Kimi Coding uses Chat Completions, the same protocol as the official Kimi Code client.
|
|
||||||
function createKimiCodingModels(kimiModels: Record<string, ModelsDevModel>): Model<"openai-completions">[] {
|
|
||||||
const models: Model<"openai-completions">[] = [];
|
|
||||||
for (const [modelId, m] of Object.entries(kimiModels)) {
|
|
||||||
if (m.tool_call !== true) continue;
|
|
||||||
|
|
||||||
const isK3Style = KIMI_CODING_K3_STYLE_MODEL_IDS.has(modelId);
|
|
||||||
const impliedCost = KIMI_CODING_IMPLIED_COSTS[modelId];
|
|
||||||
// K3-style models send reasoning_effort, so expose the effort values models.dev lists.
|
|
||||||
const effortLevels = isK3Style && m.reasoning_options ? getEffortThinkingLevelMap(m.reasoning_options) : undefined;
|
|
||||||
|
|
||||||
models.push({
|
|
||||||
id: modelId,
|
|
||||||
name: m.name || modelId,
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "kimi-coding",
|
|
||||||
baseUrl: "https://api.kimi.com/coding/v1",
|
|
||||||
compat: {
|
|
||||||
...(isK3Style ? KIMI_K3_OPENAI_COMPAT : KIMI_OPENAI_COMPAT),
|
|
||||||
// All models accept system text after the conversation starts.
|
|
||||||
supportsMidConvoSystemMessages: true,
|
|
||||||
...(isK3Style ? { supportsMidConvoToolAdditions: true } : {}),
|
|
||||||
},
|
|
||||||
reasoning: m.reasoning === true,
|
|
||||||
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
|
||||||
cost: {
|
|
||||||
input: m.cost?.input || impliedCost?.input || 0,
|
|
||||||
output: m.cost?.output || impliedCost?.output || 0,
|
|
||||||
cacheRead: m.cost?.cache_read || impliedCost?.cacheRead || 0,
|
|
||||||
cacheWrite: m.cost?.cache_write || impliedCost?.cacheWrite || 0,
|
|
||||||
},
|
|
||||||
contextWindow: m.limit?.context || 4096,
|
|
||||||
maxTokens: m.limit?.output || 4096,
|
|
||||||
// Kimi Code reports every model as always-thinking.
|
|
||||||
thinkingLevelMap: { ...effortLevels, off: null },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadModelsDevData(): Promise<Model<any>[]> {
|
async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||||
try {
|
try {
|
||||||
console.log("Fetching models from models.dev API...");
|
console.log("Fetching models from models.dev API...");
|
||||||
|
|
@ -2432,7 +2372,48 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||||
|
|
||||||
// Process Kimi For Coding models
|
// Process Kimi For Coding models
|
||||||
if (data["kimi-code-plan-global"]?.models) {
|
if (data["kimi-code-plan-global"]?.models) {
|
||||||
models.push(...createKimiCodingModels(data["kimi-code-plan-global"].models as Record<string, ModelsDevModel>));
|
const kimiModels = data["kimi-code-plan-global"].models as Record<string, ModelsDevModel>;
|
||||||
|
const hasCanonicalModel = Object.prototype.hasOwnProperty.call(kimiModels, "kimi-for-coding");
|
||||||
|
|
||||||
|
const kimiAliases = new Set(["k2p5", "k2p6", "k2p7"]);
|
||||||
|
|
||||||
|
for (const [modelId, model] of Object.entries(kimiModels)) {
|
||||||
|
const m = model as ModelsDevModel;
|
||||||
|
if (m.tool_call !== true) continue;
|
||||||
|
// models.dev may expose versioned aliases (e.g. k2p5/k2p6/k2p7).
|
||||||
|
// Normalize aliases to the canonical model id and drop duplicates when canonical exists.
|
||||||
|
if (kimiAliases.has(modelId) && hasCanonicalModel) continue;
|
||||||
|
|
||||||
|
const normalizedId = kimiAliases.has(modelId) ? "kimi-for-coding" : modelId;
|
||||||
|
const normalizedName = kimiAliases.has(modelId) ? "Kimi For Coding" : m.name || normalizedId;
|
||||||
|
const isKimiK3 = normalizedId === "k3";
|
||||||
|
const allowEmptySignature = isKimiK3 || normalizedId === "kimi-for-coding";
|
||||||
|
const impliedCost = KIMI_CODING_IMPLIED_COSTS[normalizedId];
|
||||||
|
|
||||||
|
models.push({
|
||||||
|
id: normalizedId,
|
||||||
|
name: normalizedName,
|
||||||
|
api: "anthropic-messages",
|
||||||
|
provider: "kimi-coding",
|
||||||
|
// Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages
|
||||||
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
|
compat: {
|
||||||
|
...(allowEmptySignature ? { allowEmptySignature: true } : {}),
|
||||||
|
forceAdaptiveThinking: true,
|
||||||
|
},
|
||||||
|
reasoning: isKimiK3 || m.reasoning === true,
|
||||||
|
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||||
|
cost: {
|
||||||
|
input: m.cost?.input || impliedCost?.input || 0,
|
||||||
|
output: m.cost?.output || impliedCost?.output || 0,
|
||||||
|
cacheRead: m.cost?.cache_read || impliedCost?.cacheRead || 0,
|
||||||
|
cacheWrite: m.cost?.cache_write || impliedCost?.cacheWrite || 0,
|
||||||
|
},
|
||||||
|
contextWindow: m.limit?.context || 4096,
|
||||||
|
maxTokens: m.limit?.output || 4096,
|
||||||
|
});
|
||||||
|
recordModelsDevReasoningOptions("kimi-coding", normalizedId, m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Moonshot AI models
|
// Process Moonshot AI models
|
||||||
|
|
@ -2440,6 +2421,14 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||||
{ key: "moonshotai", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1" },
|
{ key: "moonshotai", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1" },
|
||||||
{ key: "moonshotai-cn", provider: "moonshotai-cn", baseUrl: "https://api.moonshot.cn/v1" },
|
{ key: "moonshotai-cn", provider: "moonshotai-cn", baseUrl: "https://api.moonshot.cn/v1" },
|
||||||
] as const;
|
] as const;
|
||||||
|
const moonshotCompat: OpenAICompletionsCompat = {
|
||||||
|
supportsStore: false,
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
supportsReasoningEffort: false,
|
||||||
|
maxTokensField: "max_tokens",
|
||||||
|
supportsStrictMode: false,
|
||||||
|
thinkingFormat: "deepseek",
|
||||||
|
};
|
||||||
const getMoonshotProviderModels = (key: "moonshotai" | "moonshotai-cn"): Record<string, ModelsDevModel> => {
|
const getMoonshotProviderModels = (key: "moonshotai" | "moonshotai-cn"): Record<string, ModelsDevModel> => {
|
||||||
const providerModels = data[key]?.models as Record<string, ModelsDevModel> | undefined;
|
const providerModels = data[key]?.models as Record<string, ModelsDevModel> | undefined;
|
||||||
return providerModels ? { ...providerModels } : {};
|
return providerModels ? { ...providerModels } : {};
|
||||||
|
|
@ -2454,7 +2443,12 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||||
if (m.tool_call !== true) continue;
|
if (m.tool_call !== true) continue;
|
||||||
|
|
||||||
const isKimiK3 = modelId === "kimi-k3";
|
const isKimiK3 = modelId === "kimi-k3";
|
||||||
const compat = { ...(isKimiK3 ? KIMI_K3_OPENAI_COMPAT : KIMI_OPENAI_COMPAT) };
|
const compat = isKimiK3 ? { ...moonshotCompat } : moonshotCompat;
|
||||||
|
if (isKimiK3) {
|
||||||
|
compat.requiresReasoningContentOnAssistantMessages = true;
|
||||||
|
compat.thinkingFormat = "openai";
|
||||||
|
compat.supportsReasoningEffort = true;
|
||||||
|
}
|
||||||
models.push({
|
models.push({
|
||||||
id: modelId,
|
id: modelId,
|
||||||
name: m.name || modelId,
|
name: m.name || modelId,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
|
import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts";
|
||||||
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
|
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
|
||||||
import { loadKimiCodingOAuth } from "../auth/oauth/load.ts";
|
import { loadKimiCodingOAuth } from "../auth/oauth/load.ts";
|
||||||
import { createProvider, type Provider } from "../models.ts";
|
import { createProvider, type Provider } from "../models.ts";
|
||||||
import { KIMI_CODING_MODELS } from "./kimi-coding.models.ts";
|
import { KIMI_CODING_MODELS } from "./kimi-coding.models.ts";
|
||||||
|
|
||||||
export function kimiCodingProvider(): Provider<"openai-completions"> {
|
export function kimiCodingProvider(): Provider<"anthropic-messages"> {
|
||||||
return createProvider({
|
return createProvider({
|
||||||
id: "kimi-coding",
|
id: "kimi-coding",
|
||||||
name: "Kimi For Coding",
|
name: "Kimi For Coding",
|
||||||
baseUrl: "https://api.kimi.com/coding/v1",
|
baseUrl: "https://api.kimi.com/coding",
|
||||||
auth: {
|
auth: {
|
||||||
apiKey: envApiKeyAuth("Kimi API key", ["KIMI_API_KEY"]),
|
apiKey: envApiKeyAuth("Kimi API key", ["KIMI_API_KEY"]),
|
||||||
oauth: lazyOAuth({
|
oauth: lazyOAuth({
|
||||||
|
|
@ -19,6 +19,6 @@ export function kimiCodingProvider(): Provider<"openai-completions"> {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
models: Object.values(KIMI_CODING_MODELS),
|
models: Object.values(KIMI_CODING_MODELS),
|
||||||
api: openAICompletionsApi(),
|
api: anthropicMessagesApi(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ const EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS = [
|
||||||
"fireworks/accounts/fireworks/models/deepseek-v4p1-flash",
|
"fireworks/accounts/fireworks/models/deepseek-v4p1-flash",
|
||||||
"fireworks/accounts/fireworks/models/gpt-oss-120b",
|
"fireworks/accounts/fireworks/models/gpt-oss-120b",
|
||||||
"fireworks/accounts/fireworks/models/qwen3p8-max",
|
"fireworks/accounts/fireworks/models/qwen3p8-max",
|
||||||
|
"kimi-coding/kimi-for-coding",
|
||||||
|
"kimi-coding/k3",
|
||||||
|
"kimi-coding/kimi-for-coding-highspeed",
|
||||||
"opencode/claude-opus-4-8",
|
"opencode/claude-opus-4-8",
|
||||||
"opencode/claude-opus-5",
|
"opencode/claude-opus-5",
|
||||||
"vercel-ai-gateway/anthropic/claude-opus-4.8",
|
"vercel-ai-gateway/anthropic/claude-opus-4.8",
|
||||||
|
|
@ -38,7 +41,9 @@ describe("Anthropic adaptive thinking model metadata", () => {
|
||||||
// Regression for #9323: Fireworks uses catalog effort metadata and
|
// Regression for #9323: Fireworks uses catalog effort metadata and
|
||||||
// verified fallbacks, not a fixed set of adaptive model names.
|
// verified fallbacks, not a fixed set of adaptive model names.
|
||||||
modelId.startsWith("fireworks/") ||
|
modelId.startsWith("fireworks/") ||
|
||||||
/(opus[-.](4[-.][678]|5)|sonnet[-.]4[-.]6|sonnet[-.]5|haiku[-.]5[-.]5|fable[-.]5)/.test(modelId),
|
/(opus[-.](4[-.][678]|5)|sonnet[-.]4[-.]6|sonnet[-.]5|haiku[-.]5[-.]5|fable[-.]5|kimi-coding\/)/.test(
|
||||||
|
modelId,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -134,4 +134,13 @@ describe("Anthropic empty thinking signature compat", () => {
|
||||||
{ type: "text", text: "internal reasoning" },
|
{ type: "text", text: "internal reasoning" },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["k3"] 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,20 @@ describe("Anthropic forceAdaptiveThinking compat override", () => {
|
||||||
expect(payload.output_config).toEqual({ effort: "xhigh" });
|
expect(payload.output_config).toEqual({ effort: "xhigh" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["kimi-for-coding", "medium", "medium"],
|
||||||
|
["k3", "max", "max"],
|
||||||
|
["kimi-for-coding-highspeed", "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"),
|
||||||
|
|
|
||||||
|
|
@ -1041,7 +1041,7 @@ describe("Generate E2E Tests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skipIf(!process.env.KIMI_API_KEY)(
|
describe.skipIf(!process.env.KIMI_API_KEY)(
|
||||||
"Kimi For Coding Provider (kimi-for-coding via OpenAI Completions)",
|
"Kimi For Coding Provider (kimi-for-coding via Anthropic Messages)",
|
||||||
() => {
|
() => {
|
||||||
const llm = getModel("kimi-coding", "kimi-for-coding");
|
const llm = getModel("kimi-coding", "kimi-for-coding");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,12 +209,6 @@ describe("getSupportedThinkingLevels", () => {
|
||||||
expect(getSupportedThinkingLevels(model!)).toEqual(["low", "high", "max"]);
|
expect(getSupportedThinkingLevels(model!)).toEqual(["low", "high", "max"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("excludes off for always-thinking Kimi Coding Highspeed", () => {
|
|
||||||
const model = getModel("kimi-coding", "kimi-for-coding-highspeed");
|
|
||||||
expect(model).toBeDefined();
|
|
||||||
expect(getSupportedThinkingLevels(model!)).not.toContain("off");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("includes only high for OpenCode Grok Build", () => {
|
it("includes only high for OpenCode Grok Build", () => {
|
||||||
const model = getModel("opencode", "grok-build-0.1");
|
const model = getModel("opencode", "grok-build-0.1");
|
||||||
expect(model).toBeDefined();
|
expect(model).toBeDefined();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
import { getModel, streamSimple } from "../src/compat.ts";
|
import { streamSimple } from "../src/compat.ts";
|
||||||
import type { Api, Context, Model, Tool } from "../src/types.ts";
|
import type { Api, Context, Model, Tool } from "../src/types.ts";
|
||||||
|
|
||||||
class PayloadCaptured extends Error {}
|
class PayloadCaptured extends Error {}
|
||||||
|
|
@ -339,32 +339,6 @@ describe("transcript system messages", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
type KimiPayload = {
|
|
||||||
tools?: Array<{ function?: { name: string } }>;
|
|
||||||
messages: Array<{ role: string; tools?: Array<{ function?: { name: string } }> }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// #10720: Kimi Coding uses Chat Completions so K3-style models keep added tools out of the top-level list.
|
|
||||||
test.each(["k3", "k3-256k", "kimi-for-coding"] as const)("anchors additions for Kimi Coding %s", async (modelId) => {
|
|
||||||
const payload = await capturePayload<KimiPayload>(getModel("kimi-coding", modelId), additionContext);
|
|
||||||
|
|
||||||
expect(payload.tools?.map((value) => value.function?.name)).toEqual(["base_tool"]);
|
|
||||||
expect(payload.messages.find((message) => message.tools)?.tools?.map((value) => value.function?.name)).toEqual([
|
|
||||||
"late_tool",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// #10720: Kimi Code Highspeed rejects tool-bearing system messages.
|
|
||||||
test("keeps Kimi Coding Highspeed additions in top-level tools", async () => {
|
|
||||||
const payload = await capturePayload<KimiPayload>(
|
|
||||||
getModel("kimi-coding", "kimi-for-coding-highspeed"),
|
|
||||||
additionContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(payload.tools?.map((value) => value.function?.name)).toEqual(["base_tool", "late_tool"]);
|
|
||||||
expect(payload.messages.some((message) => message.tools !== undefined)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("keeps Kimi K2 system text inline without dynamic tool messages", async () => {
|
test("keeps Kimi K2 system text inline without dynamic tool messages", async () => {
|
||||||
const model: Model<"openai-completions"> = {
|
const model: Model<"openai-completions"> = {
|
||||||
...modelBase,
|
...modelBase,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue