UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

252 lines (251 loc) • 11.2 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { n as readLiteralSchemaValues, r as schemaAnnotationsOnly, t as mergeLiteralSchemas } from "./json-schema-literals-CTM-h34e.mjs"; import { isDeepStrictEqual } from "node:util"; import { GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS, cleanSchemaForGemini, cleanSchemaForLlamacppGbnf, findLlamacppGbnfSchemaViolations, findOpenAIStrictSchemaViolations, normalizeOpenAIStrictCompatSchema, stripUnsupportedSchemaKeywords } from "@openclaw/ai/internal/tool-schema"; //#region src/plugin-sdk/provider-tools.ts /** * Finds unsupported JSON-schema keywords and reports their nested schema paths. */ function findUnsupportedSchemaKeywords(schema, path, unsupportedKeywords) { if (!schema || typeof schema !== "object") return []; if (Array.isArray(schema)) return schema.flatMap((item, index) => findUnsupportedSchemaKeywords(item, `${path}[${index}]`, unsupportedKeywords)); const record = schema; const violations = []; if (isRecord(record.properties)) for (const [key, value] of Object.entries(record.properties)) violations.push(...findUnsupportedSchemaKeywords(value, `${path}.properties.${key}`, unsupportedKeywords)); for (const [key, value] of Object.entries(record)) { if (key === "properties") continue; if (unsupportedKeywords.has(key)) violations.push(`${path}.${key}`); if (value && typeof value === "object") violations.push(...findUnsupportedSchemaKeywords(value, `${path}.${key}`, unsupportedKeywords)); } return violations; } function normalizeToolSchemasIfChanged(ctx, normalizeSchema) { return ctx.tools.map((tool) => { if (!tool.parameters || typeof tool.parameters !== "object") return tool; const parameters = normalizeSchema(tool.parameters); return parameters === tool.parameters ? tool : { ...tool, parameters }; }); } function inspectToolSchemas(ctx, inspect) { return ctx.tools.flatMap((tool, toolIndex) => { const violations = inspect(tool.parameters, `${tool.name}.parameters`); return violations.length > 0 ? [{ toolName: tool.name, toolIndex, violations }] : []; }); } /** * Rewrites tool schemas into Gemini-compatible JSON schema before provider dispatch. */ function normalizeGeminiToolSchemas(ctx) { return ctx.tools.map((tool) => { if (!tool.parameters || typeof tool.parameters !== "object") return tool; return { ...tool, parameters: cleanSchemaForGemini(tool.parameters) }; }); } /** * Reports Gemini-incompatible schema keywords without mutating tool definitions. */ function inspectGeminiToolSchemas(ctx) { return inspectToolSchemas(ctx, (schema, path) => findUnsupportedSchemaKeywords(schema, path, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS)); } /** Rewrites tool schemas into the JSON Schema subset accepted by llama.cpp GBNF. */ function normalizeLlamacppGbnfToolSchemas(ctx) { return normalizeToolSchemasIfChanged(ctx, cleanSchemaForLlamacppGbnf); } /** Reports tool-schema constraints that llama.cpp GBNF cannot compile. */ function inspectLlamacppGbnfToolSchemas(ctx) { return inspectToolSchemas(ctx, findLlamacppGbnfSchemaViolations); } /** * Rewrites OpenAI-native tool schemas to satisfy strict object-schema requirements. */ function normalizeOpenAIToolSchemas(ctx) { if (!shouldApplyOpenAIToolCompat(ctx)) return ctx.tools; return ctx.tools.map((tool) => { if (tool.parameters != null && typeof tool.parameters !== "object") return tool; return { ...tool, parameters: normalizeOpenAIStrictCompatSchema(tool.parameters ?? {}) }; }); } function shouldApplyOpenAIToolCompat(ctx) { const provider = (ctx.model?.provider ?? ctx.provider ?? "").trim().toLowerCase(); const api = (ctx.model?.api ?? ctx.modelApi ?? "").trim().toLowerCase(); const baseUrl = (ctx.model?.baseUrl ?? "").trim().toLowerCase(); if (provider === "openai") { if (api === "openai-responses") return !baseUrl || isOpenAIResponsesBaseUrl(baseUrl); return api === "openai-chatgpt-responses" && (!baseUrl || isOpenAIResponsesBaseUrl(baseUrl) || isOpenAICodexBaseUrl(baseUrl)); } return false; } function isOpenAIResponsesBaseUrl(baseUrl) { return /^https:\/\/api\.openai\.com(?:\/v1)?(?:\/|$)/i.test(baseUrl); } function isOpenAICodexBaseUrl(baseUrl) { return /^https:\/\/chatgpt\.com\/backend-api(?:\/|$)/i.test(baseUrl); } /** * Reports OpenAI strict-schema diagnostics for transports that enforce them before dispatch. */ function inspectOpenAIToolSchemas(ctx) { if (!shouldApplyOpenAIToolCompat(ctx)) return []; return []; } /** * DeepSeek rejects union keywords in tool schemas. */ const DEEPSEEK_UNSUPPORTED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set(["anyOf", "oneOf"]); function isNullSchemaVariant(schema) { if (!isRecord(schema)) return false; return schema.type === "null" || Array.isArray(schema.type) && schema.type.length === 1 && schema.type[0] === "null" || schema.const === null || Array.isArray(schema.enum) && schema.enum.length === 1 && schema.enum[0] === null; } function normalizeDeepSeekSchema(schema) { if (Array.isArray(schema)) { let changed = false; const normalized = schema.map((entry) => { const next = normalizeDeepSeekSchema(entry); changed ||= next !== entry; return next; }); return changed ? normalized : schema; } if (!schema || typeof schema !== "object") return schema; const record = schema; const unionKey = Array.isArray(record.anyOf) ? "anyOf" : Array.isArray(record.oneOf) ? "oneOf" : void 0; let changed = unionKey !== void 0; const normalized = Object.fromEntries(Object.entries(record).filter(([key]) => key !== unionKey).map(([key, value]) => { const next = normalizeDeepSeekSchema(value); changed ||= next !== value; return [key, next]; })); if (!unionKey) return changed ? normalized : schema; const normalizedVariants = record[unionKey].map((entry) => normalizeDeepSeekSchema(entry)); const nonNullVariants = normalizedVariants.filter((entry) => !isNullSchemaVariant(entry)); const hasNullVariant = nonNullVariants.length < normalizedVariants.length || nonNullVariants.some((entry) => { if (!isObjectSchemaVariant(entry) || !Array.isArray(entry.type) || !entry.type.includes("null")) return false; const literals = readLiteralSchemaValues(entry); return literals === void 0 || literals.includes(null); }); if (nonNullVariants.length > 1 && nonNullVariants.every(isStringConstVariant)) return { ...normalized, type: "string", enum: nonNullVariants.map((entry) => entry.const), ...hasNullVariant ? { nullable: true } : {} }; const selected = nonNullVariants.length > 1 && nonNullVariants.every(isObjectSchemaVariant) ? flattenObjectVariants(nonNullVariants) ?? nonNullVariants[0] : nonNullVariants[0] ?? normalizedVariants[0]; if (!isRecord(selected)) return normalized; const nullableObject = hasNullVariant && nonNullVariants.length > 0 && nonNullVariants.every(isObjectSchemaVariant); let selectedSchema = selected; if (nullableObject) { selectedSchema = { ...selected, type: ["object", "null"] }; const literals = readLiteralSchemaValues(selected); if (literals) { selectedSchema.enum = literals.includes(null) ? literals : [...literals, null]; delete selectedSchema.const; } } const merged = { ...selectedSchema, ...normalized }; if (hasNullVariant && !nullableObject) merged.nullable = true; return merged; } function isStringConstVariant(entry) { return isRecord(entry) && typeof entry.const === "string"; } function isObjectSchemaVariant(entry) { return isRecord(entry) && (entry.type === "object" || Array.isArray(entry.type) && entry.type.includes("object") && entry.type.every((type) => type === "object" || type === "null")); } function flattenObjectVariants(variants) { const properties = Object.create(null); let required; for (const variant of variants) { const variantProperties = variant.properties; if (!isRecord(variantProperties)) return; for (const [key, value] of Object.entries(variantProperties)) { if (!Object.hasOwn(properties, key)) { properties[key] = value; continue; } const existing = properties[key]; if (isDeepStrictEqual(existing, value)) continue; const pooled = isRecord(existing) && isRecord(value) ? mergeLiteralSchemas(existing, value) : void 0; if (pooled) properties[key] = pooled; } const variantRequired = Array.isArray(variant.required) ? variant.required.filter((key) => typeof key === "string") : []; required = required === void 0 ? variantRequired : required.filter((key) => variantRequired.includes(key)); } for (const key of Object.keys(properties)) if (variants.some((variant) => acceptsUndeclaredKey(variant, key))) properties[key] = schemaAnnotationsOnly(properties[key]); const flattened = { type: "object", properties }; if (required && required.length > 0) flattened.required = required; return flattened; } function acceptsUndeclaredKey(variant, key) { const declared = variant.properties; if (isRecord(declared) && Object.hasOwn(declared, key)) return false; if (variant.additionalProperties !== false) return true; const patternProperties = variant.patternProperties; if (!isRecord(patternProperties)) return false; return Object.keys(patternProperties).some((pattern) => { try { return new RegExp(pattern).test(key); } catch { return false; } }); } /** * Rewrites DeepSeek-incompatible union schemas into the closest accepted shape. */ function normalizeDeepSeekToolSchemas(ctx) { return normalizeToolSchemasIfChanged(ctx, normalizeDeepSeekSchema); } /** * Reports DeepSeek-incompatible union schema paths without mutating tool definitions. */ function inspectDeepSeekToolSchemas(ctx) { return inspectToolSchemas(ctx, (schema, path) => findUnsupportedSchemaKeywords(schema, path, DEEPSEEK_UNSUPPORTED_SCHEMA_KEYWORDS)); } /** * Returns the normalizer and inspector pair for a provider tool-schema compatibility family. */ function buildProviderToolCompatFamilyHooks(family) { switch (family) { case "deepseek": return { normalizeToolSchemas: normalizeDeepSeekToolSchemas, inspectToolSchemas: inspectDeepSeekToolSchemas }; case "gemini": return { normalizeToolSchemas: normalizeGeminiToolSchemas, inspectToolSchemas: inspectGeminiToolSchemas }; case "llamacpp-gbnf": return { normalizeToolSchemas: normalizeLlamacppGbnfToolSchemas, inspectToolSchemas: inspectLlamacppGbnfToolSchemas }; case "openai": return { normalizeToolSchemas: normalizeOpenAIToolSchemas, inspectToolSchemas: inspectOpenAIToolSchemas }; } throw new Error("Unsupported provider tool compatibility family"); } //#endregion export { normalizeOpenAIToolSchemas as _, cleanSchemaForLlamacppGbnf as a, findUnsupportedSchemaKeywords as c, inspectLlamacppGbnfToolSchemas as d, inspectOpenAIToolSchemas as f, normalizeOpenAIStrictCompatSchema as g, normalizeLlamacppGbnfToolSchemas as h, cleanSchemaForGemini as i, inspectDeepSeekToolSchemas as l, normalizeGeminiToolSchemas as m, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS as n, findLlamacppGbnfSchemaViolations as o, normalizeDeepSeekToolSchemas as p, buildProviderToolCompatFamilyHooks as r, findOpenAIStrictSchemaViolations as s, DEEPSEEK_UNSUPPORTED_SCHEMA_KEYWORDS as t, inspectGeminiToolSchemas as u, stripUnsupportedSchemaKeywords as v };