/** * @packageDocumentation * GS2-58 — systemic Gemini tool-schema sanitizer at the `@langchain/google` provider boundary. * * Google Gemini's function-declaration schema is a SELECT SUBSET of OpenAPI 3.0. `@langchain/google` * sanitizes against that subset itself — `sanitizeGeminiSchema`, an allowlist of its own — so most * unsupported JSON-Schema keywords (`$defs`, `patternProperties`, `multipleOf`, `const`, * `allOf`/`oneOf`/`not`, …) do not reach the wire on their own. TWO GAPS REMAIN, and they are what * this transform is for: * * - **`$ref` makes the converter THROW, not strip.** `InvalidInputError` is raised at tool-declaration * time, so the call never leaves the process and a tool carrying a `$ref` fails outright without * this pass. The FAILURE MODE is what changed — a degraded schema became a thrown error — which * makes the sanitizer more load-bearing than a keyword-stripping reading of it suggests. **How * often it fires is a separate question, and [[GS2-68]] measured it as near-zero:** * `@langchain/mcp-adapters` dereferences `$ref` before this transform runs, and gaunt-sloth's own * zod tools inline and type their schemas, so the exposure is a raw non-adapter tool authoring * `$ref` itself. Do not read this bullet as "our tools would break". * - **An exclusive bound is DISCARDED, losing the constraint.** `exclusiveMinimum: 5` simply vanishes; * this transform rewrites it to `minimum: 5` so the bound still reaches Gemini. That rewrite is * deliberately NOT strictly equivalent — an inclusive bound admits 5 where the original excluded it * — and is preferred to silently dropping the constraint altogether. * * Both gaps are measured against `@langchain/google` 0.2.6 and pinned as a two-path differential by * `packages/core/spec/geminiSchemaSanitizer.spec.ts`, which is also how we will learn when upstream * has caught up and this module can go. * * This is the DURABLE fix (GS2-58, fix-cycle 1): rather than a denylist that is always one unknown * keyword behind, {@link sanitizeGeminiToolSchema} is an ALLOWLIST — it keeps ONLY the fields the * installed Gemini `Schema` type declares and drops everything else, so a future keyword in ANY tool * (built-in, custom, or MCP — including schemas gaunt-sloth does not author) cannot re-break Gemini. * * The allowlist ({@link GEMINI_SUPPORTED_SCHEMA_KEYWORDS}) is derived DIRECTLY from the authoritative * in-repo type — the `Gemini.Tools.Schema` interface in * `node_modules/@langchain/google/dist/chat_models/api-types.d.{ts,cts}` (a `FunctionDeclaration`'s * `parameters?: Schema`), "a select subset of an OpenAPI 3.0 schema object". Keeping it aligned with * that type (not a remembered list) is what prevents a stale allowlist silently over-stripping. * * Scope is the google/gemini provider path ONLY: {@link applyGeminiToolSchemaSanitizer} is wired into * the `google-genai` and `vertexai` presets' `processJsonConfig`. It leaves OpenAI/Anthropic/Ollama * wiring untouched, and does not weaken the GS2-56/57 build-time denylist guard (that test still runs; * this transform runs ahead of the wire send, so a sanitized tool is what Gemini sees). */ import type { BaseChatModel } from '@langchain/core/language_models/chat_models'; /** * The EXACT set of schema keywords Gemini's function-declaration schema accepts, transcribed field- * for-field from the `Gemini.Tools.Schema` interface in * `@langchain/google/dist/chat_models/api-types.d.ts`. Anything not in this set is dropped (allowlist). * * Note `anyOf` IS supported (unions / nullable) and MUST survive — only `allOf`/`oneOf`/`not` are * absent from the type and therefore dropped. `exclusiveMinimum`/`exclusiveMaximum` are NOT in the type * either; they are handled specially by rewriting them to `minimum`/`maximum` (see `sanitizeNode`) * before the allowlist filter runs. */ export declare const GEMINI_SUPPORTED_SCHEMA_KEYWORDS: ReadonlySet; /** * Pure, recursive normaliser: returns a cleaned DEEP COPY of a JSON-Schema containing only keywords * Gemini's function-declaration `Schema` accepts, so its OpenAPI-3.0 subset accepts the tool. At each * node the SAFE composition keywords are first RESOLVED into supported equivalents * (`resolveComposition`: `const` → `enum`, clean `allOf` → shallow merge) and only then does the * allowlist drop the rest. Does not mutate the input. `anyOf` unions survive; `$ref`/`oneOf`/`not` are * dropped (see `resolveComposition` for why the last three are deferred, not resolved). */ export declare function sanitizeGeminiToolSchema(schema: T): T; /** * Wire the sanitizer into a ChatGoogle model at the tool-binding boundary. Overrides the instance's * `bindTools` so every tool passed to it — built-in, custom, or MCP, via `createAgent`, * which calls `model.bindTools(tools)` — is sanitized before it reaches `@langchain/google`'s * Gemini converter. Provider-scoped: only the google-genai/vertexai presets call this, so no other * provider's tools are affected. Returns the same model instance for convenient chaining. */ export declare function applyGeminiToolSchemaSanitizer(model: T): T;