@@ -10,10 +10,16 @@ class StatusEnum(StrEnum):
10
10
INACTIVE = "INACTIVE"
11
11
BANNED = "BANNED"
12
12
13
+ class PostalAddress(BaseModel):
14
+ line1: str
15
+ line2: str | None = None
16
+ city: str
17
+ country: str
18
+
13
19
class ContactInfo(BaseModel):
14
20
email: str = Field(..., description="User's email address")
15
21
phone: str | None = Field(None, description="Phone number (E.164 format)")
16
- address: str = Field(..., description="Address")
22
+ address: PostalAddress = Field(..., description="Address")
17
23
18
24
class Job(BaseModel):
19
25
title: str
@@ -93,9 +99,8 @@ const userProfileSchema = {
93
99
title : 'Phone' ,
94
100
} ,
95
101
address : {
102
+ $ref : '#/$defs/PostalAddress' ,
96
103
description : 'Address' ,
97
- title : 'Address' ,
98
- type : 'string' ,
99
104
} ,
100
105
} ,
101
106
required : [ 'email' , 'address' ] ,
@@ -238,6 +243,21 @@ const userProfileSchema = {
238
243
title : 'Pet' ,
239
244
type : 'object' ,
240
245
} ,
246
+ PostalAddress : {
247
+ properties : {
248
+ line1 : { title : 'Line1' , type : 'string' } ,
249
+ line2 : {
250
+ anyOf : [ { type : 'string' } , { type : 'null' } ] ,
251
+ default : null ,
252
+ title : 'Line2' ,
253
+ } ,
254
+ city : { title : 'City' , type : 'string' } ,
255
+ country : { title : 'Country' , type : 'string' } ,
256
+ } ,
257
+ required : [ 'line1' , 'city' , 'country' ] ,
258
+ title : 'PostalAddress' ,
259
+ type : 'object' ,
260
+ } ,
241
261
Preferences : {
242
262
properties : {
243
263
newsletter_subscribed : {
@@ -420,7 +440,19 @@ describe('derefer', () => {
420
440
it ( 'inlines $ref for nested object property (contact)' , ( ) => {
421
441
expect ( derefed . properties . contact . type ) . toBe ( 'object' ) ;
422
442
expect ( derefed . properties . contact . properties . email . type ) . toBe ( 'string' ) ;
423
- expect ( derefed . properties . contact . properties . address . type ) . toBe ( 'string' ) ;
443
+ expect ( derefed . properties . contact . properties . address . type ) . toBe ( 'object' ) ;
444
+ } ) ;
445
+
446
+ it ( 'inlines $ref for nested model inside ContactInfo (address -> PostalAddress)' , ( ) => {
447
+ const contact = derefed . properties . contact ;
448
+ expect ( contact . type ) . toBe ( 'object' ) ;
449
+
450
+ const addr = contact . properties . address ;
451
+ // PostalAddress should be fully inlined
452
+ expect ( addr . type ) . toBe ( 'object' ) ;
453
+ expect ( addr . properties . line1 . type ) . toBe ( 'string' ) ;
454
+ expect ( addr . properties . city . type ) . toBe ( 'string' ) ;
455
+ expect ( addr . properties . country . type ) . toBe ( 'string' ) ;
424
456
} ) ;
425
457
426
458
it ( 'inlines $ref for enum via $defs (status)' , ( ) => {
@@ -522,6 +554,19 @@ describe('transformGeminiToolParameters', () => {
522
554
} ) ;
523
555
} ) ;
524
556
557
+ it ( 'keeps nested model flattened correctly after deref (contact.address)' , ( ) => {
558
+ const addr = transformed . properties . contact . properties . address ;
559
+ expect ( addr . type ) . toBe ( 'object' ) ;
560
+ expect ( addr . properties . line1 . type ) . toBe ( 'string' ) ;
561
+ // line2 remains nullable string
562
+ expect ( addr . properties . line2 ) . toEqual ( {
563
+ type : 'string' ,
564
+ nullable : true ,
565
+ title : 'Line2' ,
566
+ default : null ,
567
+ } ) ;
568
+ } ) ;
569
+
525
570
it ( 'flattens anyOf [array-of-model, null] to array schema with nullable: true and preserves metadata (pets)' , ( ) => {
526
571
const pets = transformed . properties . pets ;
527
572
expect ( pets . type ) . toBe ( 'array' ) ;
0 commit comments