99
1010from __future__ import annotations
1111
12+ from collections .abc import MutableMapping
1213from copy import deepcopy
1314from typing import Any , cast
1415
1718from azure .ai .agentserver .responses ._egress import strip_internal_metadata
1819from azure .ai .agentserver .responses import CreateResponse , ResponseEventStream
1920from azure .ai .agentserver .responses .models ._generated import ResponseObject
21+ from azure .ai .agentserver .responses .streaming ._internal_metadata import _ResponseInternalMetadataView
2022
2123
2224def _item () -> dict [str , Any ]:
@@ -34,9 +36,8 @@ def _item_internal_metadata(item: dict[str, Any]) -> dict[str, Any]:
3436 return item .setdefault ("internal_metadata" , {})
3537
3638
37- def _response_internal_metadata (response : ResponseObject ) -> dict [str , Any ]:
38- metadata = response .setdefault ("metadata" , {})
39- return metadata .setdefault ("_internal_metadata" , {})
39+ def _response_internal_metadata (response : ResponseObject ) -> MutableMapping [str , Any ]:
40+ return _ResponseInternalMetadataView (cast (dict [str , Any ], response ))
4041
4142
4243# --------------------------------------------------------------------------
@@ -150,8 +151,8 @@ def test_t1r_response_empty_view_when_unset():
150151def test_t2r_response_stores_under_reserved_key ():
151152 resp = _response ()
152153 _response_internal_metadata (resp )["phase" ] = 3
153- assert resp ["metadata" ]["_internal_metadata" ] == {"phase" : 3 }
154- assert dict (resp [ "metadata" ][ "_internal_metadata" ] ) == {"phase" : 3 }
154+ assert resp ["metadata" ]["_internal_metadata" ] == ' {"phase":3}'
155+ assert dict (_response_internal_metadata ( resp ) ) == {"phase" : 3 }
155156
156157
157158def test_t3r_in_place_mutation_writes_through ():
@@ -173,15 +174,25 @@ def test_t4r_does_not_clobber_client_metadata():
173174def test_t5r_clear_removes_only_reserved_key ():
174175 resp = _response ()
175176 resp ["metadata" ] = {"user" : "x" }
176- _response_internal_metadata (resp )["phase" ] = 3
177- resp ["metadata" ].pop ("_internal_metadata" )
177+ metadata = _response_internal_metadata (resp )
178+ metadata ["phase" ] = 3
179+ metadata .clear ()
178180 assert dict (resp ["metadata" ]) == {"user" : "x" }
179181
180182
181183def test_t6r_512_char_guard ():
182184 resp = _response ()
183- _response_internal_metadata (resp )["big" ] = "x" * 600
184- assert resp ["metadata" ]["_internal_metadata" ]["big" ] == "x" * 600
185+ with pytest .raises (ValueError , match = "512-char limit" ):
186+ _response_internal_metadata (resp )["big" ] = "x" * 600
187+ assert "_internal_metadata" not in resp .get ("metadata" , {})
188+
189+
190+ def test_t6r_unicode_uses_character_limit_not_ascii_escape_length ():
191+ resp = _response ()
192+ value = "\N{SNOWMAN} " * 100
193+ _response_internal_metadata (resp )["unicode" ] = value
194+ assert len (resp ["metadata" ]["_internal_metadata" ]) < 512
195+ assert dict (_response_internal_metadata (resp )) == {"unicode" : value }
185196
186197
187198def test_t6r2_16_key_guard ():
@@ -192,8 +203,9 @@ def test_t6r2_16_key_guard():
192203
193204 resp16 = _response ()
194205 resp16 ["metadata" ] = {f"k{ i } " : "v" for i in range (16 )}
195- _response_internal_metadata (resp16 )["p" ] = 1
196- assert resp16 ["metadata" ]["_internal_metadata" ] == {"p" : 1 }
206+ with pytest .raises (ValueError , match = "already has 16 keys" ):
207+ _response_internal_metadata (resp16 )["p" ] = 1
208+ assert "_internal_metadata" not in resp16 ["metadata" ]
197209
198210
199211def test_t7r_v_shaped_response_empty_view ():
@@ -207,23 +219,43 @@ def test_t10r_stream_proxy_is_response_view():
207219 req = CreateResponse ({"model" : "m" , "input" : "hi" })
208220 stream = ResponseEventStream (response_id = "resp_1" , request = req )
209221 stream .internal_metadata ["phase" ] = 3
210- assert dict (stream .response ["metadata" ]["_internal_metadata" ]) == {"phase" : 3 }
211- stream .response ["metadata" ]["_internal_metadata" ]["x" ] = 1
222+ assert stream .response ["metadata" ]["_internal_metadata" ] == '{"phase":3}'
223+ second_view = _ResponseInternalMetadataView (stream .response )
224+ second_view ["x" ] = 1
212225 assert stream .internal_metadata ["x" ] == 1
213226
214227
215228def test_t28d_response_reserved_key_roundtrips ():
216229 resp = _response ()
217230 _response_internal_metadata (resp )["phase" ] = 3
218231 reloaded = dict (resp )
219- assert dict ( reloaded ["metadata" ]["_internal_metadata" ]) == {"phase" : 3 }
220- assert reloaded [ "metadata" ][ "_internal_metadata" ] == {"phase" : 3 }
232+ assert reloaded ["metadata" ]["_internal_metadata" ] == ' {"phase":3}'
233+ assert dict ( ResponseEventStream ( response_id = "resp_1" , response = reloaded ). internal_metadata ) == {"phase" : 3 }
221234
222235
223236def test_t7a_compact_deterministic_encoding ():
224237 # Deterministic so checkpoint idempotency byte-compare is stable.
225238 resp = _response ()
226239 _response_internal_metadata (resp )["b" ] = 2
227- resp ["metadata" ]["_internal_metadata" ]["a" ] = 1
240+ _response_internal_metadata (resp )["a" ] = 1
241+ assert resp ["metadata" ]["_internal_metadata" ] == '{"a":1,"b":2}'
228242 stripped = strip_internal_metadata (deepcopy (resp ))
229243 assert stripped ["metadata" ] is None
244+
245+
246+ def test_nested_dict_snapshot_is_normalized_without_property_access ():
247+ resp = _response ()
248+ resp ["metadata" ] = {"user" : "x" , "_internal_metadata" : {"phase" : 3 }}
249+
250+ stream = ResponseEventStream (response_id = "resp_1" , response = resp )
251+
252+ assert stream .response ["metadata" ] == {"user" : "x" , "_internal_metadata" : '{"phase":3}' }
253+
254+
255+ def test_nested_dict_snapshot_respects_total_metadata_key_limit ():
256+ resp = _response ()
257+ resp ["metadata" ] = {f"k{ i } " : "v" for i in range (16 )}
258+ resp ["metadata" ]["_internal_metadata" ] = {"phase" : 3 }
259+
260+ with pytest .raises (ValueError , match = "already has 17 keys" ):
261+ ResponseEventStream (response_id = "resp_1" , response = resp )
0 commit comments