@@ -55,9 +55,6 @@ public sealed class BedrockNovaRealtimeSession : IRealtimeClientSession
5555 // bidirectional stream can't safely serve two concurrent readers.
5656 private int _activeStreamingEnumeration ;
5757
58- /// <summary>Maximum nesting depth for tool payloads to prevent stack overflow from malicious/malformed data.</summary>
59- private const int MaxToolPayloadDepth = 64 ;
60-
6158 /// <summary>Initializes a new instance of the <see cref="BedrockNovaRealtimeSession"/> class.</summary>
6259 /// <param name="runtime">The Amazon Bedrock Runtime client.</param>
6360 /// <param name="modelId">The model ID to use.</param>
@@ -291,7 +288,7 @@ public async IAsyncEnumerable<RealtimeServerMessage> GetStreamingResponseAsync(
291288 throw new InvalidOperationException ( "Session is not connected. Call ConnectAsync first." ) ;
292289 }
293290
294- if ( Interlocked . CompareExchange ( ref _activeStreamingEnumeration , 1 , 0 ) != 0 )
291+ if ( ! RealtimeAudioProtocol . TryBeginExclusiveEnumeration ( ref _activeStreamingEnumeration ) )
295292 {
296293 throw new InvalidOperationException (
297294 "Only one active streaming enumeration is allowed at a time. " +
@@ -419,7 +416,7 @@ public async IAsyncEnumerable<RealtimeServerMessage> GetStreamingResponseAsync(
419416 var item = new RealtimeConversationItem (
420417 new List < AIContent > ( ) ,
421418 id : currentContentId ,
422- role : MapRole ( currentRole ) ) ;
419+ role : RealtimeAudioProtocol . MapRole ( currentRole ) ) ;
423420
424421 yield return new ResponseOutputItemRealtimeServerMessage ( RealtimeServerMessageType . ResponseOutputItemAdded )
425422 {
@@ -495,7 +492,7 @@ public async IAsyncEnumerable<RealtimeServerMessage> GetStreamingResponseAsync(
495492 var item = new RealtimeConversationItem (
496493 itemContents ,
497494 id : currentContentId ,
498- role : MapRole ( currentRole ) ) ;
495+ role : RealtimeAudioProtocol . MapRole ( currentRole ) ) ;
499496
500497 yield return new ResponseOutputItemRealtimeServerMessage ( RealtimeServerMessageType . ResponseOutputItemDone )
501498 {
@@ -610,7 +607,7 @@ public async IAsyncEnumerable<RealtimeServerMessage> GetStreamingResponseAsync(
610607 using var argDoc = JsonDocument . Parse ( json ) ;
611608 if ( argDoc . RootElement . ValueKind == JsonValueKind . Object )
612609 {
613- return ConvertJsonElementToToolPayload ( argDoc . RootElement , 0 )
610+ return RealtimeAudioProtocol . ConvertJsonElementToToolPayload ( argDoc . RootElement , 0 )
614611 as Dictionary < string , object ? > ;
615612 }
616613 }
@@ -659,7 +656,7 @@ public async IAsyncEnumerable<RealtimeServerMessage> GetStreamingResponseAsync(
659656 }
660657 finally
661658 {
662- Volatile . Write ( ref _activeStreamingEnumeration , 0 ) ;
659+ RealtimeAudioProtocol . EndExclusiveEnumeration ( ref _activeStreamingEnumeration ) ;
663660 }
664661 }
665662
@@ -1511,16 +1508,6 @@ private static string MapStopReason(string? stopReason) =>
15111508 _ => RealtimeResponseStatus . Completed
15121509 } ;
15131510
1514- private static ChatRole ? MapRole ( string ? role ) =>
1515- role ? . ToUpperInvariant ( ) switch
1516- {
1517- "USER" => ChatRole . User ,
1518- "ASSISTANT" => ChatRole . Assistant ,
1519- "SYSTEM" => ChatRole . System ,
1520- "TOOL" => ChatRole . Tool ,
1521- _ => null
1522- } ;
1523-
15241511 private static UsageDetails ? ParseUsage ( JsonElement usageElement )
15251512 {
15261513 int totalInputTokens = 0 ;
@@ -1557,7 +1544,7 @@ private static string MapStopReason(string? stopReason) =>
15571544 private static string SerializeToolResult ( object ? result )
15581545 {
15591546 // Normalize first to handle JsonElement, byte[], nested dicts, etc.
1560- result = NormalizeToolPayload ( result ) ;
1547+ result = RealtimeAudioProtocol . NormalizeToolPayload ( result ) ;
15611548
15621549 if ( result is null )
15631550 {
@@ -1590,7 +1577,7 @@ private static string SerializeToolResult(object? result)
15901577 using var buffer = new MemoryStream ( ) ;
15911578 using ( var writer = new Utf8JsonWriter ( buffer ) )
15921579 {
1593- WriteNormalizedValue ( dict , writer ) ;
1580+ RealtimeAudioProtocol . WriteNormalizedValue ( dict , writer ) ;
15941581 }
15951582
15961583 return Encoding . UTF8 . GetString ( buffer . ToArray ( ) ) ;
@@ -1602,183 +1589,13 @@ private static string SerializeToolResult(object? result)
16021589 {
16031590 writer . WriteStartObject ( ) ;
16041591 writer . WritePropertyName ( "result" ) ;
1605- WriteNormalizedValue ( result , writer ) ;
1592+ RealtimeAudioProtocol . WriteNormalizedValue ( result , writer ) ;
16061593 writer . WriteEndObject ( ) ;
16071594 }
16081595
16091596 return Encoding . UTF8 . GetString ( wrapBuffer . ToArray ( ) ) ;
16101597 }
16111598
1612- /// <summary>
1613- /// Writes a normalized value (produced by <see cref="NormalizeToolPayload"/>) to a <see cref="Utf8JsonWriter"/>.
1614- /// Handles null, string, bool, numeric primitives, Dictionary, and List without reflection.
1615- /// </summary>
1616- private static void WriteNormalizedValue ( object ? value , Utf8JsonWriter writer )
1617- {
1618- switch ( value )
1619- {
1620- case null :
1621- writer . WriteNullValue ( ) ;
1622- break ;
1623- case string s :
1624- writer . WriteStringValue ( s ) ;
1625- break ;
1626- case bool b :
1627- writer . WriteBooleanValue ( b ) ;
1628- break ;
1629- case int i :
1630- writer . WriteNumberValue ( i ) ;
1631- break ;
1632- case long l :
1633- writer . WriteNumberValue ( l ) ;
1634- break ;
1635- case float f :
1636- writer . WriteNumberValue ( f ) ;
1637- break ;
1638- case double d :
1639- writer . WriteNumberValue ( d ) ;
1640- break ;
1641- case decimal m :
1642- writer . WriteNumberValue ( m ) ;
1643- break ;
1644- case Dictionary < string , object ? > dict :
1645- writer . WriteStartObject ( ) ;
1646- foreach ( var kvp in dict )
1647- {
1648- writer . WritePropertyName ( kvp . Key ) ;
1649- WriteNormalizedValue ( kvp . Value , writer ) ;
1650- }
1651- writer . WriteEndObject ( ) ;
1652- break ;
1653- case List < object ? > list :
1654- writer . WriteStartArray ( ) ;
1655- foreach ( var item in list )
1656- {
1657- WriteNormalizedValue ( item , writer ) ;
1658- }
1659- writer . WriteEndArray ( ) ;
1660- break ;
1661- default :
1662- writer . WriteStringValue ( value . ToString ( ) ) ;
1663- break ;
1664- }
1665- }
1666-
1667- /// <summary>
1668- /// Recursively normalizes a tool payload into a tree of primitives, dictionaries, and lists.
1669- /// Handles JsonElement, byte[], nested dicts/lists, and enforces a maximum nesting depth.
1670- /// </summary>
1671- internal static object ? NormalizeToolPayload ( object ? value , int depth = 0 )
1672- {
1673- ValidateToolPayloadDepth ( depth ) ;
1674-
1675- switch ( value )
1676- {
1677- case null :
1678- return null ;
1679- case byte [ ] bytes :
1680- return Convert . ToBase64String ( bytes ) ;
1681- case JsonElement element :
1682- return ConvertJsonElementToToolPayload ( element , depth + 1 ) ;
1683- case JsonDocument document :
1684- return ConvertJsonElementToToolPayload ( document . RootElement , depth + 1 ) ;
1685- case string :
1686- case bool :
1687- case int :
1688- case long :
1689- case float :
1690- case double :
1691- case decimal :
1692- return value ;
1693- case IReadOnlyDictionary < string , object ? > roDict :
1694- return NormalizeToolArguments ( roDict , depth + 1 ) ;
1695- case IEnumerable < KeyValuePair < string , object ? > > pairs :
1696- return NormalizeToolArguments (
1697- new Dictionary < string , object ? > ( pairs . Select ( p => p ) , StringComparer . Ordinal ) , depth + 1 ) ;
1698- case IDictionary dict :
1699- var mapped = new Dictionary < string , object ? > ( StringComparer . Ordinal ) ;
1700- foreach ( DictionaryEntry entry in dict )
1701- {
1702- string key = entry . Key . ToString ( ) ! ;
1703- mapped [ key ] = NormalizeToolPayload ( entry . Value , depth + 1 ) ;
1704- }
1705- return mapped ;
1706- case IEnumerable < AIContent > aiContents :
1707- return aiContents . Select ( content => NormalizeToolPayload ( content , depth + 1 ) ) . ToList ( ) ;
1708- case IEnumerable < object ? > enumerable :
1709- var list = new List < object ? > ( ) ;
1710- foreach ( var item in enumerable )
1711- {
1712- list . Add ( NormalizeToolPayload ( item , depth + 1 ) ) ;
1713- }
1714- return list ;
1715- default :
1716- return value . ToString ( ) ;
1717- }
1718- }
1719-
1720- /// <summary>
1721- /// Normalizes a dictionary of tool arguments, recursively normalizing each value.
1722- /// </summary>
1723- internal static Dictionary < string , object ? > NormalizeToolArguments ( IReadOnlyDictionary < string , object ? > arguments , int depth = 0 )
1724- {
1725- ValidateToolPayloadDepth ( depth ) ;
1726-
1727- var normalized = new Dictionary < string , object ? > ( arguments . Count , StringComparer . Ordinal ) ;
1728- foreach ( var pair in arguments )
1729- {
1730- normalized [ pair . Key ] = NormalizeToolPayload ( pair . Value , depth + 1 ) ;
1731- }
1732- return normalized ;
1733- }
1734-
1735- /// <summary>
1736- /// Converts a <see cref="JsonElement"/> to a tree of primitives, dictionaries, and lists.
1737- /// </summary>
1738- private static object ? ConvertJsonElementToToolPayload ( JsonElement element , int depth )
1739- {
1740- ValidateToolPayloadDepth ( depth ) ;
1741-
1742- switch ( element . ValueKind )
1743- {
1744- case JsonValueKind . Object :
1745- var dictionary = new Dictionary < string , object ? > ( StringComparer . Ordinal ) ;
1746- foreach ( var property in element . EnumerateObject ( ) )
1747- {
1748- dictionary [ property . Name ] = ConvertJsonElementToToolPayload ( property . Value , depth + 1 ) ;
1749- }
1750- return dictionary ;
1751- case JsonValueKind . Array :
1752- var arrayList = new List < object ? > ( ) ;
1753- foreach ( var item in element . EnumerateArray ( ) )
1754- {
1755- arrayList . Add ( ConvertJsonElementToToolPayload ( item , depth + 1 ) ) ;
1756- }
1757- return arrayList ;
1758- case JsonValueKind . String :
1759- return element . GetString ( ) ;
1760- case JsonValueKind . Number :
1761- return element . TryGetInt64 ( out long l ) ? l : element . GetDouble ( ) ;
1762- case JsonValueKind . True :
1763- return true ;
1764- case JsonValueKind . False :
1765- return false ;
1766- case JsonValueKind . Null :
1767- case JsonValueKind . Undefined :
1768- default :
1769- return null ;
1770- }
1771- }
1772-
1773- private static void ValidateToolPayloadDepth ( int depth )
1774- {
1775- if ( depth > MaxToolPayloadDepth )
1776- {
1777- throw new InvalidOperationException (
1778- $ "Realtime tool payloads exceed the maximum supported nesting depth of { MaxToolPayloadDepth } .") ;
1779- }
1780- }
1781-
17821599 #endregion
17831600}
17841601
0 commit comments