@ai-matrx/content-ir 0.9.0 → 0.10.0
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.
- package/CHANGELOG.md +63 -0
- package/README.md +24 -5
- package/dist/convert.cjs +1680 -0
- package/dist/convert.cjs.map +1 -0
- package/dist/convert.d.cts +230 -0
- package/dist/convert.d.ts +230 -0
- package/dist/convert.js +1666 -0
- package/dist/convert.js.map +1 -0
- package/dist/core.cjs +2493 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +370 -0
- package/dist/core.d.ts +370 -0
- package/dist/core.js +2452 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -2030
- package/dist/index.d.ts +9 -2030
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ir-tree-DbLVxbf1.d.cts +441 -0
- package/dist/ir-tree-Dsc_66ek.d.ts +441 -0
- package/dist/ir-types-95bA2cXH.d.cts +119 -0
- package/dist/ir-types-95bA2cXH.d.ts +119 -0
- package/dist/kind-schema.types-CwncWj9U.d.cts +139 -0
- package/dist/kind-schema.types-CwncWj9U.d.ts +139 -0
- package/dist/registry.cjs +468 -0
- package/dist/registry.cjs.map +1 -0
- package/dist/registry.d.cts +357 -0
- package/dist/registry.d.ts +357 -0
- package/dist/registry.js +456 -0
- package/dist/registry.js.map +1 -0
- package/dist/session.cjs +2052 -0
- package/dist/session.cjs.map +1 -0
- package/dist/session.d.cts +75 -0
- package/dist/session.d.ts +75 -0
- package/dist/session.js +2047 -0
- package/dist/session.js.map +1 -0
- package/dist/wire.cjs +310 -0
- package/dist/wire.cjs.map +1 -0
- package/dist/wire.d.cts +326 -0
- package/dist/wire.d.ts +326 -0
- package/dist/wire.js +291 -0
- package/dist/wire.js.map +1 -0
- package/package.json +73 -1
package/dist/convert.cjs
ADDED
|
@@ -0,0 +1,1680 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// core/kind-schema.types.ts
|
|
4
|
+
var KIND_KEY = "__kind";
|
|
5
|
+
function scalarArrayItemType(type) {
|
|
6
|
+
if (type === "number[]") return "number";
|
|
7
|
+
if (type === "boolean[]") return "boolean";
|
|
8
|
+
return "string";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// utils/text-case-converter.ts
|
|
12
|
+
var DEFAULT_WORD_REPLACEMENTS = {
|
|
13
|
+
// Acronyms & initialisms
|
|
14
|
+
"api": "API",
|
|
15
|
+
"apis": "APIs",
|
|
16
|
+
"ui": "UI",
|
|
17
|
+
"ux": "UX",
|
|
18
|
+
"id": "ID",
|
|
19
|
+
"ids": "IDs",
|
|
20
|
+
"qr": "QR",
|
|
21
|
+
"ssr": "SSR",
|
|
22
|
+
"csr": "CSR",
|
|
23
|
+
"ssg": "SSG",
|
|
24
|
+
"isr": "ISR",
|
|
25
|
+
"spa": "SPA",
|
|
26
|
+
"pwa": "PWA",
|
|
27
|
+
"sdk": "SDK",
|
|
28
|
+
"sdks": "SDKs",
|
|
29
|
+
"cli": "CLI",
|
|
30
|
+
"tty": "TTY",
|
|
31
|
+
"repl": "REPL",
|
|
32
|
+
"ci": "CI",
|
|
33
|
+
"cd": "CD",
|
|
34
|
+
"cpu": "CPU",
|
|
35
|
+
"cpus": "CPUs",
|
|
36
|
+
"gpu": "GPU",
|
|
37
|
+
"gpus": "GPUs",
|
|
38
|
+
"ram": "RAM",
|
|
39
|
+
"rom": "ROM",
|
|
40
|
+
"ssd": "SSD",
|
|
41
|
+
"ssds": "SSDs",
|
|
42
|
+
"hdd": "HDD",
|
|
43
|
+
"hdds": "HDDs",
|
|
44
|
+
"kpi": "KPI",
|
|
45
|
+
"kpis": "KPIs",
|
|
46
|
+
"sla": "SLA",
|
|
47
|
+
"slas": "SLAs",
|
|
48
|
+
"slo": "SLO",
|
|
49
|
+
"slos": "SLOs",
|
|
50
|
+
"sli": "SLI",
|
|
51
|
+
"slis": "SLIs",
|
|
52
|
+
"dom": "DOM",
|
|
53
|
+
// Web, formats, protocols
|
|
54
|
+
"url": "URL",
|
|
55
|
+
"urls": "URLs",
|
|
56
|
+
"uri": "URI",
|
|
57
|
+
"uris": "URIs",
|
|
58
|
+
"http": "HTTP",
|
|
59
|
+
"https": "HTTPS",
|
|
60
|
+
"html": "HTML",
|
|
61
|
+
"css": "CSS",
|
|
62
|
+
"json": "JSON",
|
|
63
|
+
"yaml": "YAML",
|
|
64
|
+
"yml": "YML",
|
|
65
|
+
"toml": "TOML",
|
|
66
|
+
"csv": "CSV",
|
|
67
|
+
"pdf": "PDF",
|
|
68
|
+
"tsv": "TSV",
|
|
69
|
+
"jpg": "JPG",
|
|
70
|
+
"jpeg": "JPEG",
|
|
71
|
+
"png": "PNG",
|
|
72
|
+
"gif": "GIF",
|
|
73
|
+
"webp": "WebP",
|
|
74
|
+
"heic": "HEIC",
|
|
75
|
+
"heif": "HEIF",
|
|
76
|
+
"bmp": "BMP",
|
|
77
|
+
"tiff": "TIFF",
|
|
78
|
+
"ico": "ICO",
|
|
79
|
+
"xml": "XML",
|
|
80
|
+
"sql": "SQL",
|
|
81
|
+
"db": "DB",
|
|
82
|
+
"dbs": "DBs",
|
|
83
|
+
"nosql": "NoSQL",
|
|
84
|
+
"graphql": "GraphQL",
|
|
85
|
+
"grpc": "gRPC",
|
|
86
|
+
"rest": "REST",
|
|
87
|
+
"restful": "RESTful",
|
|
88
|
+
"websocket": "WebSocket",
|
|
89
|
+
"websockets": "WebSockets",
|
|
90
|
+
"webrtc": "WebRTC",
|
|
91
|
+
// Networking
|
|
92
|
+
"ip": "IP",
|
|
93
|
+
"ipv4": "IPv4",
|
|
94
|
+
"ipv6": "IPv6",
|
|
95
|
+
"dns": "DNS",
|
|
96
|
+
"dhcp": "DHCP",
|
|
97
|
+
"nat": "NAT",
|
|
98
|
+
"tcp": "TCP",
|
|
99
|
+
"udp": "UDP",
|
|
100
|
+
"icmp": "ICMP",
|
|
101
|
+
"ttl": "TTL",
|
|
102
|
+
"lan": "LAN",
|
|
103
|
+
"wan": "WAN",
|
|
104
|
+
"vlan": "VLAN",
|
|
105
|
+
"cdn": "CDN",
|
|
106
|
+
"ftp": "FTP",
|
|
107
|
+
"ssh": "SSH",
|
|
108
|
+
"tls": "TLS",
|
|
109
|
+
"ssl": "SSL",
|
|
110
|
+
// Security & crypto
|
|
111
|
+
"jwt": "JWT",
|
|
112
|
+
"jws": "JWS",
|
|
113
|
+
"jwe": "JWE",
|
|
114
|
+
"hmac": "HMAC",
|
|
115
|
+
"rsa": "RSA",
|
|
116
|
+
"ecdsa": "ECDSA",
|
|
117
|
+
"aes": "AES",
|
|
118
|
+
"pbkdf2": "PBKDF2",
|
|
119
|
+
"argon2": "Argon2",
|
|
120
|
+
"scrypt": "scrypt",
|
|
121
|
+
"totp": "TOTP",
|
|
122
|
+
"hotp": "HOTP",
|
|
123
|
+
"mfa": "MFA",
|
|
124
|
+
"2fa": "2FA",
|
|
125
|
+
"csrf": "CSRF",
|
|
126
|
+
"xss": "XSS",
|
|
127
|
+
"ssrf": "SSRF",
|
|
128
|
+
"rce": "RCE",
|
|
129
|
+
"dos": "DoS",
|
|
130
|
+
"ddos": "DDoS",
|
|
131
|
+
"mitm": "MITM",
|
|
132
|
+
"csp": "CSP",
|
|
133
|
+
"cors": "CORS",
|
|
134
|
+
"pii": "PII",
|
|
135
|
+
"phi": "PHI",
|
|
136
|
+
"gdpr": "GDPR",
|
|
137
|
+
"ccpa": "CCPA",
|
|
138
|
+
"hipaa": "HIPAA",
|
|
139
|
+
"rfc": "RFC",
|
|
140
|
+
// Platforms, langs, tools (single-token)
|
|
141
|
+
"javascript": "JavaScript",
|
|
142
|
+
"typescript": "TypeScript",
|
|
143
|
+
"jsx": "JSX",
|
|
144
|
+
"tsx": "TSX",
|
|
145
|
+
"node": "Node",
|
|
146
|
+
// (used when tokenized alone)
|
|
147
|
+
"deno": "Deno",
|
|
148
|
+
"bun": "Bun",
|
|
149
|
+
"react": "React",
|
|
150
|
+
"nextjs": "Next.js",
|
|
151
|
+
// if your tokenizer drops dots, keep this
|
|
152
|
+
"nodejs": "Node.js",
|
|
153
|
+
"postgresql": "PostgreSQL",
|
|
154
|
+
"postgres": "Postgres",
|
|
155
|
+
"mysql": "MySQL",
|
|
156
|
+
"sqlite": "SQLite",
|
|
157
|
+
"redis": "Redis",
|
|
158
|
+
"supabase": "Supabase",
|
|
159
|
+
"docker": "Docker",
|
|
160
|
+
"kubernetes": "Kubernetes",
|
|
161
|
+
"k8s": "Kubernetes",
|
|
162
|
+
"helm": "Helm",
|
|
163
|
+
"npm": "npm",
|
|
164
|
+
"pnpm": "pnpm",
|
|
165
|
+
"yarn": "Yarn",
|
|
166
|
+
"eslint": "ESLint",
|
|
167
|
+
"prettier": "Prettier",
|
|
168
|
+
"vite": "Vite",
|
|
169
|
+
"webpack": "Webpack",
|
|
170
|
+
"babel": "Babel",
|
|
171
|
+
// OS & vendors
|
|
172
|
+
"macos": "macOS",
|
|
173
|
+
"ios": "iOS",
|
|
174
|
+
"ipados": "iPadOS",
|
|
175
|
+
"watchos": "watchOS",
|
|
176
|
+
"tvos": "tvOS",
|
|
177
|
+
"windows": "Windows",
|
|
178
|
+
"linux": "Linux",
|
|
179
|
+
"ubuntu": "Ubuntu",
|
|
180
|
+
"github": "GitHub",
|
|
181
|
+
"gitlab": "GitLab",
|
|
182
|
+
"bitbucket": "Bitbucket",
|
|
183
|
+
// Data & analytics
|
|
184
|
+
"etl": "ETL",
|
|
185
|
+
"elt": "ELT",
|
|
186
|
+
"olap": "OLAP",
|
|
187
|
+
"oltp": "OLTP",
|
|
188
|
+
"bi": "BI",
|
|
189
|
+
// Time & locales
|
|
190
|
+
"utc": "UTC",
|
|
191
|
+
"gmt": "GMT",
|
|
192
|
+
"pst": "PST",
|
|
193
|
+
"pdt": "PDT",
|
|
194
|
+
"pt": "PT",
|
|
195
|
+
// Common “small words” to keep lowercase (unless first/last word)
|
|
196
|
+
"or": "or",
|
|
197
|
+
"and": "and",
|
|
198
|
+
"the": "the",
|
|
199
|
+
"of": "of",
|
|
200
|
+
"in": "in",
|
|
201
|
+
"to": "to",
|
|
202
|
+
"with": "with",
|
|
203
|
+
"as": "as",
|
|
204
|
+
"by": "by",
|
|
205
|
+
"for": "for",
|
|
206
|
+
"on": "on",
|
|
207
|
+
"at": "at",
|
|
208
|
+
"up": "up",
|
|
209
|
+
"a": "a",
|
|
210
|
+
"an": "an",
|
|
211
|
+
"is": "is",
|
|
212
|
+
"are": "are",
|
|
213
|
+
"was": "was",
|
|
214
|
+
"were": "were",
|
|
215
|
+
"be": "be",
|
|
216
|
+
"but": "but",
|
|
217
|
+
"nor": "nor",
|
|
218
|
+
"so": "so",
|
|
219
|
+
"yet": "yet",
|
|
220
|
+
"per": "per",
|
|
221
|
+
"via": "via",
|
|
222
|
+
// Latin abbreviations (tokenized as words in some pipelines)
|
|
223
|
+
"eg": "e.g.",
|
|
224
|
+
"ie": "i.e.",
|
|
225
|
+
"etc": "etc.",
|
|
226
|
+
"aka": "aka",
|
|
227
|
+
"vs": "vs.",
|
|
228
|
+
"v": "v.",
|
|
229
|
+
// Client abbreviations
|
|
230
|
+
"CIC": "CIC",
|
|
231
|
+
"AGR": "AGR",
|
|
232
|
+
"AGER": "AGER",
|
|
233
|
+
"DD": "DD",
|
|
234
|
+
"TS": "TS",
|
|
235
|
+
"TM": "TM",
|
|
236
|
+
"arman": "Arman"
|
|
237
|
+
};
|
|
238
|
+
var DEFAULT_OPTIONS = {
|
|
239
|
+
textCase: "title",
|
|
240
|
+
wordReplacements: DEFAULT_WORD_REPLACEMENTS,
|
|
241
|
+
trim: true
|
|
242
|
+
};
|
|
243
|
+
function formatText(text, options = {}) {
|
|
244
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
245
|
+
if (!text) return "";
|
|
246
|
+
let normalized = text.replace(/_/g, " ").replace(/-/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ");
|
|
247
|
+
if (opts.trim) {
|
|
248
|
+
normalized = normalized.trim();
|
|
249
|
+
}
|
|
250
|
+
let caseTransformed = normalized;
|
|
251
|
+
switch (opts.textCase) {
|
|
252
|
+
case "title":
|
|
253
|
+
caseTransformed = normalized.replace(
|
|
254
|
+
/\w\S*/g,
|
|
255
|
+
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
256
|
+
);
|
|
257
|
+
break;
|
|
258
|
+
case "sentence":
|
|
259
|
+
if (normalized.length > 0) {
|
|
260
|
+
caseTransformed = normalized.charAt(0).toUpperCase() + normalized.slice(1).toLowerCase();
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case "lower":
|
|
264
|
+
caseTransformed = normalized.toLowerCase();
|
|
265
|
+
break;
|
|
266
|
+
case "upper":
|
|
267
|
+
caseTransformed = normalized.toUpperCase();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
let result = caseTransformed;
|
|
271
|
+
if (opts.wordReplacements) {
|
|
272
|
+
Object.entries(opts.wordReplacements).forEach(([key, value]) => {
|
|
273
|
+
const regex = new RegExp(`\\b${key}\\b`, "gi");
|
|
274
|
+
result = result.replace(regex, value);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// core/schema-structure.ts
|
|
281
|
+
function formatBlockLabel(key) {
|
|
282
|
+
return formatText(key, { textCase: "title" });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// convert/openai-schema-converter.ts
|
|
286
|
+
function requiredNullableFlags(required, nullable) {
|
|
287
|
+
return {
|
|
288
|
+
...required ? { required: true } : {},
|
|
289
|
+
...nullable ? { nullable: true } : {}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
var METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
293
|
+
"description",
|
|
294
|
+
"title",
|
|
295
|
+
"default",
|
|
296
|
+
"examples",
|
|
297
|
+
"format",
|
|
298
|
+
"minimum",
|
|
299
|
+
"maximum",
|
|
300
|
+
"multipleOf",
|
|
301
|
+
"exclusiveMinimum",
|
|
302
|
+
"exclusiveMaximum",
|
|
303
|
+
"minLength",
|
|
304
|
+
"maxLength",
|
|
305
|
+
"minItems",
|
|
306
|
+
"maxItems",
|
|
307
|
+
"pattern",
|
|
308
|
+
"const",
|
|
309
|
+
"$schema",
|
|
310
|
+
"$id",
|
|
311
|
+
"deprecated",
|
|
312
|
+
"readOnly",
|
|
313
|
+
"writeOnly"
|
|
314
|
+
]);
|
|
315
|
+
function isRecord(value) {
|
|
316
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
317
|
+
}
|
|
318
|
+
function deepClone(value) {
|
|
319
|
+
return JSON.parse(JSON.stringify(value));
|
|
320
|
+
}
|
|
321
|
+
function fieldSchemaSummary(field) {
|
|
322
|
+
if (field.type === "enum") {
|
|
323
|
+
return `enum(${field.values.join("|")}${field.open ? "|+any" : ""})${field.required ? "*" : ""}`;
|
|
324
|
+
}
|
|
325
|
+
if (field.type === "array") {
|
|
326
|
+
return `array<${field.itemKinds.join("|")}>${field.required ? "*" : ""}`;
|
|
327
|
+
}
|
|
328
|
+
if (field.type === "inline_object") {
|
|
329
|
+
return `inline{${Object.keys(field.fields).join(",")}}${field.required ? "*" : ""}`;
|
|
330
|
+
}
|
|
331
|
+
if (field.type === "object") {
|
|
332
|
+
return `object:${field.kind}${field.required ? "*" : ""}`;
|
|
333
|
+
}
|
|
334
|
+
if (field.type === "union") {
|
|
335
|
+
const members = [...field.scalars, ...field.kinds ?? []];
|
|
336
|
+
return `union(${members.join("|")})${field.required ? "*" : ""}`;
|
|
337
|
+
}
|
|
338
|
+
if (field.type === "record") {
|
|
339
|
+
return `record(${field.values})${field.required ? "*" : ""}`;
|
|
340
|
+
}
|
|
341
|
+
return `${field.type}${field.required ? "*" : ""}${field.nullable ? "?" : ""}`;
|
|
342
|
+
}
|
|
343
|
+
function resolvePrimaryType(node) {
|
|
344
|
+
const raw = node.type;
|
|
345
|
+
if (typeof raw === "string") {
|
|
346
|
+
const t = raw === "integer" ? "number" : raw;
|
|
347
|
+
return { type: t, nullable: false, members: [t] };
|
|
348
|
+
}
|
|
349
|
+
if (Array.isArray(raw)) {
|
|
350
|
+
const types = raw.filter((t) => typeof t === "string");
|
|
351
|
+
const nullable = types.includes("null");
|
|
352
|
+
const members = [
|
|
353
|
+
...new Set(
|
|
354
|
+
types.filter((t) => t !== "null").map((t) => t === "integer" ? "number" : t)
|
|
355
|
+
)
|
|
356
|
+
];
|
|
357
|
+
const primary = members[0] ?? (nullable && types.length === 1 ? "null" : null);
|
|
358
|
+
return { type: primary ?? null, nullable, members };
|
|
359
|
+
}
|
|
360
|
+
if (node.enum) return { type: "string", nullable: false, members: ["string"] };
|
|
361
|
+
if (node.properties)
|
|
362
|
+
return { type: "object", nullable: false, members: ["object"] };
|
|
363
|
+
if (node.items)
|
|
364
|
+
return { type: "array", nullable: false, members: ["array"] };
|
|
365
|
+
return { type: null, nullable: false, members: [] };
|
|
366
|
+
}
|
|
367
|
+
function carriedMetadataKeys(field) {
|
|
368
|
+
if (field === null) return /* @__PURE__ */ new Set();
|
|
369
|
+
const carried = /* @__PURE__ */ new Set(["description", "default"]);
|
|
370
|
+
if (field.type === "number") {
|
|
371
|
+
carried.add("minimum");
|
|
372
|
+
carried.add("maximum");
|
|
373
|
+
carried.add("multipleOf");
|
|
374
|
+
}
|
|
375
|
+
return carried;
|
|
376
|
+
}
|
|
377
|
+
function collectDropped(node, path, carriedField = null) {
|
|
378
|
+
const carried = carriedMetadataKeys(carriedField);
|
|
379
|
+
const dropped = {};
|
|
380
|
+
for (const [key, value] of Object.entries(node)) {
|
|
381
|
+
if (METADATA_KEYS.has(key) && !carried.has(key)) {
|
|
382
|
+
dropped[key] = value;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (Object.keys(dropped).length > 0) {
|
|
386
|
+
return [{ path, dropped }];
|
|
387
|
+
}
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
function attachNodeMetadata(node, field) {
|
|
391
|
+
let out = field;
|
|
392
|
+
if (typeof node.description === "string" && out.description === void 0) {
|
|
393
|
+
out = { ...out, description: node.description };
|
|
394
|
+
}
|
|
395
|
+
if (node.default !== void 0 && out.default === void 0) {
|
|
396
|
+
out = { ...out, default: node.default };
|
|
397
|
+
}
|
|
398
|
+
if (out.type === "number") {
|
|
399
|
+
const bounds = {};
|
|
400
|
+
if (typeof node.minimum === "number" && out.min === void 0) {
|
|
401
|
+
bounds.min = node.minimum;
|
|
402
|
+
}
|
|
403
|
+
if (typeof node.maximum === "number" && out.max === void 0) {
|
|
404
|
+
bounds.max = node.maximum;
|
|
405
|
+
}
|
|
406
|
+
if (typeof node.multipleOf === "number" && out.step === void 0) {
|
|
407
|
+
bounds.step = node.multipleOf;
|
|
408
|
+
}
|
|
409
|
+
if (Object.keys(bounds).length > 0) out = { ...out, ...bounds };
|
|
410
|
+
}
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
413
|
+
function synthesizeItemKindSlug(schemaName, fieldName) {
|
|
414
|
+
const base = fieldName.endsWith("ies") ? `${fieldName.slice(0, -3)}y` : fieldName.endsWith("s") ? fieldName.slice(0, -1) : fieldName;
|
|
415
|
+
return `${schemaName}_${base}`;
|
|
416
|
+
}
|
|
417
|
+
function makeKindJsonSchemaProperty(kindSlug, strict) {
|
|
418
|
+
const base = {
|
|
419
|
+
type: "string",
|
|
420
|
+
description: "Block discriminator for render pipeline."
|
|
421
|
+
};
|
|
422
|
+
if (strict) {
|
|
423
|
+
return { ...base, const: kindSlug };
|
|
424
|
+
}
|
|
425
|
+
return { ...base, enum: [kindSlug] };
|
|
426
|
+
}
|
|
427
|
+
function injectKindIntoObjectSchema(objectSchema, kindSlug, strict) {
|
|
428
|
+
const clone = deepClone(objectSchema);
|
|
429
|
+
const properties = isRecord(clone.properties) ? { ...clone.properties } : {};
|
|
430
|
+
properties[KIND_KEY] = makeKindJsonSchemaProperty(kindSlug, strict);
|
|
431
|
+
const required = Array.isArray(clone.required) ? clone.required.filter((k) => typeof k === "string") : [];
|
|
432
|
+
const nextRequired = required.includes(KIND_KEY) ? required : [KIND_KEY, ...required];
|
|
433
|
+
return {
|
|
434
|
+
...clone,
|
|
435
|
+
properties,
|
|
436
|
+
required: nextRequired,
|
|
437
|
+
...strict ? { additionalProperties: false } : {}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function injectKindsIntoRootSchema(rootSchema, rootKindSlug, arrayBindings, strict) {
|
|
441
|
+
let updated = injectKindIntoObjectSchema(rootSchema, rootKindSlug, strict);
|
|
442
|
+
if (!isRecord(updated.properties)) {
|
|
443
|
+
return updated;
|
|
444
|
+
}
|
|
445
|
+
const properties = { ...updated.properties };
|
|
446
|
+
for (const binding of arrayBindings) {
|
|
447
|
+
const fieldNode = properties[binding.arrayField];
|
|
448
|
+
if (!isRecord(fieldNode)) continue;
|
|
449
|
+
const items = fieldNode.items;
|
|
450
|
+
const itemNode = Array.isArray(items) ? items[0] : items;
|
|
451
|
+
if (!isRecord(itemNode)) continue;
|
|
452
|
+
const updatedItem = injectKindIntoObjectSchema(
|
|
453
|
+
itemNode,
|
|
454
|
+
binding.itemKindSlug,
|
|
455
|
+
strict
|
|
456
|
+
);
|
|
457
|
+
properties[binding.arrayField] = {
|
|
458
|
+
...fieldNode,
|
|
459
|
+
items: updatedItem
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return { ...updated, properties };
|
|
463
|
+
}
|
|
464
|
+
function buildAgentSchemaWithRenderBlockSupport(input, rootKindSlug, arrayBindings, strict) {
|
|
465
|
+
if (!isRecord(input)) return null;
|
|
466
|
+
const normalized = normalizeAiSchemaInput(input);
|
|
467
|
+
if (!normalized.rootSchema) return null;
|
|
468
|
+
if (normalized.rootSchema.type !== "object" && !isRecord(normalized.rootSchema.properties)) {
|
|
469
|
+
return deepClone(input);
|
|
470
|
+
}
|
|
471
|
+
const updatedRoot = injectKindsIntoRootSchema(
|
|
472
|
+
normalized.rootSchema,
|
|
473
|
+
rootKindSlug,
|
|
474
|
+
arrayBindings,
|
|
475
|
+
strict
|
|
476
|
+
);
|
|
477
|
+
if (isRecord(input.schema) && typeof input.name === "string") {
|
|
478
|
+
return {
|
|
479
|
+
...deepClone(input),
|
|
480
|
+
schema: updatedRoot
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (isRecord(input.json_schema)) {
|
|
484
|
+
const inner = buildAgentSchemaWithRenderBlockSupport(
|
|
485
|
+
input.json_schema,
|
|
486
|
+
rootKindSlug,
|
|
487
|
+
arrayBindings,
|
|
488
|
+
strict
|
|
489
|
+
);
|
|
490
|
+
return inner ? { ...deepClone(input), json_schema: inner } : null;
|
|
491
|
+
}
|
|
492
|
+
if (typeof input.name === "string") {
|
|
493
|
+
return {
|
|
494
|
+
...deepClone(input),
|
|
495
|
+
schema: updatedRoot
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return updatedRoot;
|
|
499
|
+
}
|
|
500
|
+
function resolveRef(ref, ctx) {
|
|
501
|
+
if (ref === "#" || ref === "#/") {
|
|
502
|
+
return { slug: ctx.schemaName, node: ctx.rootSchema };
|
|
503
|
+
}
|
|
504
|
+
const match = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
|
|
505
|
+
if (!match) return null;
|
|
506
|
+
const name = decodeURIComponent(
|
|
507
|
+
(match[1] ?? "").replace(/~1/g, "/").replace(/~0/g, "~")
|
|
508
|
+
);
|
|
509
|
+
const node = ctx.defs[name];
|
|
510
|
+
if (!isRecord(node)) return null;
|
|
511
|
+
const declared = isRecord(node.properties) ? readBlockKindFromProperties(node.properties) : null;
|
|
512
|
+
return { slug: declared ?? name, node };
|
|
513
|
+
}
|
|
514
|
+
function refToObjectField(resolved, path, ctx) {
|
|
515
|
+
if (ctx.refsInProgress.has(resolved.slug)) return;
|
|
516
|
+
ctx.refsInProgress.add(resolved.slug);
|
|
517
|
+
try {
|
|
518
|
+
registerDeclaredKindDraft(resolved.slug, resolved.node, path, ctx);
|
|
519
|
+
} finally {
|
|
520
|
+
ctx.refsInProgress.delete(resolved.slug);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function isAnyValueSchema(node) {
|
|
524
|
+
return node.type === void 0 && node.enum === void 0 && node.const === void 0 && node.properties === void 0 && node.items === void 0 && node.additionalProperties === void 0 && node.anyOf === void 0 && node.oneOf === void 0 && node.allOf === void 0 && node.$ref === void 0;
|
|
525
|
+
}
|
|
526
|
+
function registerDeclaredKindDraft(declaredKind, objectNode, path, ctx) {
|
|
527
|
+
if (ctx.blockSchemas.some((draft) => draft.slug === declaredKind)) return;
|
|
528
|
+
if (!isRecord(objectNode.properties)) return;
|
|
529
|
+
const memberRequired = new Set(
|
|
530
|
+
Array.isArray(objectNode.required) ? objectNode.required.filter((k) => typeof k === "string") : []
|
|
531
|
+
);
|
|
532
|
+
const fields = {};
|
|
533
|
+
for (const [propName, propNode] of Object.entries(objectNode.properties)) {
|
|
534
|
+
if (!isRecord(propNode)) continue;
|
|
535
|
+
if (propName === KIND_KEY) continue;
|
|
536
|
+
fields[propName] = convertFieldOrWiden(
|
|
537
|
+
propName,
|
|
538
|
+
propNode,
|
|
539
|
+
memberRequired.has(propName),
|
|
540
|
+
`${path}.${propName}`,
|
|
541
|
+
ctx
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
ctx.blockSchemas.push({
|
|
545
|
+
slug: declaredKind,
|
|
546
|
+
label: formatBlockLabel(declaredKind),
|
|
547
|
+
fields
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
function convertFieldOrWiden(fieldName, node, required, path, ctx) {
|
|
551
|
+
const before = ctx.problems.length;
|
|
552
|
+
const converted = convertProperty(fieldName, node, required, path, ctx);
|
|
553
|
+
if (converted) return converted;
|
|
554
|
+
for (let i = before; i < ctx.problems.length; i++) {
|
|
555
|
+
const problem = ctx.problems[i];
|
|
556
|
+
if (problem && problem.severity === "error") problem.severity = "warning";
|
|
557
|
+
}
|
|
558
|
+
ctx.problems.push({
|
|
559
|
+
severity: "warning",
|
|
560
|
+
path,
|
|
561
|
+
message: `Field "${fieldName}" has no exact field type \u2014 carried as any JSON value (json) rather than dropped from the kind.`
|
|
562
|
+
});
|
|
563
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
564
|
+
}
|
|
565
|
+
function convertProperty(fieldName, node, required, path, ctx) {
|
|
566
|
+
const core = convertPropertyCore(fieldName, node, required, path, ctx);
|
|
567
|
+
const field = core === null ? null : attachNodeMetadata(node, core);
|
|
568
|
+
ctx.droppedMetadata.push(...collectDropped(node, path, field));
|
|
569
|
+
return field;
|
|
570
|
+
}
|
|
571
|
+
function convertPropertyCore(fieldName, node, required, path, ctx) {
|
|
572
|
+
if (typeof node.$ref === "string") {
|
|
573
|
+
const resolved = resolveRef(node.$ref, ctx);
|
|
574
|
+
if (!resolved) {
|
|
575
|
+
ctx.problems.push({
|
|
576
|
+
severity: "error",
|
|
577
|
+
path,
|
|
578
|
+
message: `Unresolvable $ref ("${node.$ref}") \u2014 only "#", "#/$defs/<name>" and "#/definitions/<name>" are supported.`
|
|
579
|
+
});
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
refToObjectField(resolved, path, ctx);
|
|
583
|
+
return {
|
|
584
|
+
...requiredNullableFlags(required),
|
|
585
|
+
type: "object",
|
|
586
|
+
kind: resolved.slug
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (Array.isArray(node.anyOf) || Array.isArray(node.oneOf)) {
|
|
590
|
+
const variants = node.anyOf ?? node.oneOf;
|
|
591
|
+
const scalarTypes = /* @__PURE__ */ new Set();
|
|
592
|
+
const enumVariantSets = [];
|
|
593
|
+
const memberKinds = [];
|
|
594
|
+
const anonymousObjects = [];
|
|
595
|
+
let sawNull = false;
|
|
596
|
+
let unsupported = false;
|
|
597
|
+
for (const variant of variants) {
|
|
598
|
+
if (!isRecord(variant)) {
|
|
599
|
+
unsupported = true;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (typeof variant.$ref === "string") {
|
|
603
|
+
const resolvedVariant = resolveRef(variant.$ref, ctx);
|
|
604
|
+
if (!resolvedVariant) {
|
|
605
|
+
ctx.problems.push({
|
|
606
|
+
severity: "error",
|
|
607
|
+
path,
|
|
608
|
+
message: `Unresolvable $ref inside anyOf/oneOf ("${variant.$ref}").`
|
|
609
|
+
});
|
|
610
|
+
unsupported = true;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
refToObjectField(resolvedVariant, path, ctx);
|
|
614
|
+
memberKinds.push(resolvedVariant.slug);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const { type: type2, nullable: nullable2 } = resolvePrimaryType(variant);
|
|
618
|
+
if (nullable2) sawNull = true;
|
|
619
|
+
if (type2 === "null") {
|
|
620
|
+
sawNull = true;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (type2 === "string" && Array.isArray(variant.enum)) {
|
|
624
|
+
enumVariantSets.push(
|
|
625
|
+
variant.enum.filter((v) => typeof v === "string")
|
|
626
|
+
);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (type2 === "string" || type2 === "number" || type2 === "boolean") {
|
|
630
|
+
scalarTypes.add(type2);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (type2 === "object" && isRecord(variant.properties)) {
|
|
634
|
+
const declared = readBlockKindFromProperties(variant.properties);
|
|
635
|
+
if (declared) {
|
|
636
|
+
memberKinds.push(declared);
|
|
637
|
+
registerDeclaredKindDraft(declared, variant, `${path}<${declared}>`, ctx);
|
|
638
|
+
} else {
|
|
639
|
+
anonymousObjects.push(variant);
|
|
640
|
+
}
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
unsupported = true;
|
|
644
|
+
}
|
|
645
|
+
if (unsupported) {
|
|
646
|
+
ctx.problems.push({
|
|
647
|
+
severity: "error",
|
|
648
|
+
path,
|
|
649
|
+
message: "anyOf/oneOf with unsupported variants cannot be converted automatically."
|
|
650
|
+
});
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
if (enumVariantSets.length > 0 && anonymousObjects.length === 0 && memberKinds.length === 0 && [...scalarTypes].every((s) => s === "string")) {
|
|
654
|
+
const values = [...new Set(enumVariantSets.flat())];
|
|
655
|
+
const open = scalarTypes.has("string");
|
|
656
|
+
return {
|
|
657
|
+
...requiredNullableFlags(required, sawNull),
|
|
658
|
+
type: "enum",
|
|
659
|
+
values,
|
|
660
|
+
...open ? { open: true } : {}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
if (enumVariantSets.length > 0) {
|
|
664
|
+
ctx.problems.push({
|
|
665
|
+
severity: "warning",
|
|
666
|
+
path,
|
|
667
|
+
message: "anyOf/oneOf mixes an enum variant with non-string members \u2014 enum widened to string within the union."
|
|
668
|
+
});
|
|
669
|
+
scalarTypes.add("string");
|
|
670
|
+
}
|
|
671
|
+
if (anonymousObjects.length === 1 && scalarTypes.size === 0 && memberKinds.length === 0) {
|
|
672
|
+
const [sole] = anonymousObjects;
|
|
673
|
+
const converted = sole ? convertProperty(fieldName, sole, required, path, ctx) : null;
|
|
674
|
+
if (!converted) return null;
|
|
675
|
+
return sawNull ? { ...converted, nullable: true } : converted;
|
|
676
|
+
}
|
|
677
|
+
if (anonymousObjects.length > 0) {
|
|
678
|
+
ctx.problems.push({
|
|
679
|
+
severity: "error",
|
|
680
|
+
path,
|
|
681
|
+
message: "anyOf/oneOf mixing anonymous objects with other variants cannot be converted \u2014 declare __kind on each object variant."
|
|
682
|
+
});
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
if (scalarTypes.size === 1 && memberKinds.length === 0) {
|
|
686
|
+
const [scalar] = [...scalarTypes];
|
|
687
|
+
if (scalar) {
|
|
688
|
+
return { ...requiredNullableFlags(required, sawNull), type: scalar };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (scalarTypes.size > 0 || memberKinds.length > 0) {
|
|
692
|
+
ctx.problems.push({
|
|
693
|
+
severity: "warning",
|
|
694
|
+
path,
|
|
695
|
+
message: `Converted anyOf/oneOf to union of: ${[
|
|
696
|
+
...scalarTypes,
|
|
697
|
+
...memberKinds
|
|
698
|
+
].join(", ")}.`
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
...requiredNullableFlags(required, sawNull),
|
|
702
|
+
type: "union",
|
|
703
|
+
scalars: [...scalarTypes],
|
|
704
|
+
...memberKinds.length > 0 ? { kinds: memberKinds } : {}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
ctx.problems.push({
|
|
708
|
+
severity: "warning",
|
|
709
|
+
path,
|
|
710
|
+
message: "anyOf/oneOf carried only null variants \u2014 widened to json (any value)."
|
|
711
|
+
});
|
|
712
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
713
|
+
}
|
|
714
|
+
if (Array.isArray(node.allOf)) {
|
|
715
|
+
ctx.problems.push({
|
|
716
|
+
severity: "warning",
|
|
717
|
+
path,
|
|
718
|
+
message: "allOf merged using first object branch only."
|
|
719
|
+
});
|
|
720
|
+
const objectBranch = node.allOf.find(
|
|
721
|
+
(b) => b.properties
|
|
722
|
+
);
|
|
723
|
+
if (objectBranch) {
|
|
724
|
+
return convertProperty(fieldName, objectBranch, required, path, ctx);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (isAnyValueSchema(node)) {
|
|
728
|
+
ctx.problems.push({
|
|
729
|
+
severity: "info",
|
|
730
|
+
path,
|
|
731
|
+
message: `Typeless schema at "${path || "(root)"}" \u2014 any JSON value (json).`
|
|
732
|
+
});
|
|
733
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
734
|
+
}
|
|
735
|
+
const { type, nullable, members } = resolvePrimaryType(node);
|
|
736
|
+
if (members.length > 1) {
|
|
737
|
+
const objectish = members.some((m) => m === "object" || m === "array");
|
|
738
|
+
if (objectish) {
|
|
739
|
+
ctx.problems.push({
|
|
740
|
+
severity: "info",
|
|
741
|
+
path,
|
|
742
|
+
message: `Union of [${members.join(", ")}] at "${path || "(root)"}" spans objects/arrays \u2014 carried as any JSON value (json).`
|
|
743
|
+
});
|
|
744
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
745
|
+
}
|
|
746
|
+
const scalars = members.filter(
|
|
747
|
+
(m) => m === "string" || m === "number" || m === "boolean"
|
|
748
|
+
);
|
|
749
|
+
if (scalars.length === members.length && scalars.length > 1) {
|
|
750
|
+
return {
|
|
751
|
+
...requiredNullableFlags(required, nullable),
|
|
752
|
+
type: "union",
|
|
753
|
+
scalars
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
ctx.problems.push({
|
|
757
|
+
severity: "warning",
|
|
758
|
+
path,
|
|
759
|
+
message: `Union of [${members.join(", ")}] at "${path || "(root)"}" has no exact field type \u2014 carried as any JSON value (json) rather than narrowed to "${type}".`
|
|
760
|
+
});
|
|
761
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
762
|
+
}
|
|
763
|
+
if (type === "string") {
|
|
764
|
+
if (Array.isArray(node.enum)) {
|
|
765
|
+
const values = node.enum.filter(
|
|
766
|
+
(v) => typeof v === "string"
|
|
767
|
+
);
|
|
768
|
+
if (values.length !== node.enum.length) {
|
|
769
|
+
ctx.problems.push({
|
|
770
|
+
severity: "warning",
|
|
771
|
+
path,
|
|
772
|
+
message: "Enum contains non-string values; non-strings dropped."
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
...requiredNullableFlags(required, nullable),
|
|
777
|
+
type: "enum",
|
|
778
|
+
values
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
return {
|
|
782
|
+
...requiredNullableFlags(required, nullable),
|
|
783
|
+
type: "string"
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
if (type === "number") {
|
|
787
|
+
return {
|
|
788
|
+
...requiredNullableFlags(required, nullable),
|
|
789
|
+
type: "number"
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
if (type === "boolean") {
|
|
793
|
+
return {
|
|
794
|
+
...requiredNullableFlags(required, nullable),
|
|
795
|
+
type: "boolean"
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
if (type === "array") {
|
|
799
|
+
const items = node.items;
|
|
800
|
+
if (!items) {
|
|
801
|
+
ctx.problems.push({
|
|
802
|
+
severity: "error",
|
|
803
|
+
path,
|
|
804
|
+
message: "Array field is missing items schema."
|
|
805
|
+
});
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
const itemNode = Array.isArray(items) ? items[0] : items;
|
|
809
|
+
if (!isRecord(itemNode)) {
|
|
810
|
+
ctx.problems.push({
|
|
811
|
+
severity: "error",
|
|
812
|
+
path,
|
|
813
|
+
message: "Array items schema must be an object."
|
|
814
|
+
});
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
if (typeof itemNode.$ref === "string") {
|
|
818
|
+
const resolvedItem = resolveRef(itemNode.$ref, ctx);
|
|
819
|
+
if (!resolvedItem) {
|
|
820
|
+
ctx.problems.push({
|
|
821
|
+
severity: "error",
|
|
822
|
+
path: `${path}[]`,
|
|
823
|
+
message: `Unresolvable $ref in array items ("${itemNode.$ref}").`
|
|
824
|
+
});
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
refToObjectField(resolvedItem, `${path}[]`, ctx);
|
|
828
|
+
ctx.arrayBindings.push({
|
|
829
|
+
arrayField: fieldName,
|
|
830
|
+
itemKindSlug: resolvedItem.slug
|
|
831
|
+
});
|
|
832
|
+
return {
|
|
833
|
+
...requiredNullableFlags(required, nullable),
|
|
834
|
+
type: "array",
|
|
835
|
+
itemKinds: [resolvedItem.slug]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
if (isAnyValueSchema(itemNode)) {
|
|
839
|
+
return {
|
|
840
|
+
...requiredNullableFlags(required, nullable),
|
|
841
|
+
type: "json[]"
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const itemVariants = Array.isArray(itemNode.anyOf) ? itemNode.anyOf : Array.isArray(itemNode.oneOf) ? itemNode.oneOf : null;
|
|
845
|
+
if (itemVariants) {
|
|
846
|
+
const itemEnumSets = [];
|
|
847
|
+
let itemPlainString = false;
|
|
848
|
+
let itemsAllString = true;
|
|
849
|
+
for (const variant of itemVariants) {
|
|
850
|
+
if (!isRecord(variant) || resolvePrimaryType(variant).type !== "string") {
|
|
851
|
+
itemsAllString = false;
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
if (Array.isArray(variant.enum)) {
|
|
855
|
+
itemEnumSets.push(
|
|
856
|
+
variant.enum.filter((v) => typeof v === "string")
|
|
857
|
+
);
|
|
858
|
+
} else {
|
|
859
|
+
itemPlainString = true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (itemsAllString && itemEnumSets.length > 0) {
|
|
863
|
+
return {
|
|
864
|
+
...requiredNullableFlags(required, nullable),
|
|
865
|
+
type: "string[]",
|
|
866
|
+
values: [...new Set(itemEnumSets.flat())],
|
|
867
|
+
...itemPlainString ? { open: true } : {}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const itemKinds = [];
|
|
871
|
+
for (const variant of itemVariants) {
|
|
872
|
+
if (isRecord(variant) && typeof variant.$ref === "string") {
|
|
873
|
+
const resolvedVariant = resolveRef(variant.$ref, ctx);
|
|
874
|
+
if (!resolvedVariant) {
|
|
875
|
+
ctx.problems.push({
|
|
876
|
+
severity: "error",
|
|
877
|
+
path: `${path}[]`,
|
|
878
|
+
message: `Unresolvable $ref in array items anyOf ("${variant.$ref}").`
|
|
879
|
+
});
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
itemKinds.push(resolvedVariant.slug);
|
|
883
|
+
refToObjectField(resolvedVariant, `${path}[]<${resolvedVariant.slug}>`, ctx);
|
|
884
|
+
ctx.arrayBindings.push({
|
|
885
|
+
arrayField: fieldName,
|
|
886
|
+
itemKindSlug: resolvedVariant.slug
|
|
887
|
+
});
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
if (!isRecord(variant) || !isRecord(variant.properties)) {
|
|
891
|
+
ctx.problems.push({
|
|
892
|
+
severity: "error",
|
|
893
|
+
path: `${path}[]`,
|
|
894
|
+
message: "Array items anyOf/oneOf variants must be inline objects declaring __kind, or $refs to defs."
|
|
895
|
+
});
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
const declared = readBlockKindFromProperties(variant.properties);
|
|
899
|
+
if (!declared) {
|
|
900
|
+
ctx.problems.push({
|
|
901
|
+
severity: "error",
|
|
902
|
+
path: `${path}[]`,
|
|
903
|
+
message: "Array items anyOf/oneOf variant is missing a __kind const/enum declaration."
|
|
904
|
+
});
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
itemKinds.push(declared);
|
|
908
|
+
registerDeclaredKindDraft(declared, variant, `${path}[]<${declared}>`, ctx);
|
|
909
|
+
ctx.arrayBindings.push({ arrayField: fieldName, itemKindSlug: declared });
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
...requiredNullableFlags(required, nullable),
|
|
913
|
+
type: "array",
|
|
914
|
+
itemKinds
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
const itemType = resolvePrimaryType(itemNode).type;
|
|
918
|
+
if (itemType === "string") {
|
|
919
|
+
if (Array.isArray(itemNode.enum)) {
|
|
920
|
+
return {
|
|
921
|
+
...requiredNullableFlags(required, nullable),
|
|
922
|
+
type: "string[]",
|
|
923
|
+
values: itemNode.enum.filter(
|
|
924
|
+
(v) => typeof v === "string"
|
|
925
|
+
)
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
...requiredNullableFlags(required, nullable),
|
|
930
|
+
type: "string[]"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
if (itemType === "number") {
|
|
934
|
+
return {
|
|
935
|
+
...requiredNullableFlags(required, nullable),
|
|
936
|
+
type: "number[]"
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
if (itemType === "boolean") {
|
|
940
|
+
return {
|
|
941
|
+
...requiredNullableFlags(required, nullable),
|
|
942
|
+
type: "boolean[]"
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
if (itemType === "object" && isRecord(itemNode.properties)) {
|
|
946
|
+
const declaredItemKind = readBlockKindFromProperties(itemNode.properties);
|
|
947
|
+
const itemKindSlug = declaredItemKind ?? synthesizeItemKindSlug(ctx.schemaName, fieldName);
|
|
948
|
+
const itemRequired = new Set(
|
|
949
|
+
Array.isArray(itemNode.required) ? itemNode.required.filter((k) => typeof k === "string") : []
|
|
950
|
+
);
|
|
951
|
+
const itemFields = {};
|
|
952
|
+
for (const [propName, propNode] of Object.entries(itemNode.properties)) {
|
|
953
|
+
if (!isRecord(propNode)) continue;
|
|
954
|
+
if (propName === KIND_KEY) continue;
|
|
955
|
+
itemFields[propName] = convertFieldOrWiden(
|
|
956
|
+
propName,
|
|
957
|
+
propNode,
|
|
958
|
+
itemRequired.has(propName),
|
|
959
|
+
`${path}[].${propName}`,
|
|
960
|
+
ctx
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
const alreadyHasBlockKind = propNameIsBlockKind(itemNode.properties) || itemRequired.has(KIND_KEY);
|
|
964
|
+
ctx.arrayBindings.push({
|
|
965
|
+
arrayField: fieldName,
|
|
966
|
+
itemKindSlug
|
|
967
|
+
});
|
|
968
|
+
if (!ctx.blockSchemas.some((draft) => draft.slug === itemKindSlug)) {
|
|
969
|
+
ctx.blockSchemas.push({
|
|
970
|
+
slug: itemKindSlug,
|
|
971
|
+
label: declaredItemKind ? formatBlockLabel(itemKindSlug) : formatBlockLabel(`${ctx.schemaName}_${fieldName}_item`),
|
|
972
|
+
fields: itemFields
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
if (!alreadyHasBlockKind) {
|
|
976
|
+
ctx.problems.push({
|
|
977
|
+
severity: "warning",
|
|
978
|
+
path: `${path}[]`,
|
|
979
|
+
message: `Array "${fieldName}" items need __kind:"${itemKindSlug}" at runtime (OPTION 1: server injects, OPTION 2: use agent schema with __kind).`
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
...requiredNullableFlags(required, nullable),
|
|
984
|
+
type: "array",
|
|
985
|
+
itemKinds: [itemKindSlug]
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
ctx.problems.push({
|
|
989
|
+
severity: "error",
|
|
990
|
+
path,
|
|
991
|
+
message: `Unsupported array items type "${itemType ?? "unknown"}".`
|
|
992
|
+
});
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
if (type === "object") {
|
|
996
|
+
const ap = node.additionalProperties;
|
|
997
|
+
const apIsOpen = ap === true || isRecord(ap) && isAnyValueSchema(ap);
|
|
998
|
+
if (isRecord(node.properties)) {
|
|
999
|
+
const nestedRequired = new Set(
|
|
1000
|
+
Array.isArray(node.required) ? node.required.filter((k) => typeof k === "string") : []
|
|
1001
|
+
);
|
|
1002
|
+
const nestedFields = {};
|
|
1003
|
+
for (const [propName, propNode] of Object.entries(node.properties)) {
|
|
1004
|
+
if (!isRecord(propNode)) continue;
|
|
1005
|
+
if (propName === KIND_KEY) continue;
|
|
1006
|
+
nestedFields[propName] = convertFieldOrWiden(
|
|
1007
|
+
propName,
|
|
1008
|
+
propNode,
|
|
1009
|
+
nestedRequired.has(propName),
|
|
1010
|
+
`${path}.${propName}`,
|
|
1011
|
+
ctx
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
const referencedKind = readBlockKindFromProperties(node.properties);
|
|
1015
|
+
if (referencedKind) {
|
|
1016
|
+
if (!ctx.refsInProgress.has(referencedKind)) {
|
|
1017
|
+
ctx.refsInProgress.add(referencedKind);
|
|
1018
|
+
try {
|
|
1019
|
+
registerDeclaredKindDraft(referencedKind, node, path, ctx);
|
|
1020
|
+
} finally {
|
|
1021
|
+
ctx.refsInProgress.delete(referencedKind);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
...requiredNullableFlags(required, nullable),
|
|
1026
|
+
type: "object",
|
|
1027
|
+
kind: referencedKind
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
if (Object.keys(nestedFields).length === 0 && !apIsOpen && isRecord(ap) && KIND_KEY in node.properties) {
|
|
1031
|
+
const apType = resolvePrimaryType(ap).type;
|
|
1032
|
+
return {
|
|
1033
|
+
...requiredNullableFlags(required, nullable),
|
|
1034
|
+
type: "record",
|
|
1035
|
+
values: apType === "string" || apType === "number" || apType === "boolean" ? apType : "json"
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
let open = apIsOpen;
|
|
1039
|
+
if (!apIsOpen && isRecord(ap)) {
|
|
1040
|
+
ctx.problems.push({
|
|
1041
|
+
severity: "warning",
|
|
1042
|
+
path,
|
|
1043
|
+
message: "Typed additionalProperties alongside declared properties is not representable \u2014 treated as an OPEN object (extra values unconstrained)."
|
|
1044
|
+
});
|
|
1045
|
+
open = true;
|
|
1046
|
+
} else if (ap === false) {
|
|
1047
|
+
ctx.droppedMetadata.push({
|
|
1048
|
+
path,
|
|
1049
|
+
dropped: { additionalProperties: false }
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
...requiredNullableFlags(required, nullable),
|
|
1054
|
+
type: "inline_object",
|
|
1055
|
+
fields: nestedFields,
|
|
1056
|
+
...open ? { open: true } : {}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (apIsOpen || ap === void 0) {
|
|
1060
|
+
if (ap === void 0) {
|
|
1061
|
+
ctx.problems.push({
|
|
1062
|
+
severity: "info",
|
|
1063
|
+
path,
|
|
1064
|
+
message: `Bare object schema at "${path || "(root)"}" \u2014 record of any JSON values.`
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
...requiredNullableFlags(required, nullable),
|
|
1069
|
+
type: "record",
|
|
1070
|
+
values: "json"
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
if (isRecord(ap)) {
|
|
1074
|
+
const apType = resolvePrimaryType(ap).type;
|
|
1075
|
+
if (apType === "string" || apType === "number" || apType === "boolean") {
|
|
1076
|
+
return {
|
|
1077
|
+
...requiredNullableFlags(required, nullable),
|
|
1078
|
+
type: "record",
|
|
1079
|
+
values: apType
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
ctx.problems.push({
|
|
1083
|
+
severity: "warning",
|
|
1084
|
+
path,
|
|
1085
|
+
message: `Record value schema at "${path}" is not scalar \u2014 widened to record of any JSON values.`
|
|
1086
|
+
});
|
|
1087
|
+
return {
|
|
1088
|
+
...requiredNullableFlags(required, nullable),
|
|
1089
|
+
type: "record",
|
|
1090
|
+
values: "json"
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
...requiredNullableFlags(required, nullable),
|
|
1095
|
+
type: "inline_object",
|
|
1096
|
+
fields: {}
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
ctx.problems.push({
|
|
1100
|
+
severity: "error",
|
|
1101
|
+
path,
|
|
1102
|
+
message: `Unsupported or missing JSON Schema type at "${path}".`
|
|
1103
|
+
});
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
function propNameIsBlockKind(properties) {
|
|
1107
|
+
return KIND_KEY in properties;
|
|
1108
|
+
}
|
|
1109
|
+
function readBlockKindFromProperties(properties) {
|
|
1110
|
+
const kindProp = properties[KIND_KEY];
|
|
1111
|
+
if (!isRecord(kindProp)) return null;
|
|
1112
|
+
if (typeof kindProp.const === "string") return kindProp.const;
|
|
1113
|
+
if (Array.isArray(kindProp.enum) && typeof kindProp.enum[0] === "string") {
|
|
1114
|
+
return kindProp.enum[0];
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
function normalizeAiSchemaInput(input) {
|
|
1119
|
+
const parseErrors = [];
|
|
1120
|
+
if (!isRecord(input)) {
|
|
1121
|
+
return {
|
|
1122
|
+
name: null,
|
|
1123
|
+
strict: null,
|
|
1124
|
+
rootSchema: null,
|
|
1125
|
+
parseErrors: ["Input must be a JSON object."]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
if (isRecord(input.json_schema)) {
|
|
1129
|
+
return normalizeAiSchemaInput(input.json_schema);
|
|
1130
|
+
}
|
|
1131
|
+
if (isRecord(input.schema) && typeof input.name === "string") {
|
|
1132
|
+
return {
|
|
1133
|
+
name: input.name,
|
|
1134
|
+
strict: typeof input.strict === "boolean" ? input.strict : null,
|
|
1135
|
+
rootSchema: input.schema,
|
|
1136
|
+
parseErrors: []
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof input.type === "string" || Array.isArray(input.type) || input.properties || input.items) {
|
|
1140
|
+
const name = typeof input.name === "string" ? input.name : typeof input.title === "string" ? input.title : null;
|
|
1141
|
+
return {
|
|
1142
|
+
name,
|
|
1143
|
+
strict: typeof input.strict === "boolean" ? input.strict : null,
|
|
1144
|
+
rootSchema: input,
|
|
1145
|
+
parseErrors: []
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
parseErrors.push(
|
|
1149
|
+
"Expected OpenAI output_schema shape { name, schema } or a root JSON Schema object."
|
|
1150
|
+
);
|
|
1151
|
+
return { name: null, strict: null, rootSchema: null, parseErrors };
|
|
1152
|
+
}
|
|
1153
|
+
function convertAiSchemaToBlockFields(schemaName, rootSchema, strict) {
|
|
1154
|
+
const rawDefs = isRecord(rootSchema.$defs) ? rootSchema.$defs : isRecord(rootSchema.definitions) ? rootSchema.definitions : {};
|
|
1155
|
+
const defs = {};
|
|
1156
|
+
for (const [name, node] of Object.entries(rawDefs)) {
|
|
1157
|
+
if (isRecord(node)) defs[name] = node;
|
|
1158
|
+
}
|
|
1159
|
+
const ctx = {
|
|
1160
|
+
schemaName,
|
|
1161
|
+
strict,
|
|
1162
|
+
problems: [],
|
|
1163
|
+
droppedMetadata: [],
|
|
1164
|
+
blockSchemas: [],
|
|
1165
|
+
arrayBindings: [],
|
|
1166
|
+
defs,
|
|
1167
|
+
rootSchema,
|
|
1168
|
+
refsInProgress: /* @__PURE__ */ new Set()
|
|
1169
|
+
};
|
|
1170
|
+
ctx.droppedMetadata.push(...collectDropped(rootSchema, ""));
|
|
1171
|
+
const rootAp = rootSchema.additionalProperties;
|
|
1172
|
+
const rootIsOpen = rootAp === true || isRecord(rootAp) && isAnyValueSchema(rootAp);
|
|
1173
|
+
const properties = rootSchema.properties;
|
|
1174
|
+
if (!isRecord(properties) || rootIsOpen) {
|
|
1175
|
+
const rootField = convertProperty("$root", rootSchema, false, "$root", ctx);
|
|
1176
|
+
if (!rootField) {
|
|
1177
|
+
ctx.problems.push({
|
|
1178
|
+
severity: "error",
|
|
1179
|
+
path: "",
|
|
1180
|
+
message: "Root schema could not be converted (see problems above) \u2014 expected an object with properties, a scalar/array/json root, or an open object."
|
|
1181
|
+
});
|
|
1182
|
+
return emptyConversionResult(schemaName, strict, ctx);
|
|
1183
|
+
}
|
|
1184
|
+
ctx.blockSchemas.unshift({
|
|
1185
|
+
slug: schemaName,
|
|
1186
|
+
label: formatBlockLabel(schemaName),
|
|
1187
|
+
fields: {},
|
|
1188
|
+
root: rootField
|
|
1189
|
+
});
|
|
1190
|
+
return {
|
|
1191
|
+
schemaName,
|
|
1192
|
+
strict,
|
|
1193
|
+
blockSchemas: ctx.blockSchemas,
|
|
1194
|
+
problems: ctx.problems,
|
|
1195
|
+
droppedMetadata: ctx.droppedMetadata
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const required = new Set(
|
|
1199
|
+
Array.isArray(rootSchema.required) ? rootSchema.required.filter((k) => typeof k === "string") : []
|
|
1200
|
+
);
|
|
1201
|
+
const convertedFields = {};
|
|
1202
|
+
for (const [fieldName, fieldNode] of Object.entries(properties)) {
|
|
1203
|
+
if (!isRecord(fieldNode)) continue;
|
|
1204
|
+
if (fieldName === KIND_KEY) continue;
|
|
1205
|
+
convertedFields[fieldName] = convertFieldOrWiden(
|
|
1206
|
+
fieldName,
|
|
1207
|
+
fieldNode,
|
|
1208
|
+
required.has(fieldName),
|
|
1209
|
+
fieldName,
|
|
1210
|
+
ctx
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
const rootHasKind = propNameIsBlockKind(properties) || required.has(KIND_KEY);
|
|
1214
|
+
if (!rootHasKind) {
|
|
1215
|
+
ctx.problems.push({
|
|
1216
|
+
severity: "warning",
|
|
1217
|
+
path: "",
|
|
1218
|
+
message: `Root object needs __kind:"${schemaName}" at runtime (OPTION 1: server injects, OPTION 2: use agent schema with __kind).`
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
const rootDraft = {
|
|
1222
|
+
slug: schemaName,
|
|
1223
|
+
label: formatBlockLabel(schemaName),
|
|
1224
|
+
fields: convertedFields
|
|
1225
|
+
};
|
|
1226
|
+
ctx.blockSchemas.unshift(rootDraft);
|
|
1227
|
+
if (typeof rootSchema.additionalProperties === "boolean") {
|
|
1228
|
+
ctx.droppedMetadata.push({
|
|
1229
|
+
path: "",
|
|
1230
|
+
dropped: { additionalProperties: rootSchema.additionalProperties }
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
if (strict) {
|
|
1234
|
+
ctx.droppedMetadata.push({
|
|
1235
|
+
path: "",
|
|
1236
|
+
dropped: { strict }
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
schemaName,
|
|
1241
|
+
strict,
|
|
1242
|
+
blockSchemas: ctx.blockSchemas,
|
|
1243
|
+
problems: ctx.problems,
|
|
1244
|
+
droppedMetadata: ctx.droppedMetadata
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
function emptyConversionResult(schemaName, strict, ctx) {
|
|
1248
|
+
return {
|
|
1249
|
+
schemaName,
|
|
1250
|
+
strict,
|
|
1251
|
+
blockSchemas: ctx.blockSchemas,
|
|
1252
|
+
problems: ctx.problems,
|
|
1253
|
+
droppedMetadata: ctx.droppedMetadata
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
function compareFieldSchemas(aiField, blockField, fieldName) {
|
|
1257
|
+
const aiPresent = aiField !== void 0;
|
|
1258
|
+
const blockPresent = blockField !== void 0;
|
|
1259
|
+
const aiSummary = aiField ? fieldSchemaSummary(aiField) : null;
|
|
1260
|
+
const blockSummary = blockField ? fieldSchemaSummary(blockField) : null;
|
|
1261
|
+
if (aiPresent && blockPresent) {
|
|
1262
|
+
if (aiSummary === blockSummary) {
|
|
1263
|
+
return {
|
|
1264
|
+
field: fieldName,
|
|
1265
|
+
aiPresent,
|
|
1266
|
+
blockPresent,
|
|
1267
|
+
aiSummary,
|
|
1268
|
+
blockSummary,
|
|
1269
|
+
status: "match"
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
const aiRicher = aiField?.type === "enum" && blockField?.type === "string" || aiField?.type === "inline_object" && blockField?.type !== "inline_object";
|
|
1273
|
+
const blockRicher = blockField?.type === "enum" && aiField?.type === "string" || blockField?.required && !aiField?.required;
|
|
1274
|
+
return {
|
|
1275
|
+
field: fieldName,
|
|
1276
|
+
aiPresent,
|
|
1277
|
+
blockPresent,
|
|
1278
|
+
aiSummary,
|
|
1279
|
+
blockSummary,
|
|
1280
|
+
status: aiRicher ? "ai_richer" : blockRicher ? "block_richer" : "type_mismatch",
|
|
1281
|
+
...aiSummary !== blockSummary && {
|
|
1282
|
+
detail: `${aiSummary} vs ${blockSummary}`
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
if (aiPresent) {
|
|
1287
|
+
return {
|
|
1288
|
+
field: fieldName,
|
|
1289
|
+
aiPresent,
|
|
1290
|
+
blockPresent,
|
|
1291
|
+
aiSummary,
|
|
1292
|
+
blockSummary,
|
|
1293
|
+
status: "ai_only"
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
field: fieldName,
|
|
1298
|
+
aiPresent,
|
|
1299
|
+
blockPresent,
|
|
1300
|
+
aiSummary,
|
|
1301
|
+
blockSummary,
|
|
1302
|
+
status: "block_only"
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function compareWithExistingKindSchema(convertedFields, existing) {
|
|
1306
|
+
if (!existing) return [];
|
|
1307
|
+
const allFields = /* @__PURE__ */ new Set([
|
|
1308
|
+
...Object.keys(convertedFields),
|
|
1309
|
+
...Object.keys(existing.fields)
|
|
1310
|
+
]);
|
|
1311
|
+
return [...allFields].sort().map(
|
|
1312
|
+
(field) => compareFieldSchemas(
|
|
1313
|
+
convertedFields[field],
|
|
1314
|
+
existing.fields[field],
|
|
1315
|
+
field
|
|
1316
|
+
)
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
function runSchemaConversion(input, existingSchemas) {
|
|
1320
|
+
const normalized = normalizeAiSchemaInput(input);
|
|
1321
|
+
if (!normalized.rootSchema || !normalized.name) {
|
|
1322
|
+
return {
|
|
1323
|
+
schemaName: normalized.name,
|
|
1324
|
+
strict: normalized.strict,
|
|
1325
|
+
blockSchemas: [],
|
|
1326
|
+
agentSchemaWithKinds: null,
|
|
1327
|
+
problems: [],
|
|
1328
|
+
droppedMetadata: [],
|
|
1329
|
+
comparisons: [],
|
|
1330
|
+
parseErrors: normalized.parseErrors.length ? normalized.parseErrors : ["Schema name and root schema are required."]
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
const strict = normalized.strict ?? false;
|
|
1334
|
+
const core = convertAiSchemaToBlockFields(
|
|
1335
|
+
normalized.name,
|
|
1336
|
+
normalized.rootSchema,
|
|
1337
|
+
strict
|
|
1338
|
+
);
|
|
1339
|
+
const existing = existingSchemas[normalized.name] ?? null;
|
|
1340
|
+
const rootDraft = core.blockSchemas[0];
|
|
1341
|
+
const comparisons = rootDraft ? compareWithExistingKindSchema(rootDraft.fields, existing) : [];
|
|
1342
|
+
const problems = [...core.problems];
|
|
1343
|
+
if (!existing) {
|
|
1344
|
+
problems.push({
|
|
1345
|
+
severity: "info",
|
|
1346
|
+
path: "",
|
|
1347
|
+
message: `No existing block schema with slug "${normalized.name}".`
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
const agentSchemaWithKinds = buildAgentSchemaWithRenderBlockSupport(
|
|
1351
|
+
input,
|
|
1352
|
+
normalized.name,
|
|
1353
|
+
collectArrayBindingsFromDrafts(core.blockSchemas, normalized.name),
|
|
1354
|
+
strict
|
|
1355
|
+
);
|
|
1356
|
+
return {
|
|
1357
|
+
...core,
|
|
1358
|
+
agentSchemaWithKinds,
|
|
1359
|
+
comparisons,
|
|
1360
|
+
problems,
|
|
1361
|
+
parseErrors: []
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
function collectArrayBindingsFromDrafts(drafts, rootSlug) {
|
|
1365
|
+
const root = drafts.find((d) => d.slug === rootSlug);
|
|
1366
|
+
if (!root) return [];
|
|
1367
|
+
const bindings = [];
|
|
1368
|
+
for (const [fieldName, field] of Object.entries(root.fields)) {
|
|
1369
|
+
if (field.type !== "array") continue;
|
|
1370
|
+
for (const itemKind of field.itemKinds) {
|
|
1371
|
+
bindings.push({ arrayField: fieldName, itemKindSlug: itemKind });
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return bindings;
|
|
1375
|
+
}
|
|
1376
|
+
function validateBlockSchemaSavePlan(blockSchemas, existingSlugs, hasConversionErrors) {
|
|
1377
|
+
const errors = [];
|
|
1378
|
+
const normalizedExisting = new Set(
|
|
1379
|
+
existingSlugs.map((s) => s.trim().toLowerCase())
|
|
1380
|
+
);
|
|
1381
|
+
const batchSlugs = /* @__PURE__ */ new Set();
|
|
1382
|
+
const itemKindRefs = [];
|
|
1383
|
+
for (const draft of blockSchemas) {
|
|
1384
|
+
const slug = draft.slug.trim();
|
|
1385
|
+
const normalized = slug.toLowerCase();
|
|
1386
|
+
if (!slug) {
|
|
1387
|
+
errors.push("Every block schema must have a non-empty slug.");
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (batchSlugs.has(normalized)) {
|
|
1391
|
+
errors.push(`Duplicate slug in conversion batch: "${slug}".`);
|
|
1392
|
+
}
|
|
1393
|
+
batchSlugs.add(normalized);
|
|
1394
|
+
}
|
|
1395
|
+
const entries = blockSchemas.map((draft) => {
|
|
1396
|
+
const existsInDb = normalizedExisting.has(draft.slug.trim().toLowerCase());
|
|
1397
|
+
return {
|
|
1398
|
+
draft,
|
|
1399
|
+
existsInDb,
|
|
1400
|
+
willSave: !existsInDb
|
|
1401
|
+
};
|
|
1402
|
+
});
|
|
1403
|
+
for (const draft of blockSchemas) {
|
|
1404
|
+
for (const [fieldName, field] of Object.entries(draft.fields)) {
|
|
1405
|
+
if (field.type !== "array") continue;
|
|
1406
|
+
for (const itemKind of field.itemKinds) {
|
|
1407
|
+
const inBatch = batchSlugs.has(itemKind.trim().toLowerCase());
|
|
1408
|
+
const inDb = normalizedExisting.has(itemKind.trim().toLowerCase());
|
|
1409
|
+
const satisfied = inBatch || inDb;
|
|
1410
|
+
itemKindRefs.push({
|
|
1411
|
+
parentSlug: draft.slug,
|
|
1412
|
+
field: fieldName,
|
|
1413
|
+
itemKind,
|
|
1414
|
+
satisfied,
|
|
1415
|
+
source: inBatch ? "batch" : inDb ? "database" : "missing"
|
|
1416
|
+
});
|
|
1417
|
+
if (!satisfied) {
|
|
1418
|
+
errors.push(
|
|
1419
|
+
`"${draft.slug}".${fieldName} references itemKind "${itemKind}" \u2014 not in this batch and not in DB.`
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const newCount = entries.filter((e) => e.willSave).length;
|
|
1426
|
+
if (newCount === 0 && blockSchemas.length > 0) {
|
|
1427
|
+
errors.push("All block schema slugs already exist in the database.");
|
|
1428
|
+
}
|
|
1429
|
+
if (hasConversionErrors) {
|
|
1430
|
+
errors.push("Resolve conversion errors before saving.");
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
entries,
|
|
1434
|
+
itemKindRefs,
|
|
1435
|
+
newCount,
|
|
1436
|
+
canSave: errors.length === 0 && newCount > 0,
|
|
1437
|
+
errors
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
function fieldsToDbPayload(fields) {
|
|
1441
|
+
return JSON.parse(JSON.stringify(fields));
|
|
1442
|
+
}
|
|
1443
|
+
function isDuplicateBlockSlug(slug, entries) {
|
|
1444
|
+
const normalized = slug.trim().toLowerCase();
|
|
1445
|
+
return entries.some((e) => e.slug.trim().toLowerCase() === normalized);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// convert/kind-to-json-schema.ts
|
|
1449
|
+
function collectReferencedKinds(fields) {
|
|
1450
|
+
const out = [];
|
|
1451
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1452
|
+
const add = (kind) => {
|
|
1453
|
+
if (!seen.has(kind)) {
|
|
1454
|
+
seen.add(kind);
|
|
1455
|
+
out.push(kind);
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
const visit = (field) => {
|
|
1459
|
+
if (field.type === "object") {
|
|
1460
|
+
add(field.kind);
|
|
1461
|
+
} else if (field.type === "array") {
|
|
1462
|
+
field.itemKinds.forEach(add);
|
|
1463
|
+
} else if (field.type === "union") {
|
|
1464
|
+
(field.kinds ?? []).forEach(add);
|
|
1465
|
+
} else if (field.type === "inline_object") {
|
|
1466
|
+
Object.values(field.fields).forEach(visit);
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
Object.values(fields).forEach(visit);
|
|
1470
|
+
return out;
|
|
1471
|
+
}
|
|
1472
|
+
function collectSchemaReferencedKinds(schema) {
|
|
1473
|
+
if (!schema.root) return collectReferencedKinds(schema.fields);
|
|
1474
|
+
return collectReferencedKinds({ [ROOT_REF_FIELD]: schema.root });
|
|
1475
|
+
}
|
|
1476
|
+
var ROOT_REF_FIELD = "__root";
|
|
1477
|
+
function kindSchemaToJsonSchema(kind, resolve, options = {}) {
|
|
1478
|
+
const strict = options.strict ?? false;
|
|
1479
|
+
const injectKind = options.injectKind ?? true;
|
|
1480
|
+
const rootSchema = resolve(kind);
|
|
1481
|
+
if (!rootSchema) return null;
|
|
1482
|
+
const visited = /* @__PURE__ */ new Set([kind]);
|
|
1483
|
+
const defsOrder = [];
|
|
1484
|
+
const resolvedDefs = /* @__PURE__ */ new Map();
|
|
1485
|
+
const unresolved = [];
|
|
1486
|
+
const queue = collectSchemaReferencedKinds(rootSchema);
|
|
1487
|
+
while (queue.length > 0) {
|
|
1488
|
+
const next = queue.shift();
|
|
1489
|
+
if (visited.has(next)) continue;
|
|
1490
|
+
visited.add(next);
|
|
1491
|
+
defsOrder.push(next);
|
|
1492
|
+
const schema = resolve(next);
|
|
1493
|
+
if (!schema) {
|
|
1494
|
+
unresolved.push(next);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
resolvedDefs.set(next, schema);
|
|
1498
|
+
queue.push(...collectSchemaReferencedKinds(schema));
|
|
1499
|
+
}
|
|
1500
|
+
const refFor = (slug) => slug === kind ? { $ref: "#" } : { $ref: `#/$defs/${slug}` };
|
|
1501
|
+
const withNull = (type, nullable) => nullable ? [type, "null"] : type;
|
|
1502
|
+
function fieldToJsonSchema(field) {
|
|
1503
|
+
const node = fieldToJsonSchemaCore(field);
|
|
1504
|
+
if (field.description !== void 0) node.description = field.description;
|
|
1505
|
+
if (field.default !== void 0) node.default = field.default;
|
|
1506
|
+
return node;
|
|
1507
|
+
}
|
|
1508
|
+
function fieldToJsonSchemaCore(field) {
|
|
1509
|
+
switch (field.type) {
|
|
1510
|
+
case "string":
|
|
1511
|
+
case "boolean":
|
|
1512
|
+
return { type: withNull(field.type, field.nullable) };
|
|
1513
|
+
case "number":
|
|
1514
|
+
return {
|
|
1515
|
+
type: withNull("number", field.nullable),
|
|
1516
|
+
...!strict && field.min !== void 0 ? { minimum: field.min } : {},
|
|
1517
|
+
...!strict && field.max !== void 0 ? { maximum: field.max } : {},
|
|
1518
|
+
...!strict && field.step !== void 0 ? { multipleOf: field.step } : {}
|
|
1519
|
+
};
|
|
1520
|
+
case "json":
|
|
1521
|
+
return {};
|
|
1522
|
+
case "string[]": {
|
|
1523
|
+
const items = field.values === void 0 ? { type: "string" } : field.open ? {
|
|
1524
|
+
anyOf: [
|
|
1525
|
+
{ type: "string", enum: [...field.values] },
|
|
1526
|
+
{ type: "string" }
|
|
1527
|
+
]
|
|
1528
|
+
} : { type: "string", enum: [...field.values] };
|
|
1529
|
+
return { type: withNull("array", field.nullable), items };
|
|
1530
|
+
}
|
|
1531
|
+
case "number[]":
|
|
1532
|
+
case "boolean[]":
|
|
1533
|
+
return {
|
|
1534
|
+
type: withNull("array", field.nullable),
|
|
1535
|
+
items: { type: scalarArrayItemType(field.type) }
|
|
1536
|
+
};
|
|
1537
|
+
case "json[]":
|
|
1538
|
+
return {
|
|
1539
|
+
type: withNull("array", field.nullable),
|
|
1540
|
+
items: {}
|
|
1541
|
+
};
|
|
1542
|
+
case "enum": {
|
|
1543
|
+
if (!field.open) {
|
|
1544
|
+
return {
|
|
1545
|
+
type: withNull("string", field.nullable),
|
|
1546
|
+
enum: [...field.values]
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
const variants = [
|
|
1550
|
+
{ type: "string", enum: [...field.values] },
|
|
1551
|
+
{ type: "string" }
|
|
1552
|
+
];
|
|
1553
|
+
if (field.nullable) variants.push({ type: "null" });
|
|
1554
|
+
return { anyOf: variants };
|
|
1555
|
+
}
|
|
1556
|
+
case "union": {
|
|
1557
|
+
const variants = field.scalars.map((scalar) => ({
|
|
1558
|
+
type: scalar
|
|
1559
|
+
}));
|
|
1560
|
+
for (const memberKind of field.kinds ?? []) {
|
|
1561
|
+
variants.push(refFor(memberKind));
|
|
1562
|
+
}
|
|
1563
|
+
if (field.nullable) variants.push({ type: "null" });
|
|
1564
|
+
return { anyOf: variants };
|
|
1565
|
+
}
|
|
1566
|
+
case "record":
|
|
1567
|
+
return {
|
|
1568
|
+
type: withNull("object", field.nullable),
|
|
1569
|
+
additionalProperties: field.values === "json" ? true : { type: field.values }
|
|
1570
|
+
};
|
|
1571
|
+
case "inline_object": {
|
|
1572
|
+
const properties = {};
|
|
1573
|
+
const required = [];
|
|
1574
|
+
for (const [name, child] of Object.entries(field.fields)) {
|
|
1575
|
+
properties[name] = fieldToJsonSchema(child);
|
|
1576
|
+
if (child.required) required.push(name);
|
|
1577
|
+
}
|
|
1578
|
+
return {
|
|
1579
|
+
type: withNull("object", field.nullable),
|
|
1580
|
+
properties,
|
|
1581
|
+
required,
|
|
1582
|
+
...field.open ? { additionalProperties: true } : strict ? { additionalProperties: false } : {}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
case "object":
|
|
1586
|
+
return field.nullable ? { anyOf: [refFor(field.kind), { type: "null" }] } : refFor(field.kind);
|
|
1587
|
+
case "array": {
|
|
1588
|
+
const [soleItemKind] = field.itemKinds;
|
|
1589
|
+
const items = field.itemKinds.length === 1 && soleItemKind !== void 0 ? refFor(soleItemKind) : { anyOf: field.itemKinds.map(refFor) };
|
|
1590
|
+
return { type: withNull("array", field.nullable), items };
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function kindObjectSchema(schema) {
|
|
1595
|
+
if (schema.root) {
|
|
1596
|
+
return fieldToJsonSchema(schema.root);
|
|
1597
|
+
}
|
|
1598
|
+
const properties = {};
|
|
1599
|
+
const required = [];
|
|
1600
|
+
for (const [name, field] of Object.entries(schema.fields)) {
|
|
1601
|
+
properties[name] = fieldToJsonSchema(field);
|
|
1602
|
+
if (field.required) required.push(name);
|
|
1603
|
+
}
|
|
1604
|
+
const node = {
|
|
1605
|
+
type: "object",
|
|
1606
|
+
properties,
|
|
1607
|
+
required,
|
|
1608
|
+
...strict ? { additionalProperties: false } : {}
|
|
1609
|
+
};
|
|
1610
|
+
return injectKind ? injectKindIntoObjectSchema(node, schema.kind, strict) : node;
|
|
1611
|
+
}
|
|
1612
|
+
function unresolvedStub(slug) {
|
|
1613
|
+
const node = {
|
|
1614
|
+
type: "object",
|
|
1615
|
+
description: `Unresolved kind "${slug}" \u2014 schema not available at export time.`
|
|
1616
|
+
};
|
|
1617
|
+
return injectKind ? injectKindIntoObjectSchema(node, slug, false) : node;
|
|
1618
|
+
}
|
|
1619
|
+
const rootNode = kindObjectSchema(rootSchema);
|
|
1620
|
+
if (defsOrder.length === 0) {
|
|
1621
|
+
return { name: kind, schema: rootNode, strict, unresolved };
|
|
1622
|
+
}
|
|
1623
|
+
const defs = {};
|
|
1624
|
+
for (const slug of defsOrder) {
|
|
1625
|
+
const schema = resolvedDefs.get(slug);
|
|
1626
|
+
defs[slug] = schema ? kindObjectSchema(schema) : unresolvedStub(slug);
|
|
1627
|
+
}
|
|
1628
|
+
return { name: kind, schema: { ...rootNode, $defs: defs }, strict, unresolved };
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// convert/json-schema-to-kind.ts
|
|
1632
|
+
function kindSchemaFromJsonSchema(kind, jsonSchema) {
|
|
1633
|
+
if (jsonSchema === null || jsonSchema === void 0) {
|
|
1634
|
+
return { schema: null, children: {}, problems: [] };
|
|
1635
|
+
}
|
|
1636
|
+
const normalized = normalizeAiSchemaInput(jsonSchema);
|
|
1637
|
+
const rootSchema = normalized.rootSchema ?? (typeof jsonSchema === "object" && !Array.isArray(jsonSchema) ? jsonSchema : null);
|
|
1638
|
+
if (!rootSchema) {
|
|
1639
|
+
return {
|
|
1640
|
+
schema: null,
|
|
1641
|
+
children: {},
|
|
1642
|
+
problems: [
|
|
1643
|
+
{
|
|
1644
|
+
severity: "error",
|
|
1645
|
+
path: "",
|
|
1646
|
+
message: `Kind "${kind}": emitted_json_schema is not a JSON object.`
|
|
1647
|
+
}
|
|
1648
|
+
]
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
const converted = convertAiSchemaToBlockFields(kind, rootSchema, false);
|
|
1652
|
+
let schema = null;
|
|
1653
|
+
const children = {};
|
|
1654
|
+
for (const draft of converted.blockSchemas) {
|
|
1655
|
+
const asKindSchema = {
|
|
1656
|
+
kind: draft.slug,
|
|
1657
|
+
fields: draft.fields,
|
|
1658
|
+
...draft.root ? { root: draft.root } : {}
|
|
1659
|
+
};
|
|
1660
|
+
if (draft.slug === kind) schema = asKindSchema;
|
|
1661
|
+
else children[draft.slug] = asKindSchema;
|
|
1662
|
+
}
|
|
1663
|
+
return { schema, children, problems: converted.problems };
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
exports.buildAgentSchemaWithRenderBlockSupport = buildAgentSchemaWithRenderBlockSupport;
|
|
1667
|
+
exports.collectReferencedKinds = collectReferencedKinds;
|
|
1668
|
+
exports.collectSchemaReferencedKinds = collectSchemaReferencedKinds;
|
|
1669
|
+
exports.compareWithExistingKindSchema = compareWithExistingKindSchema;
|
|
1670
|
+
exports.convertAiSchemaToBlockFields = convertAiSchemaToBlockFields;
|
|
1671
|
+
exports.fieldsToDbPayload = fieldsToDbPayload;
|
|
1672
|
+
exports.injectKindIntoObjectSchema = injectKindIntoObjectSchema;
|
|
1673
|
+
exports.isDuplicateBlockSlug = isDuplicateBlockSlug;
|
|
1674
|
+
exports.kindSchemaFromJsonSchema = kindSchemaFromJsonSchema;
|
|
1675
|
+
exports.kindSchemaToJsonSchema = kindSchemaToJsonSchema;
|
|
1676
|
+
exports.normalizeAiSchemaInput = normalizeAiSchemaInput;
|
|
1677
|
+
exports.runSchemaConversion = runSchemaConversion;
|
|
1678
|
+
exports.validateBlockSchemaSavePlan = validateBlockSchemaSavePlan;
|
|
1679
|
+
//# sourceMappingURL=convert.cjs.map
|
|
1680
|
+
//# sourceMappingURL=convert.cjs.map
|