mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-30 22:00:53 +08:00
fix: sanitize tools for moonshot (#24730)
This commit is contained in:
@@ -1089,6 +1089,21 @@ export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JS
|
||||
}
|
||||
*/
|
||||
|
||||
if (model.providerID === "moonshotai" || model.api.id.toLowerCase().includes("kimi")) {
|
||||
const sanitizeMoonshot = (obj: unknown): unknown => {
|
||||
if (obj === null || typeof obj !== "object") return obj
|
||||
if (Array.isArray(obj)) return obj.map(sanitizeMoonshot)
|
||||
// Moonshot expands $ref before validation and rejects sibling keywords like description on the same node.
|
||||
if ("$ref" in obj && typeof obj.$ref === "string") return { $ref: obj.$ref }
|
||||
const result = Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, sanitizeMoonshot(value)]))
|
||||
// MFJS does not support tuple-style `items` arrays; it requires one schema object for all array items.
|
||||
if (Array.isArray(result.items)) result.items = result.items[0] ?? {}
|
||||
return result
|
||||
}
|
||||
|
||||
schema = sanitizeMoonshot(schema) as JSONSchema.BaseSchema | JSONSchema7
|
||||
}
|
||||
|
||||
// Convert integer enums to string enums for Google/Gemini
|
||||
if (model.providerID === "google" || model.api.id.includes("gemini")) {
|
||||
const isPlainObject = (node: unknown): node is Record<string, any> =>
|
||||
|
||||
@@ -855,6 +855,160 @@ describe("ProviderTransform.schema - gemini non-object properties removal", () =
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.schema - moonshot $ref siblings", () => {
|
||||
const moonshotModel = {
|
||||
providerID: "moonshotai",
|
||||
api: {
|
||||
id: "kimi-k2",
|
||||
},
|
||||
} as any
|
||||
|
||||
test("removes sibling descriptions from referenced tool parameter schemas", () => {
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
deviceType: {
|
||||
description: "Optional. The type of device that captured the screenshot, e.g. mobile or desktop.",
|
||||
enum: ["DEVICE_TYPE_UNSPECIFIED", "MOBILE", "DESKTOP", "TABLET", "AGNOSTIC"],
|
||||
type: "string",
|
||||
},
|
||||
modelId: {
|
||||
description: "Optional. The model to use for generation.",
|
||||
enum: ["MODEL_ID_UNSPECIFIED", "GEMINI_3_PRO", "GEMINI_3_FLASH", "GEMINI_3_1_PRO"],
|
||||
type: "string",
|
||||
},
|
||||
projectId: {
|
||||
description: "Required. The project ID of screens to generate variants for.",
|
||||
type: "string",
|
||||
},
|
||||
prompt: {
|
||||
description: "Required. The input text used to generate the variants.",
|
||||
type: "string",
|
||||
},
|
||||
selectedScreenIds: {
|
||||
description: "Required. The screen ids of screen to generate variants for.",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
type: "array",
|
||||
},
|
||||
variantOptions: {
|
||||
$ref: "#/$defs/VariantOptions",
|
||||
description:
|
||||
"Required. The variant options for generation, including the number of variants, creative range, and aspects to focus on.",
|
||||
},
|
||||
},
|
||||
required: ["projectId", "selectedScreenIds", "prompt", "variantOptions"],
|
||||
$defs: {
|
||||
VariantOptions: {
|
||||
description:
|
||||
"Configuration options for design variant generation. This message captures all parameters used to generate variants, allowing the configuration to be stored, replayed, or analyzed.",
|
||||
properties: {
|
||||
aspects: {
|
||||
description: "Optional. Specific aspects to focus on. If empty, all aspects may be varied.",
|
||||
items: {
|
||||
enum: [
|
||||
"VARIANT_ASPECT_UNSPECIFIED",
|
||||
"LAYOUT",
|
||||
"COLOR_SCHEME",
|
||||
"IMAGES",
|
||||
"TEXT_FONT",
|
||||
"TEXT_CONTENT",
|
||||
],
|
||||
type: "string",
|
||||
},
|
||||
type: "array",
|
||||
},
|
||||
creativeRange: {
|
||||
description: "Optional. Creative range for variations. Default: EXPLORE",
|
||||
enum: ["CREATIVE_RANGE_UNSPECIFIED", "REFINE", "EXPLORE", "REIMAGINE"],
|
||||
type: "string",
|
||||
},
|
||||
variantCount: {
|
||||
description: "Optional. Number of variants to generate (1-5). Default: 3",
|
||||
format: "int32",
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
description: "Request message for GenerateVariants.",
|
||||
additionalProperties: false,
|
||||
} as any
|
||||
|
||||
const result = ProviderTransform.schema(moonshotModel, schema) as any
|
||||
|
||||
expect(result.properties.variantOptions).toEqual({
|
||||
$ref: "#/$defs/VariantOptions",
|
||||
})
|
||||
expect(result.$defs.VariantOptions.description).toBe(schema.$defs.VariantOptions.description)
|
||||
})
|
||||
|
||||
test("also runs for kimi models outside the moonshot provider", () => {
|
||||
const result = ProviderTransform.schema(
|
||||
{
|
||||
providerID: "openrouter",
|
||||
name: "Kimi K2",
|
||||
api: {
|
||||
id: "moonshotai/kimi-k2",
|
||||
},
|
||||
} as any,
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
value: {
|
||||
$ref: "#/$defs/Value",
|
||||
description: "Moonshot rejects this sibling after ref expansion.",
|
||||
},
|
||||
},
|
||||
$defs: {
|
||||
Value: {
|
||||
description: "Referenced schema description stays here.",
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
) as any
|
||||
|
||||
expect(result.properties.value).toEqual({
|
||||
$ref: "#/$defs/Value",
|
||||
})
|
||||
})
|
||||
|
||||
test("converts tuple-style array items to a single item schema", () => {
|
||||
const result = ProviderTransform.schema(
|
||||
moonshotModel,
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
codeSpec: {
|
||||
type: "object",
|
||||
properties: {
|
||||
accessibility: {
|
||||
type: "object",
|
||||
properties: {
|
||||
renderedSize: {
|
||||
description: "Rendered size [width, height] in px",
|
||||
type: "array",
|
||||
items: [{ type: "number" }, { type: "number" }],
|
||||
minItems: 2,
|
||||
maxItems: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
) as any
|
||||
|
||||
expect(result.properties.codeSpec.properties.accessibility.properties.renderedSize.items).toEqual({
|
||||
type: "number",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.message - DeepSeek reasoning content", () => {
|
||||
test("DeepSeek with tool calls includes reasoning_content in providerOptions", () => {
|
||||
const msgs = [
|
||||
|
||||
Reference in New Issue
Block a user