From 63e66ca3e6d96d9055fbd3f119bb6ece1bf29dad Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 20 Sep 2026 18:47:56 -0700 Subject: [PATCH] fix: preserve schema compatibility on rc.115 --- packages/ai/src/tool.ts | 2 +- packages/codemode/src/tool-schema.ts | 20 +++++++++++++------- packages/core/src/tool/runtime.ts | 2 +- packages/httpapi-codegen/src/index.ts | 6 +++--- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/ai/src/tool.ts b/packages/ai/src/tool.ts index c04b666887c..56c1f77ffec 100644 --- a/packages/ai/src/tool.ts +++ b/packages/ai/src/tool.ts @@ -231,7 +231,7 @@ export const toDefinitions = (tools: Tools): ReadonlyArray ) const toJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema => { - const document = Schema.toJsonSchemaDocument(schema) + const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" }) if (Object.keys(document.definitions).length === 0) return document.schema return { ...document.schema, $defs: document.definitions } } diff --git a/packages/codemode/src/tool-schema.ts b/packages/codemode/src/tool-schema.ts index 80b3bc97ab0..b103ec2f033 100644 --- a/packages/codemode/src/tool-schema.ts +++ b/packages/codemode/src/tool-schema.ts @@ -1,4 +1,4 @@ -import { JsonPointer, Schema } from "effect" +import { JsonPointer, Schema, SchemaAST } from "effect" import type { Tool, JsonSchema, SchemaType } from "./tool.js" const isEffectSchema = (schema: SchemaType): schema is Schema.Decoder & Schema.Top => Schema.isSchema(schema) @@ -203,7 +203,7 @@ const renderSchema = ( export const toTypeScript = (schema: Schema.Top, decoded = false, pretty = false): string => { try { const visible = decoded ? Schema.toType(schema) : schema - const document = Schema.toJsonSchemaDocument(visible) as { + const document = Schema.toJsonSchemaDocument(visible, { onExcessProperty: "error" }) as { readonly schema: JsonSchema readonly definitions?: Readonly> } @@ -230,7 +230,7 @@ export type InputProperty = { export const inputProperties = (tool: Tool): Array => { try { const document = isEffectSchema(tool.input) - ? (Schema.toJsonSchemaDocument(tool.input) as { + ? (Schema.toJsonSchemaDocument(tool.input, { onExcessProperty: "error" }) as { readonly schema: JsonSchema readonly definitions?: Readonly> }) @@ -261,10 +261,16 @@ export const inputProperties = (tool: Tool): Array => { export const inputTypeScript = (tool: Tool, pretty = false): string => isEffectSchema(tool.input) ? toTypeScript(tool.input, false, pretty) : jsonSchemaToTypeScript(tool.input, pretty) -// Empty object schemas render as `{}` in compact form; anything with properties, -// an index signature, or union members renders differently, so equality is a -// conservative emptiness test for both Effect and JSON Schema inputs. -export const isEmptyInput = (tool: Tool): boolean => inputTypeScript(tool) === "{}" +// Effect 4.0 models an empty Struct as a non-nullish object, so inspect its AST. +// Raw JSON Schema inputs retain the compact `{}` rendering check. +export const isEmptyInput = (tool: Tool): boolean => { + if (!isEffectSchema(tool.input)) return inputTypeScript(tool) === "{}" + return ( + SchemaAST.isObjects(tool.input.ast) && + tool.input.ast.propertySignatures.length === 0 && + tool.input.ast.indexSignatures.length === 0 + ) +} export const outputTypeScript = (tool: Tool, pretty = false): string => tool.output === undefined diff --git a/packages/core/src/tool/runtime.ts b/packages/core/src/tool/runtime.ts index f21c67a533e..e6eb5abe412 100644 --- a/packages/core/src/tool/runtime.ts +++ b/packages/core/src/tool/runtime.ts @@ -170,7 +170,7 @@ const standardJsonSchema = (schema: StandardSchemaV1, io: "input" | "o } const toJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema => { - const document = Schema.toJsonSchemaDocument(schema) + const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" }) // Effect emits valid JSON Schema that some inference providers handle poorly. Simplify it // without changing validation: `{ type: "integer", allOf: [{ minimum: 0 }] }` becomes // `{ type: "integer", minimum: 0 }` only when no keyword would be overwritten. Named schemas diff --git a/packages/httpapi-codegen/src/index.ts b/packages/httpapi-codegen/src/index.ts index ae7cd071291..b90cabad80b 100644 --- a/packages/httpapi-codegen/src/index.ts +++ b/packages/httpapi-codegen/src/index.ts @@ -1638,9 +1638,9 @@ function checksPortable(checks: SchemaAST.Checks | undefined): boolean { ? !check.aborted && check.annotations?.representation !== undefined && serializable(check.annotations.representation) && - typeof check.annotations.arbitrary === "object" && - check.annotations.arbitrary !== null && - "constraint" in check.annotations.arbitrary + typeof check.annotations.arbitraryConstraint === "object" && + check.annotations.arbitraryConstraint !== null && + serializable(check.annotations.arbitraryConstraint) : checksPortable(check.checks), ) }