mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
964c8c84c1
* refactor: consolidate coercion ownership Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries. The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries. * fix: guard integer option ownership Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation. * fix: keep integer helpers on numeric facade Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract. * fix: point numeric coercion to number runtime Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
142 lines
4.0 KiB
TypeScript
142 lines
4.0 KiB
TypeScript
import type {
|
|
AnyAgentTool,
|
|
ProviderNormalizeToolSchemasContext,
|
|
ProviderToolSchemaDiagnostic,
|
|
} from "openclaw/plugin-sdk/plugin-entry";
|
|
import { findUnsupportedSchemaKeywords } from "openclaw/plugin-sdk/provider-tools";
|
|
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
|
|
const PERPLEXITY_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
|
|
"patternProperties",
|
|
"additionalProperties",
|
|
]);
|
|
const SCHEMA_MAP_KEYS = new Set([
|
|
"properties",
|
|
"$defs",
|
|
"definitions",
|
|
"dependentSchemas",
|
|
// Legacy `dependencies` mixes schema values with property-name arrays; the
|
|
// walker leaves non-record values untouched, so both forms stay valid.
|
|
"dependencies",
|
|
]);
|
|
const SCHEMA_VALUE_KEYS = new Set([
|
|
"items",
|
|
"additionalItems",
|
|
"prefixItems",
|
|
"anyOf",
|
|
"oneOf",
|
|
"allOf",
|
|
"then",
|
|
"else",
|
|
"if",
|
|
"not",
|
|
"contains",
|
|
"propertyNames",
|
|
"unevaluatedItems",
|
|
"unevaluatedProperties",
|
|
"contentSchema",
|
|
]);
|
|
|
|
// JSON Schema allows `type` to be an array; a union containing "object" still
|
|
// admits objects, so it needs `properties` for Perplexity too.
|
|
function isObjectType(type: unknown): boolean {
|
|
return type === "object" || (Array.isArray(type) && type.includes("object"));
|
|
}
|
|
|
|
function normalizeSchemaMap(value: unknown): unknown {
|
|
const record = readRecord(value);
|
|
if (!record) {
|
|
return value;
|
|
}
|
|
return Object.fromEntries(
|
|
Object.entries(record).map(([key, schema]) => [key, normalizePerplexitySchema(schema)]),
|
|
);
|
|
}
|
|
|
|
function normalizePerplexitySchema(value: unknown): unknown {
|
|
if (Array.isArray(value)) {
|
|
return value.map(normalizePerplexitySchema);
|
|
}
|
|
const record = readRecord(value);
|
|
if (!record) {
|
|
return value;
|
|
}
|
|
const normalized: Record<string, unknown> = {};
|
|
for (const [key, child] of Object.entries(record)) {
|
|
if (PERPLEXITY_UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) {
|
|
continue;
|
|
}
|
|
normalized[key] = SCHEMA_MAP_KEYS.has(key)
|
|
? normalizeSchemaMap(child)
|
|
: SCHEMA_VALUE_KEYS.has(key)
|
|
? normalizePerplexitySchema(child)
|
|
: child;
|
|
}
|
|
if (isObjectType(normalized.type) && !("properties" in normalized)) {
|
|
normalized.properties = {};
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function findObjectSchemasMissingProperties(schema: unknown, path: string): string[] {
|
|
if (Array.isArray(schema)) {
|
|
return schema.flatMap((child, index) =>
|
|
findObjectSchemasMissingProperties(child, `${path}[${index}]`),
|
|
);
|
|
}
|
|
const record = readRecord(schema);
|
|
if (!record) {
|
|
return [];
|
|
}
|
|
const violations =
|
|
isObjectType(record.type) && !("properties" in record) ? [`${path}.properties`] : [];
|
|
for (const [key, child] of Object.entries(record)) {
|
|
if (SCHEMA_MAP_KEYS.has(key)) {
|
|
const schemas = readRecord(child);
|
|
if (schemas) {
|
|
for (const [name, nestedSchema] of Object.entries(schemas)) {
|
|
violations.push(
|
|
...findObjectSchemasMissingProperties(nestedSchema, `${path}.${key}.${name}`),
|
|
);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (SCHEMA_VALUE_KEYS.has(key)) {
|
|
violations.push(...findObjectSchemasMissingProperties(child, `${path}.${key}`));
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
export function normalizePerplexityToolSchemas(
|
|
ctx: ProviderNormalizeToolSchemasContext,
|
|
): AnyAgentTool[] {
|
|
return ctx.tools.map((tool) => {
|
|
if (!tool.parameters || typeof tool.parameters !== "object") {
|
|
return tool;
|
|
}
|
|
return {
|
|
...tool,
|
|
parameters: normalizePerplexitySchema(tool.parameters) as AnyAgentTool["parameters"],
|
|
};
|
|
});
|
|
}
|
|
|
|
export function inspectPerplexityToolSchemas(
|
|
ctx: ProviderNormalizeToolSchemasContext,
|
|
): ProviderToolSchemaDiagnostic[] {
|
|
return ctx.tools.flatMap((tool, toolIndex) => {
|
|
const path = `${tool.name}.parameters`;
|
|
const violations = [
|
|
...findUnsupportedSchemaKeywords(
|
|
tool.parameters,
|
|
path,
|
|
PERPLEXITY_UNSUPPORTED_SCHEMA_KEYWORDS,
|
|
),
|
|
...findObjectSchemasMissingProperties(tool.parameters, path),
|
|
];
|
|
return violations.length > 0 ? [{ toolName: tool.name, toolIndex, violations }] : [];
|
|
});
|
|
}
|