(value: unknown)
| 1334 | |
| 1335 | // Mirrors Codex's Rust JSON schema compatibility lowering for OpenAI tool schemas. |
| 1336 | function sanitizeOpenAISchema(value: unknown): unknown { |
| 1337 | const types = ["string", "number", "boolean", "integer", "object", "array", "null"] |
| 1338 | const compositionKeys = ["anyOf", "oneOf", "allOf"] |
| 1339 | |
| 1340 | // JSON Schema's boolean form (`true`/`false`) is unsupported by OpenAI tool schemas. |
| 1341 | if (typeof value === "boolean") return { type: "string" } |
| 1342 | if (Array.isArray(value)) return value.map(sanitizeOpenAISchema) |
| 1343 | if (!isPlainObject(value)) return value |
| 1344 | |
| 1345 | const result: JsonRecord = {} |
| 1346 | |
| 1347 | if (typeof value.$ref === "string") result.$ref = value.$ref |
| 1348 | if (typeof value.description === "string") result.description = value.description |
| 1349 | if ("const" in value) result.enum = [value.const] |
| 1350 | else if (Array.isArray(value.enum)) result.enum = value.enum |
| 1351 | |
| 1352 | if (isPlainObject(value.properties)) { |
| 1353 | result.properties = Object.fromEntries( |
| 1354 | Object.entries(value.properties).map(([key, item]) => [key, sanitizeOpenAISchema(item)]), |
| 1355 | ) |
| 1356 | } |
| 1357 | |
| 1358 | if (Array.isArray(value.required)) { |
| 1359 | result.required = value.required.filter((item) => typeof item === "string") |
| 1360 | } |
| 1361 | |
| 1362 | if ("items" in value) result.items = sanitizeOpenAISchema(value.items) |
| 1363 | |
| 1364 | if ("additionalProperties" in value) { |
| 1365 | result.additionalProperties = |
| 1366 | typeof value.additionalProperties === "boolean" |
| 1367 | ? value.additionalProperties |
| 1368 | : sanitizeOpenAISchema(value.additionalProperties) |
| 1369 | } |
| 1370 | |
| 1371 | for (const key of compositionKeys) { |
| 1372 | if (Array.isArray(value[key])) result[key] = value[key].map(sanitizeOpenAISchema) |
| 1373 | } |
| 1374 | |
| 1375 | for (const key of ["$defs", "definitions"]) { |
| 1376 | if (isPlainObject(value[key])) { |
| 1377 | result[key] = Object.fromEntries( |
| 1378 | Object.entries(value[key]).map(([name, item]) => [name, sanitizeOpenAISchema(item)]), |
| 1379 | ) |
| 1380 | } |
| 1381 | } |
| 1382 | |
| 1383 | const schemaTypes = |
| 1384 | typeof value.type === "string" |
| 1385 | ? types.includes(value.type) |
| 1386 | ? [value.type] |
| 1387 | : [] |
| 1388 | : Array.isArray(value.type) |
| 1389 | ? value.type.filter((item) => typeof item === "string" && types.includes(item)) |
| 1390 | : [] |
| 1391 | |
| 1392 | if (schemaTypes.length === 0 && (typeof result.$ref === "string" || compositionKeys.some((key) => key in result))) { |
| 1393 | return result |
no test coverage detected