openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
322 lines (321 loc) • 12.6 kB
JavaScript
import { n as cleanSchemaForGemini, t as GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS } from "./clean-for-gemini-B-ohtktB.js";
//#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 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 normalizeOpenAIStrictCompatSchema(schema) {
return normalizeOpenAIStrictCompatSchemaRecursive(schema, { promoteEmptyObject: true });
}
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));
}
if (provider === "openai") 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);
}
const OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS = new Set([
"$defs",
"definitions",
"dependentSchemas",
"patternProperties",
"properties"
]);
const OPENAI_STRICT_COMPAT_SCHEMA_NESTED_KEYS = new Set([
"additionalProperties",
"allOf",
"anyOf",
"contains",
"else",
"if",
"items",
"not",
"oneOf",
"prefixItems",
"propertyNames",
"then",
"unevaluatedItems",
"unevaluatedProperties"
]);
function normalizeOpenAIStrictCompatSchemaMap(schema) {
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
let changed = false;
const normalized = {};
for (const [key, value] of Object.entries(schema)) {
const next = normalizeOpenAIStrictCompatSchemaRecursive(value, { promoteEmptyObject: false });
normalized[key] = next;
changed ||= next !== value;
}
return changed ? normalized : schema;
}
function normalizeOpenAIStrictCompatSchemaRecursive(schema, options) {
if (Array.isArray(schema)) {
let changed = false;
const normalized = schema.map((entry) => {
const next = normalizeOpenAIStrictCompatSchemaRecursive(entry, { promoteEmptyObject: false });
changed ||= next !== entry;
return next;
});
return changed ? normalized : schema;
}
if (!schema || typeof schema !== "object") return schema;
const record = schema;
let changed = false;
const normalized = {};
for (const [key, value] of Object.entries(record)) {
const next = OPENAI_STRICT_COMPAT_SCHEMA_MAP_KEYS.has(key) ? normalizeOpenAIStrictCompatSchemaMap(value) : OPENAI_STRICT_COMPAT_SCHEMA_NESTED_KEYS.has(key) ? normalizeOpenAIStrictCompatSchemaRecursive(value, { promoteEmptyObject: false }) : value;
normalized[key] = next;
changed ||= next !== value;
}
if (Object.keys(normalized).length === 0) {
if (!options.promoteEmptyObject) return schema;
return {
type: "object",
properties: {},
required: [],
additionalProperties: false
};
}
if (!("type" in normalized) && (normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) || Array.isArray(normalized.required))) {
normalized.type = "object";
changed = true;
}
if (normalized.type === "object" && !("properties" in normalized)) {
normalized.properties = {};
changed = true;
}
const hasEmptyProperties = normalized.properties && typeof normalized.properties === "object" && !Array.isArray(normalized.properties) && Object.keys(normalized.properties).length === 0;
if (normalized.type === "object" && !Array.isArray(normalized.required) && hasEmptyProperties) {
normalized.required = [];
changed = true;
}
if (normalized.type === "object" && hasEmptyProperties && !("additionalProperties" in normalized)) {
normalized.additionalProperties = false;
changed = true;
}
return changed ? normalized : schema;
}
/**
* Finds schema paths that violate OpenAI strict tool-schema requirements.
*/
function findOpenAIStrictSchemaViolations(schema, path, options) {
if (Array.isArray(schema)) {
if (options?.requireObjectRoot) return [`${path}.type`];
return schema.flatMap((item, index) => findOpenAIStrictSchemaViolations(item, `${path}[${index}]`));
}
if (!schema || typeof schema !== "object") {
if (options?.requireObjectRoot) return [`${path}.type`];
return [];
}
const record = schema;
const violations = [];
for (const key of [
"anyOf",
"oneOf",
"allOf"
]) if (Array.isArray(record[key])) violations.push(`${path}.${key}`);
if (Array.isArray(record.type)) violations.push(`${path}.type`);
const properties = record.properties && typeof record.properties === "object" && !Array.isArray(record.properties) ? record.properties : void 0;
if (record.type === "object") {
if (record.additionalProperties !== false) violations.push(`${path}.additionalProperties`);
const required = Array.isArray(record.required) ? record.required.filter((entry) => typeof entry === "string") : void 0;
if (!required) violations.push(`${path}.required`);
else if (properties) {
const requiredSet = new Set(required);
for (const key of Object.keys(properties)) if (!requiredSet.has(key)) violations.push(`${path}.required.${key}`);
}
}
if (properties) for (const [key, value] of Object.entries(properties)) violations.push(...findOpenAIStrictSchemaViolations(value, `${path}.properties.${key}`));
for (const [key, value] of Object.entries(record)) {
if (key === "properties") continue;
if (value && typeof value === "object") violations.push(...findOpenAIStrictSchemaViolations(value, `${path}.${key}`));
}
return violations;
}
/**
* 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 = 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 "openai": return {
normalizeToolSchemas: normalizeOpenAIToolSchemas,
inspectToolSchemas: inspectOpenAIToolSchemas
};
}
throw new Error("Unsupported provider tool compatibility family");
}
//#endregion
export { inspectDeepSeekToolSchemas as a, normalizeDeepSeekToolSchemas as c, findUnsupportedSchemaKeywords as i, normalizeGeminiToolSchemas as l, buildProviderToolCompatFamilyHooks as n, inspectGeminiToolSchemas as o, findOpenAIStrictSchemaViolations as r, inspectOpenAIToolSchemas as s, DEEPSEEK_UNSUPPORTED_SCHEMA_KEYWORDS as t, normalizeOpenAIToolSchemas as u };