@open-mercato/ai-assistant 0.10.1-develop.7616.1.51a1f65492 → 0.10.1-develop.7618.1.ba9531c18e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,34 @@ function jsonNumberSchemaToZod(jsonSchema, integer) {
21
21
  return z.preprocess(coerceNumberLike, numberSchema);
22
22
  }
23
23
  function jsonSchemaToZod(jsonSchema) {
24
+ let schema = jsonSchemaTypeToZod(jsonSchema);
25
+ if (hasNonObjectDefault(jsonSchema)) {
26
+ schema = schema.default(jsonSchema.default);
27
+ }
28
+ if (typeof jsonSchema.description === "string" && jsonSchema.description.length > 0) {
29
+ schema = schema.describe(jsonSchema.description);
30
+ }
31
+ return schema;
32
+ }
33
+ function hasNonObjectDefault(jsonSchema) {
34
+ return "default" in jsonSchema && jsonSchema.type !== "object";
35
+ }
36
+ function isLiteralValue(value) {
37
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
38
+ }
39
+ function jsonEnumToZod(values) {
40
+ if (values.length === 0 || !values.every(isLiteralValue)) return null;
41
+ if (values.every((value) => typeof value === "string")) {
42
+ return z.enum(values);
43
+ }
44
+ return z.literal(values);
45
+ }
46
+ function jsonSchemaTypeToZod(jsonSchema) {
24
47
  const type = jsonSchema.type;
48
+ const enumSchema = Array.isArray(jsonSchema.enum) ? jsonEnumToZod(jsonSchema.enum) : null;
49
+ if (enumSchema) {
50
+ return enumSchema;
51
+ }
25
52
  if (type === "string") {
26
53
  return z.string();
27
54
  }
@@ -56,7 +83,7 @@ function jsonSchemaToZod(jsonSchema) {
56
83
  const shape = {};
57
84
  for (const [key, propSchema] of Object.entries(properties)) {
58
85
  let fieldSchema = jsonSchemaToZod(propSchema);
59
- if (!required.includes(key)) {
86
+ if (!required.includes(key) && !hasNonObjectDefault(propSchema)) {
60
87
  fieldSchema = fieldSchema.optional();
61
88
  }
62
89
  shape[key] = fieldSchema;
@@ -87,10 +114,6 @@ function jsonSchemaToZod(jsonSchema) {
87
114
  }
88
115
  }
89
116
  }
90
- const enumValues = jsonSchema.enum;
91
- if (enumValues && enumValues.length > 0) {
92
- return z.enum(enumValues);
93
- }
94
117
  return z.unknown();
95
118
  }
96
119
  function toSafeZodSchema(schema) {
@@ -99,7 +122,7 @@ function toSafeZodSchema(schema) {
99
122
  return cached;
100
123
  }
101
124
  try {
102
- const jsonSchema = z.toJSONSchema(schema, { unrepresentable: "any" });
125
+ const jsonSchema = z.toJSONSchema(schema, { io: "input", unrepresentable: "any" });
103
126
  if (jsonSchema.type === "object" && !jsonSchema.properties) {
104
127
  jsonSchema.properties = {};
105
128
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/schema-utils.ts"],
4
- "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { z, type ZodType } from 'zod'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * Cache for converted safe schemas to avoid repeated conversions per request.\n */\nconst safeSchemaCache = new WeakMap<ZodType, ZodType>()\n\nfunction coerceNumberLike(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n if (trimmed.length === 0) return value\n return Number(trimmed)\n}\n\nfunction jsonNumberSchemaToZod(jsonSchema: Record<string, unknown>, integer: boolean): ZodType {\n let numberSchema = integer ? z.number().int() : z.number()\n const minimum = jsonSchema.minimum\n const maximum = jsonSchema.maximum\n const exclusiveMinimum = jsonSchema.exclusiveMinimum\n const exclusiveMaximum = jsonSchema.exclusiveMaximum\n\n if (typeof minimum === 'number') numberSchema = numberSchema.min(minimum)\n if (typeof maximum === 'number') numberSchema = numberSchema.max(maximum)\n if (typeof exclusiveMinimum === 'number') numberSchema = numberSchema.gt(exclusiveMinimum)\n if (typeof exclusiveMaximum === 'number') numberSchema = numberSchema.lt(exclusiveMaximum)\n\n return z.preprocess(coerceNumberLike, numberSchema)\n}\n\n/**\n * Convert a JSON Schema to a simple Zod schema.\n * This creates a schema that can be converted back to JSON Schema without errors.\n *\n * Supports:\n * - Basic types: string, number, integer, boolean, null\n * - Arrays with item types\n * - Objects with properties and required fields\n * - Records/dictionaries via additionalProperties\n * - Union types via anyOf/oneOf\n * - Enum values\n */\nexport function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {\n const type = jsonSchema.type as string | undefined\n\n if (type === 'string') {\n return z.string()\n }\n if (type === 'number' || type === 'integer') {\n return jsonNumberSchemaToZod(jsonSchema, type === 'integer')\n }\n if (type === 'boolean') {\n return z.boolean()\n }\n if (type === 'null') {\n return z.null()\n }\n if (type === 'array') {\n const items = jsonSchema.items as Record<string, unknown> | undefined\n if (items) {\n return z.array(jsonSchemaToZod(items))\n }\n return z.array(z.unknown())\n }\n if (type === 'object') {\n const properties = jsonSchema.properties as Record<string, Record<string, unknown>> | undefined\n const required = (jsonSchema.required as string[]) || []\n const additionalProperties = jsonSchema.additionalProperties\n\n // Handle z.record() - objects with additionalProperties but no fixed properties.\n // Skip the record path when properties is present but empty \u2014 that is a\n // no-arg object schema, not a dictionary. OpenAI requires `properties: {}`\n // in the JSON Schema, and `z.object({})` produces that, whereas\n // `z.record()` does not.\n const hasFixedProperties = properties && Object.keys(properties).length > 0\n if (additionalProperties && !hasFixedProperties && properties === undefined) {\n // This is a record/dictionary type - allow any properties\n if (typeof additionalProperties === 'object') {\n return z.record(z.string(), jsonSchemaToZod(additionalProperties as Record<string, unknown>))\n }\n // additionalProperties: true means any value\n return z.record(z.string(), z.unknown())\n }\n\n if (properties) {\n const shape: Record<string, ZodType> = {}\n for (const [key, propSchema] of Object.entries(properties)) {\n let fieldSchema = jsonSchemaToZod(propSchema)\n // Make field optional if not in required array\n if (!required.includes(key)) {\n fieldSchema = fieldSchema.optional()\n }\n shape[key] = fieldSchema\n }\n // If additionalProperties is allowed, use passthrough\n if (additionalProperties) {\n return z.object(shape).passthrough()\n }\n return z.object(shape)\n }\n\n // Empty object with additionalProperties - treat as record\n if (additionalProperties) {\n return z.record(z.string(), z.unknown())\n }\n return z.object({})\n }\n\n // Handle union types (anyOf, oneOf)\n const anyOf = jsonSchema.anyOf as Record<string, unknown>[] | undefined\n const oneOf = jsonSchema.oneOf as Record<string, unknown>[] | undefined\n const unionTypes = anyOf || oneOf\n if (unionTypes && unionTypes.length >= 2) {\n const schemas = unionTypes.map(s => jsonSchemaToZod(s))\n return z.union(schemas as [ZodType, ZodType, ...ZodType[]])\n }\n\n // Handle nullable via anyOf with null\n if (anyOf && anyOf.length === 2) {\n const types = anyOf.map((s) => s.type)\n if (types.includes('null')) {\n const nonNullSchema = anyOf.find((s) => s.type !== 'null')\n if (nonNullSchema) {\n return jsonSchemaToZod(nonNullSchema).nullable()\n }\n }\n }\n\n // Handle enum\n const enumValues = jsonSchema.enum as string[] | undefined\n if (enumValues && enumValues.length > 0) {\n return z.enum(enumValues as [string, ...string[]])\n }\n\n // Fallback for empty schemas (like Date converted with unrepresentable: 'any')\n return z.unknown()\n}\n\n/**\n * Convert a Zod schema to a safe Zod schema that has no Date types.\n * Uses JSON Schema as an intermediate format to handle all Zod v4 internal complexities.\n * Results are cached to avoid repeated conversions.\n *\n * @param schema - The original Zod schema\n * @returns A safe Zod schema without Date types\n */\nexport function toSafeZodSchema(schema: ZodType): ZodType {\n // Check cache first\n const cached = safeSchemaCache.get(schema)\n if (cached) {\n return cached\n }\n\n try {\n // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types\n const jsonSchema = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // OpenAI requires `properties` on object schemas. Empty passthrough objects\n // (tools that take no args) roundtrip as `{ type: \"object\", additionalProperties: true }`\n // without `properties`, which OpenAI rejects. Ensure `properties` is present.\n if (jsonSchema.type === 'object' && !jsonSchema.properties) {\n jsonSchema.properties = {}\n }\n\n // Convert back to a simple Zod schema without Date types\n const safeSchema = jsonSchemaToZod(jsonSchema)\n\n // Cache the result\n safeSchemaCache.set(schema, safeSchema)\n\n return safeSchema\n } catch (error) {\n logger.error('Schema Utils \u2014 Error converting schema', { err: error })\n // Fallback to the original schema if conversion fails\n return schema\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { z, type ZodType } from 'zod'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * Cache for converted safe schemas to avoid repeated conversions per request.\n */\nconst safeSchemaCache = new WeakMap<ZodType, ZodType>()\n\nfunction coerceNumberLike(value: unknown): unknown {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n if (trimmed.length === 0) return value\n return Number(trimmed)\n}\n\nfunction jsonNumberSchemaToZod(jsonSchema: Record<string, unknown>, integer: boolean): ZodType {\n let numberSchema = integer ? z.number().int() : z.number()\n const minimum = jsonSchema.minimum\n const maximum = jsonSchema.maximum\n const exclusiveMinimum = jsonSchema.exclusiveMinimum\n const exclusiveMaximum = jsonSchema.exclusiveMaximum\n\n if (typeof minimum === 'number') numberSchema = numberSchema.min(minimum)\n if (typeof maximum === 'number') numberSchema = numberSchema.max(maximum)\n if (typeof exclusiveMinimum === 'number') numberSchema = numberSchema.gt(exclusiveMinimum)\n if (typeof exclusiveMaximum === 'number') numberSchema = numberSchema.lt(exclusiveMaximum)\n\n return z.preprocess(coerceNumberLike, numberSchema)\n}\n\n/**\n * Convert a JSON Schema to a simple Zod schema.\n * This creates a schema that can be converted back to JSON Schema without errors.\n *\n * Supports:\n * - Basic types: string, number, integer, boolean, null\n * - Arrays with item types\n * - Objects with properties and required fields\n * - Records/dictionaries via additionalProperties\n * - Union types via anyOf/oneOf\n * - Enum values\n * - `description` and `default`, which a model needs to call the tool correctly\n */\nexport function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {\n let schema = jsonSchemaTypeToZod(jsonSchema)\n if (hasNonObjectDefault(jsonSchema)) {\n schema = schema.default(jsonSchema.default)\n }\n if (typeof jsonSchema.description === 'string' && jsonSchema.description.length > 0) {\n schema = schema.describe(jsonSchema.description)\n }\n return schema\n}\n\n// Object schemas stay unwrapped so callers can still `.passthrough()` the root; the tool's own\n// schema applies an object default when the tool runs.\nfunction hasNonObjectDefault(jsonSchema: Record<string, unknown>): boolean {\n return 'default' in jsonSchema && jsonSchema.type !== 'object'\n}\n\nfunction isLiteralValue(value: unknown): value is string | number | boolean | null {\n return value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'\n}\n\nfunction jsonEnumToZod(values: unknown[]): ZodType | null {\n if (values.length === 0 || !values.every(isLiteralValue)) return null\n if (values.every((value): value is string => typeof value === 'string')) {\n return z.enum(values as [string, ...string[]])\n }\n return z.literal(values)\n}\n\nfunction jsonSchemaTypeToZod(jsonSchema: Record<string, unknown>): ZodType {\n const type = jsonSchema.type as string | undefined\n\n const enumSchema = Array.isArray(jsonSchema.enum) ? jsonEnumToZod(jsonSchema.enum) : null\n if (enumSchema) {\n return enumSchema\n }\n\n if (type === 'string') {\n return z.string()\n }\n if (type === 'number' || type === 'integer') {\n return jsonNumberSchemaToZod(jsonSchema, type === 'integer')\n }\n if (type === 'boolean') {\n return z.boolean()\n }\n if (type === 'null') {\n return z.null()\n }\n if (type === 'array') {\n const items = jsonSchema.items as Record<string, unknown> | undefined\n if (items) {\n return z.array(jsonSchemaToZod(items))\n }\n return z.array(z.unknown())\n }\n if (type === 'object') {\n const properties = jsonSchema.properties as Record<string, Record<string, unknown>> | undefined\n const required = (jsonSchema.required as string[]) || []\n const additionalProperties = jsonSchema.additionalProperties\n\n // Handle z.record() - objects with additionalProperties but no fixed properties.\n // Skip the record path when properties is present but empty \u2014 that is a\n // no-arg object schema, not a dictionary. OpenAI requires `properties: {}`\n // in the JSON Schema, and `z.object({})` produces that, whereas\n // `z.record()` does not.\n const hasFixedProperties = properties && Object.keys(properties).length > 0\n if (additionalProperties && !hasFixedProperties && properties === undefined) {\n // This is a record/dictionary type - allow any properties\n if (typeof additionalProperties === 'object') {\n return z.record(z.string(), jsonSchemaToZod(additionalProperties as Record<string, unknown>))\n }\n // additionalProperties: true means any value\n return z.record(z.string(), z.unknown())\n }\n\n if (properties) {\n const shape: Record<string, ZodType> = {}\n for (const [key, propSchema] of Object.entries(properties)) {\n let fieldSchema = jsonSchemaToZod(propSchema)\n // Make field optional if not in required array; a field with a default already accepts a missing value\n if (!required.includes(key) && !hasNonObjectDefault(propSchema)) {\n fieldSchema = fieldSchema.optional()\n }\n shape[key] = fieldSchema\n }\n // If additionalProperties is allowed, use passthrough\n if (additionalProperties) {\n return z.object(shape).passthrough()\n }\n return z.object(shape)\n }\n\n // Empty object with additionalProperties - treat as record\n if (additionalProperties) {\n return z.record(z.string(), z.unknown())\n }\n return z.object({})\n }\n\n // Handle union types (anyOf, oneOf)\n const anyOf = jsonSchema.anyOf as Record<string, unknown>[] | undefined\n const oneOf = jsonSchema.oneOf as Record<string, unknown>[] | undefined\n const unionTypes = anyOf || oneOf\n if (unionTypes && unionTypes.length >= 2) {\n const schemas = unionTypes.map(s => jsonSchemaToZod(s))\n return z.union(schemas as [ZodType, ZodType, ...ZodType[]])\n }\n\n // Handle nullable via anyOf with null\n if (anyOf && anyOf.length === 2) {\n const types = anyOf.map((s) => s.type)\n if (types.includes('null')) {\n const nonNullSchema = anyOf.find((s) => s.type !== 'null')\n if (nonNullSchema) {\n return jsonSchemaToZod(nonNullSchema).nullable()\n }\n }\n }\n\n // Fallback for empty schemas (like Date converted with unrepresentable: 'any')\n return z.unknown()\n}\n\n/**\n * Convert a Zod schema to a safe Zod schema that has no Date types.\n * Uses JSON Schema as an intermediate format to handle all Zod v4 internal complexities.\n * Results are cached to avoid repeated conversions.\n *\n * @param schema - The original Zod schema\n * @returns A safe Zod schema without Date types\n */\nexport function toSafeZodSchema(schema: ZodType): ZodType {\n // Check cache first\n const cached = safeSchemaCache.get(schema)\n if (cached) {\n return cached\n }\n\n try {\n // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types.\n // `io: 'input'` describes what the caller (the model) sends, so a `.default()` field stays optional.\n const jsonSchema = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as Record<string, unknown>\n\n // OpenAI requires `properties` on object schemas. Empty passthrough objects\n // (tools that take no args) roundtrip as `{ type: \"object\", additionalProperties: true }`\n // without `properties`, which OpenAI rejects. Ensure `properties` is present.\n if (jsonSchema.type === 'object' && !jsonSchema.properties) {\n jsonSchema.properties = {}\n }\n\n // Convert back to a simple Zod schema without Date types\n const safeSchema = jsonSchemaToZod(jsonSchema)\n\n // Cache the result\n safeSchemaCache.set(schema, safeSchema)\n\n return safeSchema\n } catch (error) {\n logger.error('Schema Utils \u2014 Error converting schema', { err: error })\n // Fallback to the original schema if conversion fails\n return schema\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ai-assistant",
3
- "version": "0.10.1-develop.7616.1.51a1f65492",
3
+ "version": "0.10.1-develop.7618.1.ba9531c18e",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -100,16 +100,16 @@
100
100
  "zod-to-json-schema": "^3.25.2"
101
101
  },
102
102
  "peerDependencies": {
103
- "@open-mercato/shared": "0.10.1-develop.7616.1.51a1f65492",
104
- "@open-mercato/ui": "0.10.1-develop.7616.1.51a1f65492",
103
+ "@open-mercato/shared": "0.10.1-develop.7618.1.ba9531c18e",
104
+ "@open-mercato/ui": "0.10.1-develop.7618.1.ba9531c18e",
105
105
  "react": "^19.0.0",
106
106
  "react-dom": "^19.0.0",
107
107
  "zod": ">=3.23.0"
108
108
  },
109
109
  "devDependencies": {
110
- "@open-mercato/cli": "0.10.1-develop.7616.1.51a1f65492",
111
- "@open-mercato/shared": "0.10.1-develop.7616.1.51a1f65492",
112
- "@open-mercato/ui": "0.10.1-develop.7616.1.51a1f65492",
110
+ "@open-mercato/cli": "0.10.1-develop.7618.1.ba9531c18e",
111
+ "@open-mercato/shared": "0.10.1-develop.7618.1.ba9531c18e",
112
+ "@open-mercato/ui": "0.10.1-develop.7618.1.ba9531c18e",
113
113
  "@types/react": "^19.2.18",
114
114
  "@types/react-dom": "^19.2.5",
115
115
  "react": "19.2.8",
@@ -29,4 +29,52 @@ describe('schema-utils', () => {
29
29
  expect(safeSchema.parse({ limit: '2', offset: '0' })).toEqual({ limit: 2, offset: 0 })
30
30
  expect(() => safeSchema.parse({ limit: '101', offset: '0' })).toThrow()
31
31
  })
32
+
33
+ describe('keeps what the model needs from a tool input schema', () => {
34
+ const toolInput = z.object({
35
+ query: z.string().describe('Text to search for'),
36
+ limit: z.number().int().min(1).max(100).default(20).describe('Maximum number of rows'),
37
+ status: z.enum(['open', 'closed']).optional().describe('Only rows in this status'),
38
+ sort: z.enum(['newest', 'oldest']).default('newest'),
39
+ })
40
+
41
+ const publish = (schema: z.ZodType) =>
42
+ z.toJSONSchema(schema, { io: 'input' }) as {
43
+ required?: string[]
44
+ properties: Record<string, { description?: string; enum?: unknown[]; default?: unknown }>
45
+ }
46
+
47
+ it('does not make a defaulted field required, and applies its default', () => {
48
+ const safeSchema = toSafeZodSchema(toolInput)
49
+
50
+ expect(publish(safeSchema).required).toEqual(['query'])
51
+ expect(safeSchema.parse({ query: 'acme' })).toEqual({ query: 'acme', limit: 20, sort: 'newest' })
52
+ expect(safeSchema.parse({ query: 'acme', limit: '5' })).toEqual({ query: 'acme', limit: 5, sort: 'newest' })
53
+ })
54
+
55
+ it('keeps enum values and rejects anything outside them', () => {
56
+ const safeSchema = toSafeZodSchema(toolInput)
57
+ const { properties } = publish(safeSchema)
58
+
59
+ expect(properties.status.enum).toEqual(['open', 'closed'])
60
+ expect(properties.sort.enum).toEqual(['newest', 'oldest'])
61
+ expect(() => safeSchema.parse({ query: 'acme', status: 'archived' })).toThrow()
62
+ })
63
+
64
+ it('keeps field descriptions and publishes defaults', () => {
65
+ const { properties } = publish(toSafeZodSchema(toolInput))
66
+
67
+ expect(properties.query.description).toBe('Text to search for')
68
+ expect(properties.limit.description).toBe('Maximum number of rows')
69
+ expect(properties.status.description).toBe('Only rows in this status')
70
+ expect(properties.sort.default).toBe('newest')
71
+ })
72
+
73
+ it('converts enums of non-string literals', () => {
74
+ const schema = jsonSchemaToZod({ enum: [1, 2, 3] })
75
+
76
+ expect(schema.parse(2)).toBe(2)
77
+ expect(() => schema.parse(4)).toThrow()
78
+ })
79
+ })
32
80
  })
@@ -41,10 +41,45 @@ function jsonNumberSchemaToZod(jsonSchema: Record<string, unknown>, integer: boo
41
41
  * - Records/dictionaries via additionalProperties
42
42
  * - Union types via anyOf/oneOf
43
43
  * - Enum values
44
+ * - `description` and `default`, which a model needs to call the tool correctly
44
45
  */
45
46
  export function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {
47
+ let schema = jsonSchemaTypeToZod(jsonSchema)
48
+ if (hasNonObjectDefault(jsonSchema)) {
49
+ schema = schema.default(jsonSchema.default)
50
+ }
51
+ if (typeof jsonSchema.description === 'string' && jsonSchema.description.length > 0) {
52
+ schema = schema.describe(jsonSchema.description)
53
+ }
54
+ return schema
55
+ }
56
+
57
+ // Object schemas stay unwrapped so callers can still `.passthrough()` the root; the tool's own
58
+ // schema applies an object default when the tool runs.
59
+ function hasNonObjectDefault(jsonSchema: Record<string, unknown>): boolean {
60
+ return 'default' in jsonSchema && jsonSchema.type !== 'object'
61
+ }
62
+
63
+ function isLiteralValue(value: unknown): value is string | number | boolean | null {
64
+ return value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
65
+ }
66
+
67
+ function jsonEnumToZod(values: unknown[]): ZodType | null {
68
+ if (values.length === 0 || !values.every(isLiteralValue)) return null
69
+ if (values.every((value): value is string => typeof value === 'string')) {
70
+ return z.enum(values as [string, ...string[]])
71
+ }
72
+ return z.literal(values)
73
+ }
74
+
75
+ function jsonSchemaTypeToZod(jsonSchema: Record<string, unknown>): ZodType {
46
76
  const type = jsonSchema.type as string | undefined
47
77
 
78
+ const enumSchema = Array.isArray(jsonSchema.enum) ? jsonEnumToZod(jsonSchema.enum) : null
79
+ if (enumSchema) {
80
+ return enumSchema
81
+ }
82
+
48
83
  if (type === 'string') {
49
84
  return z.string()
50
85
  }
@@ -88,8 +123,8 @@ export function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {
88
123
  const shape: Record<string, ZodType> = {}
89
124
  for (const [key, propSchema] of Object.entries(properties)) {
90
125
  let fieldSchema = jsonSchemaToZod(propSchema)
91
- // Make field optional if not in required array
92
- if (!required.includes(key)) {
126
+ // Make field optional if not in required array; a field with a default already accepts a missing value
127
+ if (!required.includes(key) && !hasNonObjectDefault(propSchema)) {
93
128
  fieldSchema = fieldSchema.optional()
94
129
  }
95
130
  shape[key] = fieldSchema
@@ -128,12 +163,6 @@ export function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {
128
163
  }
129
164
  }
130
165
 
131
- // Handle enum
132
- const enumValues = jsonSchema.enum as string[] | undefined
133
- if (enumValues && enumValues.length > 0) {
134
- return z.enum(enumValues as [string, ...string[]])
135
- }
136
-
137
166
  // Fallback for empty schemas (like Date converted with unrepresentable: 'any')
138
167
  return z.unknown()
139
168
  }
@@ -154,8 +183,9 @@ export function toSafeZodSchema(schema: ZodType): ZodType {
154
183
  }
155
184
 
156
185
  try {
157
- // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types
158
- const jsonSchema = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>
186
+ // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types.
187
+ // `io: 'input'` describes what the caller (the model) sends, so a `.default()` field stays optional.
188
+ const jsonSchema = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as Record<string, unknown>
159
189
 
160
190
  // OpenAI requires `properties` on object schemas. Empty passthrough objects
161
191
  // (tools that take no args) roundtrip as `{ type: "object", additionalProperties: true }`