mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:00:43 +00:00
refactor: tighten tool schema types
This commit is contained in:
@@ -231,7 +231,7 @@ export function createOllamaWebSearchProvider(): WebSearchProviderPlugin {
|
||||
createTool: (ctx) => ({
|
||||
description:
|
||||
"Search the web using Ollama's experimental web search API. Returns titles, URLs, and snippets from the configured Ollama host.",
|
||||
parameters: OLLAMA_WEB_SEARCH_SCHEMA as unknown as Record<string, unknown>,
|
||||
parameters: OLLAMA_WEB_SEARCH_SCHEMA,
|
||||
execute: async (args) =>
|
||||
await runOllamaWebSearch({
|
||||
config: ctx.config,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
TOOL_NAME_SEPARATOR,
|
||||
} from "./pi-bundle-mcp-names.js";
|
||||
import type { BundleMcpToolRuntime, SessionMcpRuntime } from "./pi-bundle-mcp-types.js";
|
||||
import { asToolParameterSchema, type AnyAgentTool } from "./tools/common.js";
|
||||
import type { AnyAgentTool } from "./tools/common.js";
|
||||
|
||||
function toAgentToolResult(params: {
|
||||
serverName: string;
|
||||
@@ -102,7 +102,7 @@ export async function materializeBundleMcpToolsForRun(params: {
|
||||
name: safeToolName,
|
||||
label: tool.title ?? tool.toolName,
|
||||
description: tool.description || tool.fallbackDescription,
|
||||
parameters: asToolParameterSchema(tool.inputSchema),
|
||||
parameters: tool.inputSchema,
|
||||
execute: async (_toolCallId: string, input: unknown) => {
|
||||
const result = await params.runtime.callTool(tool.serverName, tool.toolName, input);
|
||||
return toAgentToolResult({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { TSchema } from "typebox";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { AnyAgentTool } from "./tools/common.js";
|
||||
|
||||
@@ -19,7 +20,7 @@ export type McpCatalogTool = {
|
||||
toolName: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
inputSchema: unknown;
|
||||
inputSchema: TSchema;
|
||||
fallbackDescription: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TSchema } from "typebox";
|
||||
import type { ModelCompatConfig } from "../config/types.models.js";
|
||||
import { stripUnsupportedSchemaKeywords } from "../plugin-sdk/provider-tools.js";
|
||||
import { resolveUnsupportedToolSchemaKeywords } from "../plugins/provider-model-compat.js";
|
||||
@@ -134,11 +135,11 @@ function isTrulyEmptySchema(schemaRecord: Record<string, unknown>): boolean {
|
||||
export function normalizeToolParameterSchema(
|
||||
schema: unknown,
|
||||
options?: { modelProvider?: string; modelId?: string; modelCompat?: ModelCompatConfig },
|
||||
): unknown {
|
||||
): TSchema {
|
||||
const schemaRecord =
|
||||
schema && typeof schema === "object" ? (schema as Record<string, unknown>) : undefined;
|
||||
if (!schemaRecord) {
|
||||
return schema;
|
||||
return schema as TSchema;
|
||||
}
|
||||
|
||||
// Provider quirks:
|
||||
@@ -155,14 +156,14 @@ export function normalizeToolParameterSchema(
|
||||
const isAnthropicProvider = normalizedProvider.includes("anthropic");
|
||||
const unsupportedToolSchemaKeywords = resolveUnsupportedToolSchemaKeywords(options?.modelCompat);
|
||||
|
||||
function applyProviderCleaning(s: unknown): unknown {
|
||||
function applyProviderCleaning(s: unknown): TSchema {
|
||||
if (isGeminiProvider && !isAnthropicProvider) {
|
||||
return cleanSchemaForGemini(s);
|
||||
}
|
||||
if (unsupportedToolSchemaKeywords.size > 0) {
|
||||
return stripUnsupportedSchemaKeywords(s, unsupportedToolSchemaKeywords);
|
||||
return stripUnsupportedSchemaKeywords(s, unsupportedToolSchemaKeywords) as TSchema;
|
||||
}
|
||||
return s;
|
||||
return s as TSchema;
|
||||
}
|
||||
|
||||
const conditionalKey = getTopLevelConditionalKey(schemaRecord);
|
||||
@@ -188,9 +189,9 @@ export function normalizeToolParameterSchema(
|
||||
if (conditionalKey === "allOf") {
|
||||
// Top-level `allOf` is not safely flattenable with the same heuristics we
|
||||
// use for unions. Keep it explicit rather than silently rewriting it.
|
||||
return schema;
|
||||
return applyProviderCleaning(schema);
|
||||
}
|
||||
return schema;
|
||||
return applyProviderCleaning(schema);
|
||||
}
|
||||
const variants = schemaRecord[flattenableVariantKey] as unknown[];
|
||||
const mergedProperties: Record<string, unknown> = {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Type } from "typebox";
|
||||
import { Type, type TSchema } from "typebox";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
cleanToolSchemaForGemini,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
normalizeToolParameters,
|
||||
} from "./pi-tools.schema.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
import { asToolParameterSchema } from "./tools/common.js";
|
||||
|
||||
describe("normalizeToolParameterSchema", () => {
|
||||
it("normalizes truly empty schemas to type:object with properties:{}", () => {
|
||||
@@ -108,12 +107,12 @@ describe("normalizeToolParameterSchema", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function makeTool(parameters: unknown): AnyAgentTool {
|
||||
function makeTool(parameters: TSchema): AnyAgentTool {
|
||||
return {
|
||||
name: "test_tool",
|
||||
label: "Test Tool",
|
||||
description: "test",
|
||||
parameters: asToolParameterSchema(parameters),
|
||||
parameters,
|
||||
execute: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type ToolParameterSchemaOptions,
|
||||
} from "./pi-tools-parameter-schema.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
import { asToolParameterSchema } from "./tools/common.js";
|
||||
|
||||
export { normalizeToolParameterSchema };
|
||||
|
||||
@@ -27,7 +26,7 @@ export function normalizeToolParameters(
|
||||
}
|
||||
return preserveToolMeta({
|
||||
...tool,
|
||||
parameters: asToolParameterSchema(normalizeToolParameterSchema(schema, options)),
|
||||
parameters: normalizeToolParameterSchema(schema, options),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Cloud Code Assist API rejects a subset of JSON Schema keywords.
|
||||
// This module scrubs/normalizes tool schemas to keep Gemini happy.
|
||||
|
||||
import type { TSchema } from "typebox";
|
||||
|
||||
// Keywords that Cloud Code Assist API rejects (not compliant with their JSON Schema subset)
|
||||
export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
|
||||
"patternProperties",
|
||||
@@ -443,14 +445,14 @@ function flattenUnionFallback(
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function cleanSchemaForGemini(schema: unknown): unknown {
|
||||
export function cleanSchemaForGemini(schema: unknown): TSchema {
|
||||
if (!schema || typeof schema !== "object") {
|
||||
return schema;
|
||||
return schema as TSchema;
|
||||
}
|
||||
if (Array.isArray(schema)) {
|
||||
return schema.map(cleanSchemaForGemini);
|
||||
return schema.map(cleanSchemaForGemini) as TSchema;
|
||||
}
|
||||
|
||||
const defs = extendSchemaDefs(undefined, schema as Record<string, unknown>);
|
||||
return cleanSchemaForGeminiWithDefs(schema, defs, undefined);
|
||||
return cleanSchemaForGeminiWithDefs(schema, defs, undefined) as TSchema;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ export type AnyAgentTool = Omit<AgentTool<TSchema, unknown>, "execute"> &
|
||||
displaySummary?: string;
|
||||
};
|
||||
|
||||
export function asToolParameterSchema(schema: unknown): TSchema {
|
||||
return schema as TSchema;
|
||||
}
|
||||
|
||||
export function asToolParamsRecord(params: unknown): Record<string, unknown> {
|
||||
return params && typeof params === "object" && !Array.isArray(params)
|
||||
? (params as Record<string, unknown>)
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
runWebSearch,
|
||||
} from "../../web-search/runtime.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
import { asToolParameterSchema, asToolParamsRecord, jsonResult } from "./common.js";
|
||||
import { asToolParamsRecord, jsonResult } from "./common.js";
|
||||
import { SEARCH_CACHE } from "./web-search-provider-common.js";
|
||||
|
||||
export function createWebSearchTool(options?: {
|
||||
@@ -37,7 +37,7 @@ export function createWebSearchTool(options?: {
|
||||
label: "Web Search",
|
||||
name: "web_search",
|
||||
description: resolved.definition.description,
|
||||
parameters: asToolParameterSchema(resolved.definition.parameters),
|
||||
parameters: resolved.definition.parameters,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const result = await runWebSearch({
|
||||
config: options?.config,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { TSchema } from "typebox";
|
||||
import {
|
||||
cleanSchemaForGemini,
|
||||
GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS,
|
||||
} from "../agents/schema/clean-for-gemini.js";
|
||||
import { asToolParameterSchema } from "../agents/tools/common.js";
|
||||
import type { ModelCompatConfig } from "../config/types.models.js";
|
||||
import { applyModelCompatPatch } from "../plugins/provider-model-compat.js";
|
||||
import type {
|
||||
@@ -139,9 +139,7 @@ export function normalizeGeminiToolSchemas(
|
||||
}
|
||||
return {
|
||||
...tool,
|
||||
parameters: asToolParameterSchema(
|
||||
cleanSchemaForGemini(tool.parameters as Record<string, unknown>),
|
||||
),
|
||||
parameters: cleanSchemaForGemini(tool.parameters),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -172,7 +170,7 @@ export function normalizeOpenAIToolSchemas(
|
||||
if (tool.parameters == null) {
|
||||
return {
|
||||
...tool,
|
||||
parameters: asToolParameterSchema(normalizeOpenAIStrictCompatSchema({})),
|
||||
parameters: normalizeOpenAIStrictCompatSchema({}),
|
||||
};
|
||||
}
|
||||
if (typeof tool.parameters !== "object") {
|
||||
@@ -180,13 +178,15 @@ export function normalizeOpenAIToolSchemas(
|
||||
}
|
||||
return {
|
||||
...tool,
|
||||
parameters: asToolParameterSchema(normalizeOpenAIStrictCompatSchema(tool.parameters)),
|
||||
parameters: normalizeOpenAIStrictCompatSchema(tool.parameters),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOpenAIStrictCompatSchema(schema: unknown): unknown {
|
||||
return normalizeOpenAIStrictCompatSchemaRecursive(schema, { promoteEmptyObject: true });
|
||||
function normalizeOpenAIStrictCompatSchema(schema: unknown): TSchema {
|
||||
return normalizeOpenAIStrictCompatSchemaRecursive(schema, {
|
||||
promoteEmptyObject: true,
|
||||
}) as TSchema;
|
||||
}
|
||||
|
||||
function shouldApplyOpenAIToolCompat(ctx: ProviderNormalizeToolSchemasContext): boolean {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TSchema } from "typebox";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type {
|
||||
@@ -12,13 +13,13 @@ export type WebFetchProviderId = string;
|
||||
|
||||
export type WebSearchProviderToolDefinition = {
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
parameters: TSchema;
|
||||
execute: (args: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
export type WebFetchProviderToolDefinition = {
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
parameters: TSchema;
|
||||
execute: (args: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user