@open-mercato/ai-assistant 0.6.6-develop.6343.1.8120f84f30 → 0.6.6-develop.6345.2.b236ea3d4b
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.
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
const safeSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function coerceNumberLike(value) {
|
|
4
|
+
if (typeof value !== "string") return value;
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (trimmed.length === 0) return value;
|
|
7
|
+
return Number(trimmed);
|
|
8
|
+
}
|
|
9
|
+
function jsonNumberSchemaToZod(jsonSchema, integer) {
|
|
10
|
+
let numberSchema = integer ? z.number().int() : z.number();
|
|
11
|
+
const minimum = jsonSchema.minimum;
|
|
12
|
+
const maximum = jsonSchema.maximum;
|
|
13
|
+
const exclusiveMinimum = jsonSchema.exclusiveMinimum;
|
|
14
|
+
const exclusiveMaximum = jsonSchema.exclusiveMaximum;
|
|
15
|
+
if (typeof minimum === "number") numberSchema = numberSchema.min(minimum);
|
|
16
|
+
if (typeof maximum === "number") numberSchema = numberSchema.max(maximum);
|
|
17
|
+
if (typeof exclusiveMinimum === "number") numberSchema = numberSchema.gt(exclusiveMinimum);
|
|
18
|
+
if (typeof exclusiveMaximum === "number") numberSchema = numberSchema.lt(exclusiveMaximum);
|
|
19
|
+
return z.preprocess(coerceNumberLike, numberSchema);
|
|
20
|
+
}
|
|
3
21
|
function jsonSchemaToZod(jsonSchema) {
|
|
4
22
|
const type = jsonSchema.type;
|
|
5
23
|
if (type === "string") {
|
|
6
24
|
return z.string();
|
|
7
25
|
}
|
|
8
26
|
if (type === "number" || type === "integer") {
|
|
9
|
-
return
|
|
27
|
+
return jsonNumberSchemaToZod(jsonSchema, type === "integer");
|
|
10
28
|
}
|
|
11
29
|
if (type === "boolean") {
|
|
12
30
|
return z.boolean();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/schema-utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { z, type ZodType } from 'zod'\n\n/**\n * Cache for converted safe schemas to avoid repeated conversions per request.\n */\nconst safeSchemaCache = new WeakMap<ZodType, ZodType>()\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
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAuB;AAKhC,MAAM,kBAAkB,oBAAI,QAA0B;
|
|
4
|
+
"sourcesContent": ["import { z, type ZodType } from 'zod'\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 console.error('[Schema Utils] Error converting schema:', 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.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -99,16 +99,16 @@
|
|
|
99
99
|
"zod-to-json-schema": "^3.25.2"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
103
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
102
|
+
"@open-mercato/shared": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
103
|
+
"@open-mercato/ui": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
104
104
|
"react": "^19.0.0",
|
|
105
105
|
"react-dom": "^19.0.0",
|
|
106
106
|
"zod": ">=3.23.0"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@open-mercato/cli": "0.6.6-develop.
|
|
110
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
111
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
109
|
+
"@open-mercato/cli": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
110
|
+
"@open-mercato/shared": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
111
|
+
"@open-mercato/ui": "0.6.6-develop.6345.2.b236ea3d4b",
|
|
112
112
|
"@types/react": "^19.2.17",
|
|
113
113
|
"@types/react-dom": "^19.2.3",
|
|
114
114
|
"react": "19.2.7",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { jsonSchemaToZod, toSafeZodSchema } from '../schema-utils'
|
|
4
|
+
|
|
5
|
+
describe('schema-utils', () => {
|
|
6
|
+
it('coerces numeric strings for JSON Schema number fields while preserving bounds', () => {
|
|
7
|
+
const schema = jsonSchemaToZod({
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
11
|
+
ratio: { type: 'number', exclusiveMinimum: 0, exclusiveMaximum: 1 },
|
|
12
|
+
},
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(schema.parse({ limit: '2', ratio: '0.5' })).toEqual({ limit: 2, ratio: 0.5 })
|
|
17
|
+
expect(() => schema.parse({ limit: '0', ratio: '0.5' })).toThrow()
|
|
18
|
+
expect(() => schema.parse({ limit: '', ratio: '0.5' })).toThrow()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('keeps safe tool schemas compatible with OpenRouter-style string pagination args', () => {
|
|
22
|
+
const originalSchema = z.object({
|
|
23
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
24
|
+
offset: z.number().int().min(0).optional(),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const safeSchema = toSafeZodSchema(originalSchema)
|
|
28
|
+
|
|
29
|
+
expect(safeSchema.parse({ limit: '2', offset: '0' })).toEqual({ limit: 2, offset: 0 })
|
|
30
|
+
expect(() => safeSchema.parse({ limit: '101', offset: '0' })).toThrow()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -5,6 +5,28 @@ import { z, type ZodType } from 'zod'
|
|
|
5
5
|
*/
|
|
6
6
|
const safeSchemaCache = new WeakMap<ZodType, ZodType>()
|
|
7
7
|
|
|
8
|
+
function coerceNumberLike(value: unknown): unknown {
|
|
9
|
+
if (typeof value !== 'string') return value
|
|
10
|
+
const trimmed = value.trim()
|
|
11
|
+
if (trimmed.length === 0) return value
|
|
12
|
+
return Number(trimmed)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function jsonNumberSchemaToZod(jsonSchema: Record<string, unknown>, integer: boolean): ZodType {
|
|
16
|
+
let numberSchema = integer ? z.number().int() : z.number()
|
|
17
|
+
const minimum = jsonSchema.minimum
|
|
18
|
+
const maximum = jsonSchema.maximum
|
|
19
|
+
const exclusiveMinimum = jsonSchema.exclusiveMinimum
|
|
20
|
+
const exclusiveMaximum = jsonSchema.exclusiveMaximum
|
|
21
|
+
|
|
22
|
+
if (typeof minimum === 'number') numberSchema = numberSchema.min(minimum)
|
|
23
|
+
if (typeof maximum === 'number') numberSchema = numberSchema.max(maximum)
|
|
24
|
+
if (typeof exclusiveMinimum === 'number') numberSchema = numberSchema.gt(exclusiveMinimum)
|
|
25
|
+
if (typeof exclusiveMaximum === 'number') numberSchema = numberSchema.lt(exclusiveMaximum)
|
|
26
|
+
|
|
27
|
+
return z.preprocess(coerceNumberLike, numberSchema)
|
|
28
|
+
}
|
|
29
|
+
|
|
8
30
|
/**
|
|
9
31
|
* Convert a JSON Schema to a simple Zod schema.
|
|
10
32
|
* This creates a schema that can be converted back to JSON Schema without errors.
|
|
@@ -24,7 +46,7 @@ export function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {
|
|
|
24
46
|
return z.string()
|
|
25
47
|
}
|
|
26
48
|
if (type === 'number' || type === 'integer') {
|
|
27
|
-
return
|
|
49
|
+
return jsonNumberSchemaToZod(jsonSchema, type === 'integer')
|
|
28
50
|
}
|
|
29
51
|
if (type === 'boolean') {
|
|
30
52
|
return z.boolean()
|