From f60c7791c191f17aa949e17c9e89112e788b07d1 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 18 Jul 2026 06:27:36 +0200 Subject: [PATCH] fix(catalog): used Moonshot MFJS schema for Kimi models on all hosts - Enable `moonshot-mfjs` tool schema flavor for Kimi-family ids on any host, not just native endpoints, since proxies forward schemas verbatim to Moonshot's validator. --- packages/ai/src/providers/openai-responses.ts | 11 ++++++++++- packages/catalog/CHANGELOG.md | 4 ++++ packages/catalog/src/compat/openai.ts | 8 +++++++- packages/catalog/src/types.ts | 9 ++++++--- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index 053bc06b3..f38cfd207 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -38,6 +38,7 @@ import { adaptSchemaForStrict, findStrictToolSchemaViolation, NO_STRICT, + normalizeSchemaForMoonshot, sanitizeSchemaForOpenAIResponses, toolWireSchema, } from "../utils/schema"; @@ -999,7 +1000,15 @@ export function convertTools( } const strict = !NO_STRICT && strictMode && tool.strict !== false; const baseParameters = toolWireSchema(tool); - const responseParameters = sanitizeSchemaForOpenAIResponses(baseParameters); + // MFJS must run AFTER the Responses sanitizer: the sanitizer normalizes + // `{}` → `true` (issue #1179), and Moonshot's validator rejects boolean + // subschemas ("property schema … must be an object"), so the Moonshot + // pass re-coerces them last. + const sanitized = sanitizeSchemaForOpenAIResponses(baseParameters); + const responseParameters = + model.compat.toolSchemaFlavor === "moonshot-mfjs" + ? (normalizeSchemaForMoonshot(sanitized) as Record) + : sanitized; const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(responseParameters, strict); // Quarantine a tool whose emitted schema carries a provider-rejecting // enum/const-vs-type contradiction: dropping just that tool keeps the rest diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 0b188935d..4d1c32de6 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Kimi-family models now use MFJS tool schema on all hosts, including proxies like OpenRouter that forward schemas to Moonshot + ## [17.0.3] - 2026-07-17 ### Fixed diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index 896bf22fb..ab84bd379 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -532,7 +532,10 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv supportsStrictMode: detectStrictModeSupport(provider, baseUrl), extraBody: isDirectDeepseekReasoning ? { thinking: { type: "enabled" } } : undefined, toolStrictMode: isCerebras ? "all_strict" : "mixed", - toolSchemaFlavor: isMoonshotNative ? "moonshot-mfjs" : undefined, + // Kimi-family ids trigger MFJS on any host, not just native base URLs: + // proxies (OpenRouter, custom gateways) forward `tools.function.parameters` + // to Moonshot verbatim, which 400s on non-MFJS constructs. + toolSchemaFlavor: isMoonshotNative || isKimiModel ? "moonshot-mfjs" : undefined, streamIdleTimeoutMs, stripDeepseekSpecialTokens: isDeepseekModelIdOrName(spec.id) && (provider === "nvidia" || provider === "deepseek"), @@ -658,6 +661,9 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol openRouterRouting: undefined, isOpenRouterHost: isOpenRouter, wireModelIdMode: isOpenRouter ? "openrouter" : "raw", + // Mirrors buildOpenAICompat: Kimi behind a Responses-capable proxy still + // lands on Moonshot's MFJS validator. + toolSchemaFlavor: isKimiModel ? "moonshot-mfjs" : undefined, alwaysSendMaxTokens: spec.id ? isKimiModelId(spec.id) : false, enableGeminiThinkingLoopGuard: modelFamilyToken(spec.id ?? "") === "gemini", supportsObfuscationOptOut: isOpenAIUrl || spec.provider === "openai", diff --git a/packages/catalog/src/types.ts b/packages/catalog/src/types.ts index 92743a8a1..f8d3cedb1 100644 --- a/packages/catalog/src/types.ts +++ b/packages/catalog/src/types.ts @@ -314,8 +314,10 @@ export interface OpenAICompat { * normalization (collapse `const`→`enum`, infer `type` on bare enums, strip * unsupported validators/`prefixItems`) because Moonshot/Kimi native hosts * reject standard JSON Schema constructs with HTTP 400. Default: - * auto-detected (`"moonshot-mfjs"` on api.moonshot.ai / api.kimi.com). Set - * `"none"` to opt a custom Moonshot-compatible host out. + * auto-detected — Moonshot native hosts (api.moonshot.ai / api.kimi.com) + * and Kimi-family model ids on any host, since proxies (OpenRouter, custom + * gateways) forward schemas to Moonshot verbatim. Set `"none"` to opt a + * host out. */ toolSchemaFlavor?: "moonshot-mfjs" | "none"; /** @@ -514,6 +516,8 @@ export interface ResolvedOpenAISharedCompat { openRouterRouting?: OpenAICompat["openRouterRouting"]; /** Provider-specific wire model-id transform applied to the base id. */ wireModelIdMode: "raw" | "firepass" | "fireworks" | "openrouter"; + /** See {@link OpenAICompat.toolSchemaFlavor}. Read by both wire paths when converting tools. */ + toolSchemaFlavor?: OpenAICompat["toolSchemaFlavor"]; } /** @@ -584,7 +588,6 @@ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & thinkingKeep?: OpenAICompat["thinkingKeep"]; streamIdleTimeoutMs?: number; toolStrictMode: ResolvedToolStrictMode; - toolSchemaFlavor?: OpenAICompat["toolSchemaFlavor"]; /** The model sits behind Vercel AI Gateway. */ isVercelGatewayHost: boolean; dropThinkingWhenReasoningEffort: boolean;