86 lines
3 KiB
TypeScript
86 lines
3 KiB
TypeScript
import type { JsonSchema, ModelToolSchemaCompatibility } from "../../schema"
|
|
import { isRecord } from "../../utils/record"
|
|
import { GeminiToolSchema } from "./gemini-tool-schema"
|
|
|
|
const removeNullSchemas = (value: unknown): unknown => {
|
|
if (Array.isArray(value)) return value.map(removeNullSchemas)
|
|
if (!isRecord(value)) return value
|
|
const fields = Object.fromEntries(
|
|
Object.entries(value)
|
|
.filter(([key]) => key !== "anyOf")
|
|
.map(([key, field]) => [key, removeNullSchemas(field)]),
|
|
)
|
|
if (!Array.isArray(value.anyOf)) return fields
|
|
const variants = value.anyOf.filter((variant) => !isRecord(variant) || variant.type !== "null").map(removeNullSchemas)
|
|
if (variants.length === 1 && isRecord(variants[0])) return { ...fields, ...variants[0] }
|
|
return { ...fields, anyOf: variants }
|
|
}
|
|
|
|
const tupleItemsSchema = (items: ReadonlyArray<unknown>) => {
|
|
const projected = items.map(moonshotNode)
|
|
if (projected.length === 0) return {}
|
|
if (projected.length === 1) return projected[0]
|
|
return { anyOf: projected }
|
|
}
|
|
|
|
const moonshotNode = (schema: unknown): unknown => {
|
|
if (Array.isArray(schema)) return schema.map(moonshotNode)
|
|
if (!isRecord(schema)) return schema
|
|
if (typeof schema.$ref === "string") return { $ref: schema.$ref }
|
|
return Object.fromEntries(
|
|
Object.entries(schema).flatMap(([key, value]) => {
|
|
if (key === "items" && Array.isArray(value)) return [[key, tupleItemsSchema(value)]]
|
|
if (key === "prefixItems") {
|
|
if ("items" in schema) return []
|
|
return [["items", tupleItemsSchema(Array.isArray(value) ? value : [])]]
|
|
}
|
|
if (key === "unevaluatedItems") return []
|
|
return [[key, moonshotNode(value)]]
|
|
}),
|
|
)
|
|
}
|
|
|
|
const moonshot = (schema: JsonSchema): JsonSchema => {
|
|
const projected = moonshotNode(schema)
|
|
return isRecord(projected) ? projected : {}
|
|
}
|
|
|
|
const openAI = (schema: JsonSchema): JsonSchema => {
|
|
const variants = Array.isArray(schema.anyOf) ? schema.anyOf.filter(isRecord) : []
|
|
const flattened =
|
|
variants.length === 0
|
|
? { ...schema, type: "object" }
|
|
: {
|
|
...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "anyOf")),
|
|
type: "object",
|
|
properties: variants.reduce(
|
|
(properties, variant) => ({ ...(isRecord(variant.properties) ? variant.properties : {}), ...properties }),
|
|
{},
|
|
),
|
|
additionalProperties: false,
|
|
}
|
|
const normalized = removeNullSchemas(flattened)
|
|
return isRecord(normalized) ? normalized : { type: "object" }
|
|
}
|
|
|
|
const gemini = (schema: JsonSchema): JsonSchema => GeminiToolSchema.convert(schema) ?? {}
|
|
|
|
const modelCompatibility = (
|
|
schema: JsonSchema,
|
|
compatibility: ModelToolSchemaCompatibility | undefined,
|
|
): JsonSchema => {
|
|
if (compatibility === undefined) return schema
|
|
switch (compatibility) {
|
|
case "gemini":
|
|
return gemini(schema)
|
|
case "moonshot":
|
|
return moonshot(schema)
|
|
}
|
|
}
|
|
|
|
export const ToolSchemaProjection = {
|
|
gemini,
|
|
modelCompatibility,
|
|
moonshot,
|
|
openAI,
|
|
} as const
|