UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

226 lines (225 loc) 9.13 kB
import { GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS, cleanSchemaForGemini, cleanSchemaForLlamacppGbnf, findLlamacppGbnfSchemaViolations, findOpenAIStrictSchemaViolations, normalizeOpenAIStrictCompatSchema, stripUnsupportedSchemaKeywords } from "@openclaw/ai/internal/openai"; //#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 = []; const properties = record.properties && typeof record.properties === "object" && !Array.isArray(record.properties) ? record.properties : void 0; if (properties) for (const [key, value] of Object.entries(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; } /** * 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 ctx.tools.flatMap((tool, toolIndex) => { const violations = findUnsupportedSchemaKeywords(tool.parameters, `${tool.name}.parameters`, GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS); if (violations.length === 0) return []; return [{ toolName: tool.name, toolIndex, violations }]; }); } /** Rewrites tool schemas into the JSON Schema subset accepted by llama.cpp GBNF. */ function normalizeLlamacppGbnfToolSchemas(ctx) { return ctx.tools.map((tool) => { if (!tool.parameters || typeof tool.parameters !== "object") return tool; const parameters = cleanSchemaForLlamacppGbnf(tool.parameters); return parameters === tool.parameters ? tool : { ...tool, parameters }; }); } /** Reports tool-schema constraints that llama.cpp GBNF cannot compile. */ function inspectLlamacppGbnfToolSchemas(ctx) { return ctx.tools.flatMap((tool, toolIndex) => { const violations = findLlamacppGbnfSchemaViolations(tool.parameters, `${tool.name}.parameters`); return violations.length > 0 ? [{ toolName: tool.name, toolIndex, violations }] : []; }); } /** * 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) return { ...tool, parameters: normalizeOpenAIStrictCompatSchema({}) }; if (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 (!schema || typeof schema !== "object" || Array.isArray(schema)) return false; const record = schema; if (record.type === "null") return true; if (Array.isArray(record.type) && record.type.length === 1 && record.type[0] === "null") return true; if ("const" in record && record.const === null) return true; return Array.isArray(record.enum) && record.enum.length === 1 && record.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 = false; const normalized = {}; for (const [key, value] of Object.entries(record)) { if (key === "anyOf" || key === "oneOf") { if (key === unionKey) { changed = true; continue; } } const next = normalizeDeepSeekSchema(value); normalized[key] = next; changed ||= next !== value; } 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; if (nonNullVariants.length > 1 && nonNullVariants.every((entry) => isStringConstVariant(entry))) { const enumValues = nonNullVariants.map((entry) => entry.const); const merged = { ...normalized, type: "string", enum: enumValues }; if (hasNullVariant) merged.nullable = true; return merged; } const selected = nonNullVariants[0] ?? normalizedVariants[0]; if (!selected || typeof selected !== "object" || Array.isArray(selected)) return normalized; const merged = { ...selected, ...normalized }; if (hasNullVariant) merged.nullable = true; return merged; } function isStringConstVariant(entry) { if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false; return typeof entry.const === "string"; } /** * Rewrites DeepSeek-incompatible union schemas into the closest accepted shape. */ function normalizeDeepSeekToolSchemas(ctx) { return ctx.tools.map((tool) => { if (!tool.parameters || typeof tool.parameters !== "object") return tool; const parameters = normalizeDeepSeekSchema(tool.parameters); return parameters === tool.parameters ? tool : { ...tool, parameters }; }); } /** * Reports DeepSeek-incompatible union schema paths without mutating tool definitions. */ function inspectDeepSeekToolSchemas(ctx) { return ctx.tools.flatMap((tool, toolIndex) => { const violations = findUnsupportedSchemaKeywords(tool.parameters, `${tool.name}.parameters`, DEEPSEEK_UNSUPPORTED_SCHEMA_KEYWORDS); if (violations.length === 0) return []; return [{ toolName: tool.name, toolIndex, violations }]; }); } /** * 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 };