Merge codex/tool-json-schema-dsl into codex/canonical-tool-output

# Conflicts:
#	docs/core-data-structures/tools.md
#	packages/core/tools/src/schema.ts
This commit is contained in:
Tianyi Cui
2026-07-23 01:08:18 +08:00
13 changed files with 336 additions and 101 deletions

View File

@@ -30,6 +30,21 @@ function violationsOf(schema: unknown, objectRoot = false): string[] {
throw new Error('expected schema rejection')
}
function recordWithForgedIntrinsicPrototype(
own: Record<string, unknown>,
inherited: Record<string, unknown> = {},
revoked = false,
): Record<string, unknown> {
const prototype = Object.assign(Object.create(null) as Record<string, unknown>, inherited)
const ForgedObject = function ForgedObject(): void {}
Object.defineProperty(ForgedObject, 'name', { value: 'Object' })
ForgedObject.prototype = prototype
const constructor = revoked ? Proxy.revocable(ForgedObject, {}) : undefined
if (constructor !== undefined) constructor.revoke()
Object.defineProperty(prototype, 'constructor', { value: constructor?.proxy ?? ForgedObject })
return Object.assign(Object.create(prototype) as Record<string, unknown>, own)
}
describe('the enforced raw JSON Schema subset', () => {
it('accepts every JSON root and every supported node', () => {
for (const schema of [
@@ -81,6 +96,23 @@ describe('the enforced raw JSON Schema subset', () => {
.toEqual(['schema.items is not supported beside oneOf'])
expect(violationsOf({ oneOf: [{ type: 'string' }, { type: 'weird' }] })[0])
.toContain('schema.oneOf[1].type')
const sparse = new Array<unknown>(2)
sparse[0] = { type: 'string' }
expect(violationsOf({ oneOf: sparse }))
.toEqual(['schema.oneOf must be an array of at least two schemas'])
const compensatedSparse = new Array<unknown>(2)
compensatedSparse[0] = { type: 'string' }
Object.defineProperty(compensatedSparse, 'extra', { value: true })
expect(violationsOf({ oneOf: compensatedSparse }))
.toEqual(['schema.oneOf must be an array of at least two schemas'])
class ExoticBranches extends Array<unknown> {}
expect(violationsOf({ oneOf: new ExoticBranches({ type: 'string' }, { type: 'null' }) }))
.toEqual(['schema.oneOf must be an array of at least two schemas'])
const explosiveArray = new Proxy([{ type: 'string' }, { type: 'null' }], {
getPrototypeOf() { throw new Error('prototype trap') },
})
expect(violationsOf({ oneOf: explosiveArray }))
.toEqual(['schema.oneOf must be an array of at least two schemas'])
})
it('rejects unknown and misplaced keywords without accepted-then-ignored behavior', () => {
@@ -134,6 +166,9 @@ describe('the enforced raw JSON Schema subset', () => {
'schema.properties must be an object of schemas',
'schema.required names "missing" which is not in properties',
])
const sparseRequired = new Array<string>(1)
expect(violationsOf({ type: 'object', required: sparseRequired }))
.toEqual(['schema.required must be an array of strings'])
})
it('requires type-correct scalar enum and const values', () => {
@@ -163,6 +198,9 @@ describe('the enforced raw JSON Schema subset', () => {
.toEqual(['schema.enum must be a non-empty array of string values'])
expect(violationsOf({ type: 'string', enum: ['a'], const: 'b' }))
.toEqual(['schema.const must be one of schema.enum when both are declared'])
const sparseEnum = new Array<string>(1)
expect(violationsOf({ type: 'string', enum: sparseEnum }))
.toEqual(['schema.enum must be a non-empty array of string values'])
})
it('validates annotation types and lossless JSON payloads', () => {
@@ -195,6 +233,8 @@ describe('the enforced raw JSON Schema subset', () => {
it('accepts lossless annotation containers from another JavaScript realm', () => {
const schema = runInNewContext(`({
type: 'object',
properties: { value: { type: 'string', enum: ['x'] } },
required: ['value'],
default: { x: 1 },
examples: [[{ ok: true }]],
})`) as unknown
@@ -212,6 +252,28 @@ describe('the enforced raw JSON Schema subset', () => {
.toEqual(['schema.properties must be an object of schemas'])
expect(violationsOf({ type: 'object', properties: { at: new Date(0) } }))
.toEqual(['schema.properties.at must be a schema object'])
const forgedSchema = recordWithForgedIntrinsicPrototype(
{ type: 'object' },
{ oneOf: [{ type: 'string' }, { type: 'null' }] },
)
expect(violationsOf(forgedSchema)).toEqual(['schema must be a schema object'])
expect(violationsOf(forgedSchema, true)).toEqual(['schema must be a schema object'])
expect(violationsOf(recordWithForgedIntrinsicPrototype({ type: 'string' }, {}, true)))
.toEqual(['schema must be a schema object'])
const prototypeWithoutConstructor = Object.create(null) as object
expect(violationsOf(Object.create(prototypeWithoutConstructor) as unknown))
.toEqual(['schema must be a schema object'])
expect(violationsOf(Object.defineProperty({ type: 'string' }, 'hidden', { value: true })))
.toEqual(['schema must be a schema object'])
expect(violationsOf({ type: 'string', [Symbol('hidden')]: true }))
.toEqual(['schema must be a schema object'])
expect(violationsOf(new Proxy({}, {
getPrototypeOf() { throw new Error('prototype trap') },
}))).toEqual(['schema must be a schema object'])
expect(violationsOf(new Proxy({}, {
ownKeys() { throw new Error('keys trap') },
}))).toEqual(['schema must be a schema object'])
})
it('asserts deeply nested raw unions without using the JavaScript call stack', () => {
@@ -369,6 +431,21 @@ describe('validateJsonSchemaValue', () => {
asserted({ type: 'object', properties: { constructor: { type: 'string' } } }),
{},
)).toEqual([])
const inheritedUnion = Object.assign(
Object.create({ oneOf: [{ type: 'string' }, { type: 'null' }] }) as JsonSchemaNode,
{ type: 'object' as const },
)
expect(validateJsonSchemaValue(inheritedUnion, {})).toEqual([])
expect(validateJsonSchemaValue(inheritedUnion, 'x')).toEqual(['"value" must be an object'])
expect(validateJsonSchemaValue(
{ type: 'object', properties: undefined } as unknown as JsonSchemaNode,
{},
)).toEqual([])
expect(validateJsonSchemaValue(
{ type: 'object', required: undefined } as unknown as JsonSchemaNode,
{},
)).toEqual([])
})
it('keeps assertNever as a forged-schema backstop', () => {

View File

@@ -71,6 +71,7 @@ describe('the unified author schema DSL', () => {
{ type: 'string', oneOf: [{ type: 'string' }, { type: 'null' }] },
{ oneOf: 'not-an-array' },
{ type: 'string', enum: 'a' },
{},
null,
]) {
expect(() => valueSchemaSpecToJsonSchema(schema as ValueSchemaSpec), JSON.stringify(schema)).toThrow(JsonSchemaError)
@@ -80,6 +81,24 @@ describe('the unified author schema DSL', () => {
} as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
expect(() => parameterSchemaSpecToJsonSchema(null as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
expect(() => parameterSchemaSpecToJsonSchema({ bad: 42 } as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
const symbolKey = Symbol('hidden')
expect(() => parameterSchemaSpecToJsonSchema({
value: { type: 'string' },
[symbolKey]: { type: 'number' },
} as unknown as ParameterSchemaSpec)).toThrow(JsonSchemaError)
const hiddenKey = Object.defineProperty({ value: { type: 'string' } }, 'hidden', {
value: { type: 'number' },
})
expect(() => parameterSchemaSpecToJsonSchema(hiddenKey as ParameterSchemaSpec)).toThrow(JsonSchemaError)
const sparseOneOf = new Array<ValueSchemaSpec>(2)
sparseOneOf[0] = { type: 'string' }
expect(() => valueSchemaSpecToJsonSchema({ oneOf: sparseOneOf } as unknown as ValueSchemaSpec)).toThrow(JsonSchemaError)
const decoratedEnum = Object.assign(['a'], { hidden: true })
expect(() => valueSchemaSpecToJsonSchema({
type: 'string',
enum: decoratedEnum,
})).toThrow(JsonSchemaError)
})
it('rejects cyclic author schemas', () => {
@@ -143,6 +162,22 @@ describe('the unified author schema DSL', () => {
}>>().toEqualTypeOf<{ id: number } & Record<string, JsonValue>>()
})
it('bounds inference for deeply nested author schemas', () => {
type Repeat<Count extends number, Result extends unknown[] = []> =
Result['length'] extends Count ? Result : Repeat<Count, [unknown, ...Result]>
type DeepArraySchema<Levels extends unknown[]> =
Levels extends [unknown, ...infer Rest]
? { type: 'array'; items: DeepArraySchema<Rest> }
: { type: 'string' }
type PeelArrays<Value, Levels extends unknown[]> =
Levels extends [unknown, ...infer Rest]
? Value extends (infer Item)[] ? PeelArrays<Item, Rest> : never
: Value
type DeepValue = InferValue<DeepArraySchema<Repeat<50>>>
expectTypeOf<PeelArrays<DeepValue, Repeat<16>>>().toEqualTypeOf<JsonValue>()
})
it('infers required and optional parameter keys', () => {
expectTypeOf<InferArgs<{
path: { type: 'string'; required: true }
@@ -152,6 +187,7 @@ describe('the unified author schema DSL', () => {
})
it('makes invalid author forms compile-time errors', () => {
const symbolKey = Symbol('parameter')
const invalidObjects = {
// @ts-expect-error explicit object schemas require an openness decision
object: { type: 'object' } satisfies ValueSchemaSpec,
@@ -161,7 +197,9 @@ describe('the unified author schema DSL', () => {
enum: { type: 'number', enum: ['1'] } satisfies ValueSchemaSpec,
// @ts-expect-error parameter requiredness is true-or-absent
required: { value: { type: 'string', required: false } } satisfies ParameterSchemaSpec,
// @ts-expect-error parameter maps accept string keys only
symbol: { [symbolKey]: { type: 'string' } } satisfies ParameterSchemaSpec,
}
expect(Object.keys(invalidObjects)).toHaveLength(4)
expect(Object.keys(invalidObjects)).toHaveLength(5)
})
})