From ee90bca4e671c927acfe764d76f6991f0829f2cd Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 18 Jun 2026 01:57:23 +0200 Subject: [PATCH] feat(ai): integrated ark json ast parsing and normalization into the wire schema pipelines - Adds conversion utilities to detect and parse Ark JSON AST models into wire-compatible schemas. - Extracted inline JSDoc-style comments appended to object keys inside Ark ASTs to assign descriptions correctly. - Cascades normalization of property comments down child schemas within the schema processing pipelines. - Upgrades fallback JSON schema resolution to auto-detect and convert embedded Ark AST maps. --- packages/ai/src/utils/schema/wire.ts | 132 ++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/utils/schema/wire.ts b/packages/ai/src/utils/schema/wire.ts index 693dc0bdf..20042a9ca 100644 --- a/packages/ai/src/utils/schema/wire.ts +++ b/packages/ai/src/utils/schema/wire.ts @@ -66,6 +66,88 @@ export function isArkSchema(value: unknown): value is Type { ); } +function isArkJsonAst(value: unknown): boolean { + if (Array.isArray(value)) return value.some(isArkJsonAst); + if (!isSchemaRecord(value)) return false; + if (typeof value.domain === "string" || Object.hasOwn(value, "unit")) return true; + if (value.proto === "Array" && Object.hasOwn(value, "sequence")) return true; + const required = value.required; + return ( + Array.isArray(required) && + required.some(entry => isSchemaRecord(entry) && typeof entry.key === "string" && "value" in entry) + ); +} + +function parseArkObjectKey(key: string): { name: string; description?: string } { + const match = /^(.*?)\s*\/\*\*\s*([\s\S]*?)\s*\*\/\s*$/.exec(key); + if (!match) return { name: key }; + return { name: match[1].trim(), description: match[2].trim() }; +} + +function withArkKeyDescription(schema: unknown, description: string | undefined): unknown { + if (!description) return schema; + if (isSchemaRecord(schema)) { + if (typeof schema.description !== "string") schema.description = description; + return schema; + } + return { anyOf: [schema], description }; +} + +function arkJsonAstToWire(value: unknown): unknown { + if (typeof value === "string") { + switch (value) { + case "string": + case "number": + case "integer": + case "boolean": + case "object": + return { type: value }; + case "unknown": + return {}; + default: + return {}; + } + } + + if (Array.isArray(value)) { + if (value.every(item => isSchemaRecord(item) && Object.hasOwn(item, "unit"))) { + return { enum: value.map(item => (item as { unit: unknown }).unit) }; + } + return { anyOf: value.map(arkJsonAstToWire) }; + } + + if (!isSchemaRecord(value)) return {}; + + if (Object.hasOwn(value, "unit")) return { const: value.unit }; + + if (value.proto === "Array" && Object.hasOwn(value, "sequence")) { + return { type: "array", items: arkJsonAstToWire(value.sequence) }; + } + + if (value.domain === "object") { + const properties: Record = {}; + const required: string[] = []; + const addEntry = (entry: unknown, isRequired: boolean): void => { + if (!isSchemaRecord(entry) || typeof entry.key !== "string" || !("value" in entry)) return; + const key = parseArkObjectKey(entry.key); + properties[key.name] = withArkKeyDescription(arkJsonAstToWire(entry.value), key.description); + if (isRequired) required.push(key.name); + }; + if (Array.isArray(value.required)) { + for (const entry of value.required) addEntry(entry, true); + } + if (Array.isArray(value.optional)) { + for (const entry of value.optional) addEntry(entry, false); + } + const schema: Record = { type: "object", properties }; + if (required.length > 0) schema.required = required; + return schema; + } + + if (typeof value.domain === "string") return { type: value.domain }; + return {}; +} + /** Symbol-stamped caches keyed by schema object identity. */ const kZodWireSchema = Symbol("pi.schema.zod.wire"); const kJsonWireSchema = Symbol("pi.schema.json.wire"); @@ -87,12 +169,14 @@ const kArkWireSchema = Symbol("pi.schema.ark.wire"); function postProcess(schema: Record): Record { delete schema.$schema; walk(schema, true); + normalizeArkPropertyComments(schema); normalizeEmptySchemas(schema); return schema; } function postProcessJsonSchema(schema: Record): Record { walk(schema, false); + normalizeArkPropertyComments(schema); normalizeEmptySchemas(schema); return schema; } @@ -209,6 +293,51 @@ const SCHEMA_MAP_KEYS = ["properties", "patternProperties", "$defs", "definition /** Keys whose values are an array of schemas. */ const SCHEMA_ARRAY_KEYS = ["anyOf", "oneOf", "allOf", "prefixItems"] as const; +function normalizeArkPropertyComments(node: unknown): void { + if (Array.isArray(node)) { + for (const child of node) normalizeArkPropertyComments(child); + return; + } + if (!isSchemaRecord(node)) return; + const obj = node as Record; + + const properties = obj.properties; + if (isSchemaRecord(properties)) { + const required = Array.isArray(obj.required) ? obj.required : undefined; + if (required) { + obj.required = required.map(key => (typeof key === "string" ? parseArkObjectKey(key).name : key)); + } + for (const key of Object.keys(properties)) { + const parsed = parseArkObjectKey(key); + const targetKey = parsed.name; + let propertySchema = properties[key]; + if (parsed.description) { + propertySchema = withArkKeyDescription(propertySchema, parsed.description); + delete properties[key]; + properties[targetKey] = propertySchema; + } + normalizeArkPropertyComments(propertySchema); + } + } + + for (const key of SCHEMA_VALUE_KEYS) { + if (Object.hasOwn(obj, key)) normalizeArkPropertyComments(obj[key]); + } + for (const mapKey of SCHEMA_MAP_KEYS) { + if (mapKey === "properties") continue; + const map = obj[mapKey]; + if (isSchemaRecord(map)) { + for (const key in map) normalizeArkPropertyComments(map[key]); + } + } + for (const arrayKey of SCHEMA_ARRAY_KEYS) { + const array = obj[arrayKey]; + if (Array.isArray(array)) { + for (const child of array) normalizeArkPropertyComments(child); + } + } +} + /** True when `val` is a plain empty object `{}`. */ function isEmptyObject(val: unknown): val is Record { if (val === null || typeof val !== "object" || Array.isArray(val)) return false; @@ -456,7 +585,8 @@ export function toolWireSchema(tool: Tool): Record { if (isArkSchema(params)) return arkToWireSchema(params); if (isZodSchema(params)) return zodToWireSchema(params); return stamp(params as Record, kJsonWireSchema, p => { - const upgraded = upgradeJsonSchemaTo202012(p) as Record; + const raw = isArkJsonAst(p) ? arkJsonAstToWire(p) : p; + const upgraded = upgradeJsonSchemaTo202012(raw) as Record; return postProcessJsonSchema(upgraded); }); }