diff --git a/extensions/ollama/src/web-search-provider.ts b/extensions/ollama/src/web-search-provider.ts index 5295bc74a1d..5882d183670 100644 --- a/extensions/ollama/src/web-search-provider.ts +++ b/extensions/ollama/src/web-search-provider.ts @@ -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, + parameters: OLLAMA_WEB_SEARCH_SCHEMA, execute: async (args) => await runOllamaWebSearch({ config: ctx.config, diff --git a/src/agents/pi-bundle-mcp-materialize.ts b/src/agents/pi-bundle-mcp-materialize.ts index 67b03f45ece..a222ea27663 100644 --- a/src/agents/pi-bundle-mcp-materialize.ts +++ b/src/agents/pi-bundle-mcp-materialize.ts @@ -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({ diff --git a/src/agents/pi-bundle-mcp-types.ts b/src/agents/pi-bundle-mcp-types.ts index 13865bfaf10..83d962ea64f 100644 --- a/src/agents/pi-bundle-mcp-types.ts +++ b/src/agents/pi-bundle-mcp-types.ts @@ -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; }; diff --git a/src/agents/pi-tools-parameter-schema.ts b/src/agents/pi-tools-parameter-schema.ts index b1da8ea2f3b..ac273aae215 100644 --- a/src/agents/pi-tools-parameter-schema.ts +++ b/src/agents/pi-tools-parameter-schema.ts @@ -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): boolean { export function normalizeToolParameterSchema( schema: unknown, options?: { modelProvider?: string; modelId?: string; modelCompat?: ModelCompatConfig }, -): unknown { +): TSchema { const schemaRecord = schema && typeof schema === "object" ? (schema as Record) : 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 = {}; diff --git a/src/agents/pi-tools.schema.test.ts b/src/agents/pi-tools.schema.test.ts index 36d6a676615..ce10f2719b8 100644 --- a/src/agents/pi-tools.schema.test.ts +++ b/src/agents/pi-tools.schema.test.ts @@ -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(), }; } diff --git a/src/agents/pi-tools.schema.ts b/src/agents/pi-tools.schema.ts index 3d3c9118ddd..bfac93f39f7 100644 --- a/src/agents/pi-tools.schema.ts +++ b/src/agents/pi-tools.schema.ts @@ -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), }); } diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index 783b95798c7..e2db433e487 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -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); - return cleanSchemaForGeminiWithDefs(schema, defs, undefined); + return cleanSchemaForGeminiWithDefs(schema, defs, undefined) as TSchema; } diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index 43f3b99c8c7..5276dfb477e 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -34,10 +34,6 @@ export type AnyAgentTool = Omit, "execute"> & displaySummary?: string; }; -export function asToolParameterSchema(schema: unknown): TSchema { - return schema as TSchema; -} - export function asToolParamsRecord(params: unknown): Record { return params && typeof params === "object" && !Array.isArray(params) ? (params as Record) diff --git a/src/agents/tools/web-search.ts b/src/agents/tools/web-search.ts index e80d16eb4c0..88e238597fb 100644 --- a/src/agents/tools/web-search.ts +++ b/src/agents/tools/web-search.ts @@ -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, diff --git a/src/plugin-sdk/provider-tools.ts b/src/plugin-sdk/provider-tools.ts index e66436e78a2..e627a129345 100644 --- a/src/plugin-sdk/provider-tools.ts +++ b/src/plugin-sdk/provider-tools.ts @@ -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), - ), + 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 { diff --git a/src/plugins/web-provider-types.ts b/src/plugins/web-provider-types.ts index f17916ce169..e2e9edccad6 100644 --- a/src/plugins/web-provider-types.ts +++ b/src/plugins/web-provider-types.ts @@ -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; + parameters: TSchema; execute: (args: Record) => Promise>; }; export type WebFetchProviderToolDefinition = { description: string; - parameters: Record; + parameters: TSchema; execute: (args: Record) => Promise>; };